Merge branch 'master' of ssh://git.zeus.gent:2222/bestuur/kers
This commit is contained in:
commit
704dfee977
11 changed files with 160 additions and 5 deletions
|
@ -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',)
|
27
KeRS/celery.py
Normal file
27
KeRS/celery.py
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from celery import Celery
|
||||||
|
|
||||||
|
# set the default Django settings module for the 'celery' program.
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'KeRS.settings')
|
||||||
|
|
||||||
|
app = Celery('KeRS',
|
||||||
|
broker='redis://localhost:6379/0',
|
||||||
|
backend='redis://localhost:6379/1'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Using a string here means the worker doesn't have to serialize
|
||||||
|
# the configuration object to child processes.
|
||||||
|
# - namespace='CELERY' means all celery-related configuration keys
|
||||||
|
# should have a `CELERY_` prefix.
|
||||||
|
app.config_from_object('django.conf:settings', namespace='CELERY')
|
||||||
|
|
||||||
|
# Load task modules from all registered Django app configs.
|
||||||
|
app.autodiscover_tasks()
|
||||||
|
|
||||||
|
|
||||||
|
@app.task(bind=True)
|
||||||
|
def debug_task(self):
|
||||||
|
print('Request: {0!r}'.format(self.request))
|
|
@ -41,6 +41,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
6
Makefile
Normal 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
|
|
@ -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)),
|
||||||
],
|
],
|
||||||
|
|
23
events/migrations/0002_auto_20200722_0101.py
Normal file
23
events/migrations/0002_auto_20200722_0101.py
Normal 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',
|
||||||
|
),
|
||||||
|
]
|
|
@ -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
71
events/tasks.py
Normal 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()
|
|
@ -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'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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()
|
||||||
|
)
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue