Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ranking feature to userinfo #256

Merged
merged 9 commits into from
Dec 13, 2021
17 changes: 17 additions & 0 deletions stregsystem/forms.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import datetime

from django import forms

from stregsystem.models import MobilePayment, Member
Expand Down Expand Up @@ -31,3 +33,18 @@ class QRPaymentForm(forms.Form):

class PurchaseForm(forms.Form):
product_id = forms.IntegerField()


class RankingDateForm(forms.Form):
from_date = forms.DateField(widget=forms.SelectDateWidget(years=range(2000, datetime.date.today().year + 1)))
to_date = forms.DateField(
initial=datetime.date.today(), widget=forms.SelectDateWidget(years=range(2000, datetime.date.today().year + 1))
)

# validate form. make sure that from_date is before to_date
def clean(self):
cleaned_data = super().clean()
if cleaned_data['from_date'] > cleaned_data['to_date']:
# raise forms.ValidationError('Fra dato skal være før eller lig til dato')
self.add_error('to_date', 'Fra dato skal være før eller lig til dato')
return cleaned_data
3 changes: 3 additions & 0 deletions stregsystem/templates/stregsystem/menu.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
<th>
<a href="/{{room.id}}/user/{{member.id}}/pay">Indsæt penge</a>
</th>
<th>
<a href="/{{room.id}}/user/{{member.id}}/rank">Rangliste</a>
</th>
{% comment %}
<th>
<a href="/{{room.id}}/user/{{member.id}}/undo">Fortryd køb</a>
Expand Down
54 changes: 54 additions & 0 deletions stregsystem/templates/stregsystem/menu_userrank.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
{% extends "stregsystem/base.html" %}

{% block title %}Treoens stregsystem : Rangliste {% endblock %}

{% block content %}

<style>
.ranking {
border: 1px solid black;
border-collapse: collapse;
padding: 5px;
}
</style>

<center>
<h3>{{ member.firstname }} {{ member.lastname }} ({{ member.email }}) </h3>
<i>Dit første køb var: {{ member_first_purchase }}</i><br><br>
<form method="post"> {% csrf_token %}
Fra: {{ form.from_date }}<br>
Til: {{ form.to_date }}<br>
<input type="submit" name="custom-range" value="Vis ranking i interval">
</form>
{% if form.errors %}
<h3 style="color: red">{{ form.errors }}</h3>
{% endif %}
<h3>Din rangliste fra {{ from_date | date:'d-m-Y' }} til {{ to_date | date:'d-m-Y' }}:</h3>
<table class="ranking">
<tr class="ranking">
<th></th>
{% for header in rankings.keys %}
<th class="ranking">{{ header }}</th>
{% endfor %}
</tr>
<tr class="ranking">
<td>rank/total</td>
{% for _, rank in rankings.items %}
<td class="ranking">{{ rank.0.0 }}/{{ rank.0.1 }}</td>
{% endfor %}
</tr>
<tr class="ranking">
<td>enheder/hverdag</td>
{% for _, rank in rankings.items %}
<td class="ranking">{{ rank.1 }}</td>
{% endfor %}
</tr>
</table>
<br>
<i>Rank/total rækken viser din nuværende placering ud af det totale antal Fembers der har købt varer i den
givne kategori, i den angivne periode.</i><br>
<i>En hverdag er defineret som antallet af mandag til fredage mellem studiestart og project deadline i 2021:<br>
<code>((to_time - from_time).days * 162.14 / 365)</code></i>
</center>
{% endblock %}

1 change: 1 addition & 0 deletions stregsystem/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@
re_path(r'^(?P<room_id>\d+)/sale/\d+/\d+/$', lambda request, room_id: redirect('menu_index', room_id=room_id), name="menu_sale"),
re_path(r'^(?P<room_id>\d+)/user/(?P<member_id>\d+)/$', views.menu_userinfo, name="userinfo"),
re_path(r'^(?P<room_id>\d+)/user/(?P<member_id>\d+)/pay$', views.menu_userpay, name="userpay"),
re_path(r'^(?P<room_id>\d+)/user/(?P<member_id>\d+)/rank$', views.menu_userrank, name="userrank"),
re_path(r'^api/member/payment/qr$', views.qr_payment, name="payment_qr"),
]
78 changes: 76 additions & 2 deletions stregsystem/views.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import datetime
from typing import List

import pytz
from pytz import UTC

from stregreport.views import fjule_party

from django.core import management
from django.forms import modelformset_factory, formset_factory

from django.contrib.admin.views.decorators import staff_member_required
from django.contrib.auth.decorators import permission_required
from django.conf import settings
from django.db.models import Q
from django.db.models import Q, Count
from django import forms
from django.http import HttpResponsePermanentRedirect, HttpResponseBadRequest
from django.shortcuts import get_object_or_404, render
Expand All @@ -27,6 +32,7 @@
Sale,
StregForbudError,
MobilePayment,
Category,
)
from stregsystem.utils import (
make_active_productlist_query,
Expand All @@ -39,7 +45,7 @@

from .booze import ballmer_peak
from .caffeine import caffeine_mg_to_coffee_cups
from .forms import MobilePayToolForm, QRPaymentForm, PurchaseForm
from .forms import MobilePayToolForm, QRPaymentForm, PurchaseForm, RankingDateForm


def __get_news():
Expand Down Expand Up @@ -229,6 +235,74 @@ def menu_userpay(request, room_id, member_id):
return render(request, 'stregsystem/menu_userpay.html', locals())


def menu_userrank(request, room_id, member_id):
from_date = fjule_party(datetime.datetime.today().year - 1)
to_date = datetime.datetime.now(tz=pytz.timezone("Europe/Copenhagen"))
room = Room.objects.get(pk=room_id)
member = Member.objects.get(pk=member_id, active=True)

def ranking(category_ids, from_d, to_d):
qs = (
Member.objects.filter(sale__product__in=category_ids, sale__timestamp__gt=from_d, sale__timestamp__lte=to_d)
.annotate(Count('sale'))
.order_by('-sale__count', 'username')
)
if member not in qs:
return 0, qs.count()
return list(qs).index(Member.objects.get(id=member.id)) + 1, int(qs.count())

def get_product_ids_for_category(category) -> list:
return list(
Product.objects.filter(categories__exact=Category.objects.get(name__exact=category)).values_list(
'id', flat=True
)
)

def category_per_uni_day(category_ids, from_d, to_d):
qs = Member.objects.filter(
id=member.id,
sale__product__in=category_ids,
sale__timestamp__gt=from_d,
sale__timestamp__lte=to_d,
)
if member not in qs:
return 0
else:
return "{:.2f}".format(qs.count() / ((to_d - from_d).days * 162.14 / 365)) # university workdays in 2021

# let user know when they first purchased a product
member_first_purchase = "Ikke endnu, køb en limfjordsporter!"
first_purchase = Sale.objects.filter(member=member_id).order_by('-timestamp')
if first_purchase.exists():
member_first_purchase = first_purchase.last().timestamp

form = RankingDateForm()
if request.method == "POST" and request.POST['custom-range']:
form = RankingDateForm(request.POST)
if form.is_valid():
from_date = form.cleaned_data['from_date']
to_date = form.cleaned_data['to_date']
else:
# setup initial dates for form and results
form = RankingDateForm(initial={'from_date': from_date, 'to_date': to_date})

# get prod_ids for each category as dict {cat: [key1, key2])}, then flatten list of singleton
# dicts into one dict, lastly calculate member_id rating and units/weekday for category_ids
rankings = {
key: (
ranking(category_ids, from_date, to_date),
category_per_uni_day(category_ids, from_date, to_date),
)
for key, category_ids in {
k: v
for x in map(lambda x: {x: get_product_ids_for_category(x)}, list(Category.objects.all()))
for k, v in x.items()
}.items()
}

return render(request, 'stregsystem/menu_userrank.html', locals())


def menu_sale(request, room_id, member_id, product_id=None):
room = Room.objects.get(pk=room_id)
news = __get_news()
Expand Down