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

Razzia overhaul #392

Closed
wants to merge 11 commits into from
24 changes: 20 additions & 4 deletions stregreport/models.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
from django.db import models

from stregsystem.models import Member
from stregsystem.models import Member, Product


class BreadRazzia(models.Model):
class Meta:
permissions = (("host_razzia", "Can host a foobar, fnugfald or bread razzia"),)
permissions = (("host_razzia", "Can host a foobar or bread razzia"),)

BREAD = 'BR'
FOOBAR = 'FB'
FNUGFALD = 'FF'
RAZZIA_CHOICES = [(BREAD, "Brødrazzia"), (FOOBAR, "Foobar razzia"), (FNUGFALD, "Fnugfald razzia")]
RAZZIA_CHOICES = [(BREAD, "Brødrazzia"), (FOOBAR, "Foobar razzia")]
members = models.ManyToManyField(Member, through='RazziaEntry')
start_date = models.DateTimeField(auto_now_add=True)
razzia_type = models.CharField(max_length=2, choices=RAZZIA_CHOICES, default=BREAD)
Expand All @@ -20,3 +19,20 @@ class RazziaEntry(models.Model):
member = models.ForeignKey(Member, on_delete=models.CASCADE)
razzia = models.ForeignKey(BreadRazzia, on_delete=models.CASCADE)
time = models.DateTimeField(null=True, blank=True, auto_now_add=True)


class WizardRazzia(models.Model):
razzia_name = models.CharField(max_length=30)
time = models.DateTimeField(null=True, blank=True, auto_now_add=True)
members = models.ManyToManyField(Member, through='WizardEntry')
# TODO: Is a many-to-many field overkill here? We probably won't need to lookup razzia by product.
products = models.ManyToManyField(Product, blank=True)
start_date = models.DateTimeField(null=True, blank=True)
end_date = models.DateTimeField(null=True, blank=True)
refill_interval = models.DurationField(null=True, blank=True)


class WizardEntry(models.Model):
member = models.ForeignKey(Member, on_delete=models.CASCADE)
razzia = models.ForeignKey(WizardRazzia, on_delete=models.CASCADE)
time = models.DateTimeField(null=True, blank=True, auto_now_add=True)
8 changes: 4 additions & 4 deletions stregreport/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,18 @@
urlpatterns = [
re_path(r'^admin/stregsystem/razzia/bread/(?P<razzia_id>\d+)/$', views.razzia, {'razzia_type' : BreadRazzia.BREAD, 'title': 'Brødrazzia'}, name="bread_view"),
re_path(r'^admin/stregsystem/razzia/foobar/(?P<razzia_id>\d+)/$', views.razzia, {'razzia_type' : BreadRazzia.FOOBAR, 'title': 'Foobar razzia'}, name="foobar_view"),
re_path(r'^admin/stregsystem/razzia/fnugfald/(?P<razzia_id>\d+)/$', views.razzia, {'razzia_type' : BreadRazzia.FNUGFALD, 'title': 'Fnugfald razzia'}, name="fnugfald_view"),
re_path(r'^admin/stregsystem/razzia/wizardv2/(?P<razzia_id>\d+)/$', views.wizardv2_razzia, name="wizard_view"),
re_path(r'^admin/stregsystem/razzia/bread/(?P<razzia_id>\d+)/members$', views.razzia_members, {'razzia_type' : BreadRazzia.BREAD, 'title': 'Brødrazzia'}),
re_path(r'^admin/stregsystem/razzia/foobar/(?P<razzia_id>\d+)/members$', views.razzia_members, {'razzia_type' : BreadRazzia.FOOBAR, 'title': 'Foobar razzia'}),
re_path(r'^admin/stregsystem/razzia/fnugfald/(?P<razzia_id>\d+)/members$', views.razzia_members, {'razzia_type' : BreadRazzia.FNUGFALD, 'title': 'Fnugfald razzia'}),
re_path(r'^admin/stregsystem/razzia/bread/$', views.razzia_menu, {'razzia_type' : BreadRazzia.BREAD, 'new_text': "New bread razzia", 'title': 'Brødrazzia'}),
re_path(r'^admin/stregsystem/razzia/foobar/$', views.razzia_menu, {'razzia_type' : BreadRazzia.FOOBAR, 'new_text': "New foobar razzia", 'title': 'Foobar razzia'}),
re_path(r'^admin/stregsystem/razzia/fnugfald/$', views.razzia_menu, {'razzia_type' : BreadRazzia.FNUGFALD, 'new_text': "New fnugfald razzia", 'title': 'Fnugfald razzia'}),
re_path(r'^admin/stregsystem/razzia/bread/new$', views.new_razzia, {'razzia_type' : BreadRazzia.BREAD}, name="razzia_new_BR"),
re_path(r'^admin/stregsystem/razzia/foobar/new$', views.new_razzia, {'razzia_type' : BreadRazzia.FOOBAR}, name="razzia_new_FB"),
re_path(r'^admin/stregsystem/razzia/fnugfald/new$', views.new_razzia, {'razzia_type' : BreadRazzia.FNUGFALD}, name="razzia_new_FF"),
re_path(r'^admin/stregsystem/razzia/wizardv2/new$', views.wizardv2_new_razzia, name="razzia_new"),
re_path(r'^admin/stregsystem/razzia/wizard_guide/$', views.razzia_wizard),
re_path(r'^admin/stregsystem/razzia/wizardv2_guide/$', views.razzia_wizardv2),
re_path(r'^admin/stregsystem/razzia/wizard/$', views.razzia_view, name="razzia_view"),
re_path(r'^admin/stregsystem/razzia/wizardv2/$', views.wizardv2_razzia_view, name="razzia_view"),
re_path(r'^admin/stregsystem/report/sales/$', views.sales, name="salesreporting"),
re_path(r'^admin/stregsystem/report/ranks/$', views.ranks),
re_path(r'^admin/stregsystem/report/daily/$', views.daily),
Expand Down
233 changes: 172 additions & 61 deletions stregreport/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@
from django.db.models import Count, Q, Sum
from django.db.models.functions import TruncDay
from django.forms import fields
from django.forms.widgets import SelectDateWidget
from django.forms.widgets import SelectDateWidget, TextInput, NumberInput
from django.http import JsonResponse
from django.shortcuts import redirect, render, get_object_or_404
from django.urls import reverse
from django.utils import dateparse, timezone
from stregreport.forms import CategoryReportForm
from stregsystem.models import Category, Member, Product, Sale
from stregreport.models import BreadRazzia, RazziaEntry
from stregreport.models import BreadRazzia, RazziaEntry, WizardRazzia, WizardEntry
from stregsystem.templatetags.stregsystem_extras import money


Expand Down Expand Up @@ -50,81 +50,80 @@ def razzia(request, razzia_id, razzia_type=BreadRazzia.BREAD, title=None):
return razzia_view_single(request, razzia_id, None, razzia_type=razzia_type, title=title)


def _sales_to_user_in_period(username, start_date, end_date, product_list, product_dict):
result = (
Product.objects.filter(
sale__member__username__iexact=username,
id__in=product_list,
sale__timestamp__gte=start_date,
sale__timestamp__lte=end_date,
)
.annotate(cnt=Count("id"))
.values_list("name", "cnt")
)

products_bought = {product: count for product, count in result}

return {product: products_bought.get(product, 0) for product in product_dict}
@permission_required("stregreport.host_razzia")
def wizardv2_razzia(request, razzia_id):
if request.method == 'POST':
return wizardv2_razzia_view_single(request, razzia_id, request.POST['username'])
else:
return wizardv2_razzia_view_single(request, razzia_id, None)


@permission_required("stregreport.host_razzia")
def razzia_view_single(request, razzia_id, queryname, razzia_type=BreadRazzia.BREAD, title=None):
razzia = get_object_or_404(BreadRazzia, pk=razzia_id, razzia_type=razzia_type)
if queryname is not None:
result = list(Member.objects.filter(username__iexact=queryname))
if len(result) > 0:
member = result[0]
entries = list(razzia.razziaentry_set.filter(member__pk=member.pk).order_by('-time'))
already_checked_in = len(entries) > 0
wait_time = datetime.timedelta(minutes=30)
if already_checked_in:
last_entry = entries[0]
within_wait = last_entry.time > timezone.now() - wait_time
# if member has already checked in within the last hour, don't allow another check in
if already_checked_in and within_wait and razzia_type == BreadRazzia.FOOBAR:
drunkard = True
# time until next check in is legal
remaining_time_secs = int(((last_entry.time + wait_time) - timezone.now()).total_seconds() % 60)
remaining_time_mins = int(((last_entry.time + wait_time) - timezone.now()).total_seconds() // 60)
if not already_checked_in or (razzia_type == BreadRazzia.FOOBAR and not within_wait):
RazziaEntry(member=member, razzia=razzia).save()

templates = {
BreadRazzia.BREAD: 'admin/stregsystem/razzia/bread.html',
BreadRazzia.FOOBAR: 'admin/stregsystem/razzia/foobar.html',
BreadRazzia.FNUGFALD: 'admin/stregsystem/razzia/fnugfald.html',
}
return render(request, templates[razzia_type], locals())


@permission_required("stregreport.host_razzia")
def wizardv2_razzia_view_single(request, razzia_id, queryname):
razzia = get_object_or_404(WizardRazzia, pk=razzia_id)

# General page opened, no specific user
if queryname is None:
return render(request, templates[razzia_type], locals())
return render(request, "admin/stregsystem/razzia/wizardv2.html", locals())

result = list(Member.objects.filter(username__iexact=queryname))
if len(result) == 0:
return render(request, templates[razzia_type], locals())

member = result[0]

if razzia_type == BreadRazzia.FNUGFALD:
username = queryname
member_name = member.firstname + " " + member.lastname
start_date = dateparse.parse_date("2023-9-15")
end_date = dateparse.parse_date("2023-11-4")
product_list = [1910]
product_dict = {k.name: 0 for k in Product.objects.filter(id__in=product_list)}
sales_to_user = _sales_to_user_in_period(queryname, start_date, end_date, product_list, product_dict)
items_bought = sales_to_user.items()
# No member found
if len(result) <= 0:
return render(request, "admin/stregsystem/razzia/wizardv2.html", locals())

try:
item_bought_count = sales_to_user[list(sales_to_user.keys())[0]]
if item_bought_count == 0:
return render(request, templates[razzia_type], locals())
except IndexError:
return render(request, templates[razzia_type], locals())

entries = list(razzia.razziaentry_set.filter(member__pk=member.pk).order_by('-time'))
already_checked_in = len(entries) > 0
wait_time = datetime.timedelta(minutes=30)
if already_checked_in:
last_entry = entries[0]
within_wait = last_entry.time > timezone.now() - wait_time
# if member has already checked in within the last hour, don't allow another check in
if (
already_checked_in
and within_wait
and (razzia_type == BreadRazzia.FOOBAR or razzia_type == BreadRazzia.FNUGFALD)
):
drunkard = True
# time until next check in is legal
remaining_time_secs = int(((last_entry.time + wait_time) - timezone.now()).total_seconds() % 60)
remaining_time_mins = int(((last_entry.time + wait_time) - timezone.now()).total_seconds() // 60)
if not already_checked_in or (
(razzia_type == BreadRazzia.FOOBAR or razzia_type == BreadRazzia.FNUGFALD) and not within_wait
):
RazziaEntry(member=member, razzia=razzia).save()
member = result[0]
refill_enabled = razzia.refill_interval is not None

if refill_enabled:
entries = list(razzia.razziaentry_set.filter(member__pk=member.pk).order_by('-time'))
wait_time = razzia.refill_interval
already_checked_in = len(entries) > 0

if already_checked_in:
last_entry = entries[0]
too_soon = last_entry.time > timezone.now() - wait_time

if too_soon:
drunkard = True
# time until next check in is legal
remaining_time_secs = int(((last_entry.time + wait_time) - timezone.now()).total_seconds() % 60)
remaining_time_mins = int(((last_entry.time + wait_time) - timezone.now()).total_seconds() // 60)
else:
WizardEntry(member=member, razzia=razzia).save()
else:
WizardEntry(member=member, razzia=razzia).save()

return render(request, templates[razzia_type], locals())
return render(request, "admin/stregsystem/razzia/wizardv2.html", locals())


@permission_required("stregreport.host_razzia")
Expand All @@ -140,11 +139,19 @@ def new_razzia(request, razzia_type=BreadRazzia.BREAD):
razzia = BreadRazzia(razzia_type=razzia_type)
razzia.save()

views = {BreadRazzia.BREAD: 'bread_view', BreadRazzia.FOOBAR: 'foobar_view', BreadRazzia.FNUGFALD: 'fnugfald_view'}
views = {BreadRazzia.BREAD: 'bread_view', BreadRazzia.FOOBAR: 'foobar_view'}

return redirect(views[razzia_type], razzia_id=razzia.pk)


def wizardv2_new_razzia(request):
# TODO: Make proper wizard razzia with parameters.
razzia = WizardRazzia()
razzia.save()

return redirect("wizard_view", razzia_id=razzia.pk)


@permission_required("stregreport.host_razzia")
def razzia_members(request, razzia_id, razzia_type=BreadRazzia.BREAD, title=None):
razzia = get_object_or_404(BreadRazzia, pk=razzia_id, razzia_type=razzia_type)
Expand All @@ -157,6 +164,23 @@ def razzia_members(request, razzia_id, razzia_type=BreadRazzia.BREAD, title=None
razzia_members = staff_member_required(razzia_members)


def _sales_to_user_in_period(username, start_date, end_date, product_list, product_dict):
result = (
Product.objects.filter(
sale__member__username__iexact=username,
id__in=product_list,
sale__timestamp__gte=start_date,
sale__timestamp__lte=end_date,
)
.annotate(cnt=Count("id"))
.values_list("name", "cnt")
)

products_bought = {product: count for product, count in result}

return {product: products_bought.get(product, 0) for product in product_dict}


@permission_required("stregreport.host_razzia")
def razzia_view(request):
default_start = timezone.now().today() - datetime.timedelta(days=-180)
Expand Down Expand Up @@ -207,6 +231,53 @@ def razzia_view(request):
razzia_view = staff_member_required(razzia_view)


@permission_required("stregreport.host_razzia")
def wizardv2_razzia_view(request):
default_start = timezone.now().today() - datetime.timedelta(days=-180)
default_end = timezone.now().today()
start = request.GET.get('start', default_start.isoformat())
end = request.GET.get('end', default_end.isoformat())
products = request.GET.get('products', "")
username = request.GET.get('username', "")
title = request.GET.get('razzia_title', "Razzia!")

try:
product_list = [int(p) for p in products.split(",")]
except ValueError:
return render(request, 'admin/stregsystem/razzia/error_wizardv2error.html', {})

product_dict = {k.name: 0 for k in Product.objects.filter(id__in=product_list)}
if len(product_list) != len(product_dict.items()):
return render(request, 'admin/stregsystem/razzia/error_wizardv2error.html', {})

try:
user = Member.objects.get(username__iexact=username)
except (Member.DoesNotExist, Member.MultipleObjectsReturned):
return render(
request,
'admin/stregsystem/razzia/wizard_view.html',
{'start': start, 'end': end, 'products': products, 'username': username, 'razzia_title': title},
)

start_date = dateparse.parse_date(start)
end_date = dateparse.parse_date(end)
sales_to_user = _sales_to_user_in_period(username, start_date, end_date, product_list, product_dict)

return render(
request,
'admin/stregsystem/razzia/wizard_view.html',
{
'razzia_title': title,
'username': username,
'start': start,
'end': end,
'products': products,
'member_name': user.firstname + " " + user.lastname,
'items_bought': sales_to_user.items(),
},
)


@permission_required("stregreport.host_razzia")
def razzia_wizard(request):
if request.method == 'POST':
Expand Down Expand Up @@ -245,6 +316,46 @@ def razzia_wizard(request):
razzia_wizard = staff_member_required(razzia_wizard)


@permission_required("stregreport.host_razzia")
def razzia_wizardv2(request):
if request.method == 'POST':
return redirect(
reverse("razzia_view")
+ "?start={0}-{1}-{2}&end={3}-{4}-{5}&products={6}&username=&razzia_title={7}".format(
int(request.POST['start_year']),
int(request.POST['start_month']),
int(request.POST['start_day']),
int(request.POST['end_year']),
int(request.POST['end_month']),
int(request.POST['end_day']),
request.POST.get('products'),
request.POST.get('razzia_title'),
)
)

suggested_start_date = timezone.now() - datetime.timedelta(days=-180)
suggested_end_date = timezone.now()

start_date_picker = fields.DateField(
widget=SelectDateWidget(years=[x for x in range(2000, timezone.now().year + 1)])
)
end_date_picker = fields.DateField(widget=SelectDateWidget(years=[x for x in range(2000, timezone.now().year + 1)]))
refill_interval_picker = fields.IntegerField(widget=NumberInput())

razzia_name_picker = fields.CharField(widget=TextInput(), max_length=30)

return render(
request,
'admin/stregsystem/razzia/wizard.html',
{
'start_date_picker': start_date_picker.widget.render("start", suggested_start_date),
'end_date_picker': end_date_picker.widget.render("end", suggested_end_date),
'razzia_name_picker': razzia_name_picker.widget.render("razzia_name", "Ratsia"),
'refill_interval_picker': refill_interval_picker.widget.render("refill_interval", 30),
},
)


def ranks(request, year=None):
if year:
return ranks_for_year(request, int(year))
Expand Down
5 changes: 0 additions & 5 deletions stregsystem/templates/admin/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,6 @@
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<th scope="row"><a href = "/admin/stregsystem/razzia/fnugfald/">Fnugfald razzia</a></th>
<td>&nbsp;</td>
<td>&nbsp;</td>
</tr>
<tr>
<th scope="row"><a href = "/admin/stregsystem/razzia/wizard_guide/">Razzia wizard!</a></th>
<td>&nbsp;</td>
Expand Down
8 changes: 3 additions & 5 deletions stregsystem/templates/admin/stregsystem/razzia/bread.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@
{% block breadcrumbs %}<div class="breadcrumbs"><a href="../../../">Hjem</a>&nbsp;&rsaquo;&nbsp;<a href="../../../">Stregsystem</a>&nbsp;&rsaquo;&nbsp;<a href="../">Brødrazzia</a></div>{% endblock %}

{% block member_present %}
<div class="result">
<div class="status">
<div class="fa {% if already_checked_in %} fa-minus-circle failure {% else %} fa-check-circle sucess {% endif %}" aria-hidden="true"></div>
</div>
{{member.firstname}} {{member.lastname}} ({{member.username}}) {% if already_checked_in %} already checked in at {{ last_entry.time }} {% endif %}
<div class="status">
<div class="fa {% if already_checked_in %} fa-minus-circle failure {% else %} fa-check-circle sucess {% endif %}" aria-hidden="true"></div>
</div>
{{member.firstname}} {{member.lastname}} ({{member.username}}) {% if already_checked_in %} already checked in at {{ last_entry.time }} {% endif %}
{% endblock %}
Loading
Loading