From ba17e041834eacd77d38d7c7ebca84f94d28b945 Mon Sep 17 00:00:00 2001 From: Maxime Bloch Date: Wed, 22 Jul 2020 04:02:13 +0200 Subject: [PATCH] Add basic algorithm and celery stuff --- KeRS/__init__.py | 7 ++ KeRS/settings.py | 1 + Makefile | 6 ++ events/migrations/0001_initial.py | 4 +- events/migrations/0002_auto_20200722_0101.py | 23 +++++++ events/models.py | 7 +- events/tasks.py | 71 ++++++++++++++++++++ events/urls.py | 1 + events/views.py | 11 ++- requirements.txt | 7 ++ 10 files changed, 133 insertions(+), 5 deletions(-) create mode 100644 Makefile create mode 100644 events/migrations/0002_auto_20200722_0101.py create mode 100644 events/tasks.py diff --git a/KeRS/__init__.py b/KeRS/__init__.py index e69de29..070e835 100644 --- a/KeRS/__init__.py +++ b/KeRS/__init__.py @@ -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',) diff --git a/KeRS/settings.py b/KeRS/settings.py index 33a4a3a..f4e6bf7 100644 --- a/KeRS/settings.py +++ b/KeRS/settings.py @@ -40,6 +40,7 @@ INSTALLED_APPS = [ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'django_celery_beat' ] + OWN_APPS MIDDLEWARE = [ diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..bc5c2ae --- /dev/null +++ b/Makefile @@ -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 diff --git a/events/migrations/0001_initial.py b/events/migrations/0001_initial.py index f50a5f5..046f480 100644 --- a/events/migrations/0001_initial.py +++ b/events/migrations/0001_initial.py @@ -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.db import migrations, models @@ -26,7 +26,7 @@ class Migration(migrations.Migration): name='EventRegistration', fields=[ ('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')), ('user_id', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], diff --git a/events/migrations/0002_auto_20200722_0101.py b/events/migrations/0002_auto_20200722_0101.py new file mode 100644 index 0000000..ab3ddbc --- /dev/null +++ b/events/migrations/0002_auto_20200722_0101.py @@ -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', + ), + ] diff --git a/events/models.py b/events/models.py index b030d7f..54cc830 100644 --- a/events/models.py +++ b/events/models.py @@ -14,6 +14,9 @@ class EventRegistration(models.Model): ('A', 'Admitted'), ('D', 'Denied'), ) - event_id = models.ForeignKey(Event, on_delete=models.CASCADE) - user_id = models.ForeignKey(CustomUser, on_delete=models.CASCADE) + event = models.ForeignKey(Event, on_delete=models.CASCADE) + user = models.ForeignKey(CustomUser, on_delete=models.CASCADE) state = models.CharField(max_length=1, choices=REGISTRATION_STATE) + + def __str__(self): + return f'Reservation[{self.user.username}:{self.event.date}:{self.state}]' diff --git a/events/tasks.py b/events/tasks.py new file mode 100644 index 0000000..a49a2cb --- /dev/null +++ b/events/tasks.py @@ -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() diff --git a/events/urls.py b/events/urls.py index 88a9cac..16cdbc2 100644 --- a/events/urls.py +++ b/events/urls.py @@ -4,4 +4,5 @@ from . import views urlpatterns = [ path('', views.index, name='index'), + path('test/', views.view_score_stuff, name='score_stuff'), ] diff --git a/events/views.py b/events/views.py index 769a8a0..f351714 100644 --- a/events/views.py +++ b/events/views.py @@ -3,6 +3,15 @@ from django.shortcuts import render # Create your views here. from django.http import HttpResponse +from events.tasks import assign_reservations + def index(request): - return HttpResponse("Hello, world. You're at the polls index.") + return HttpResponse( + + ) + +def view_score_stuff(request): + return HttpResponse( + assign_reservations() + ) diff --git a/requirements.txt b/requirements.txt index 1fc8adc..4036681 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,13 @@ +amqp==2.6.0 asgiref==3.2.10 +billiard==3.6.3.0 +celery==4.4.6 Django==3.0.8 +future==0.18.2 +kombu==4.6.11 mysqlclient==2.0.1 pytz==2020.1 PyYAML==5.3.1 +redis==3.5.3 sqlparse==0.3.1 +vine==1.3.0