From c1d8f32341de377019841708db291c7e66e9598f Mon Sep 17 00:00:00 2001 From: Kresten Laust Date: Sun, 3 Sep 2023 00:47:39 +0200 Subject: [PATCH 01/61] Added mockup heatmap to user-page. --- stregsystem/templates/stregsystem/menu.html | 6 + .../stregsystem/purchase_heatmap.html | 155 ++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 stregsystem/templates/stregsystem/purchase_heatmap.html diff --git a/stregsystem/templates/stregsystem/menu.html b/stregsystem/templates/stregsystem/menu.html index b4110d99..69875a11 100644 --- a/stregsystem/templates/stregsystem/menu.html +++ b/stregsystem/templates/stregsystem/menu.html @@ -139,6 +139,12 @@ {% endblock %}
+
+
+ {% include "stregsystem/purchase_heatmap.html" %} +
+
+
{% if give_multibuy_hint %}

psssst. multibuy er enabled.

diff --git a/stregsystem/templates/stregsystem/purchase_heatmap.html b/stregsystem/templates/stregsystem/purchase_heatmap.html new file mode 100644 index 00000000..2788fbcd --- /dev/null +++ b/stregsystem/templates/stregsystem/purchase_heatmap.html @@ -0,0 +1,155 @@ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Uge nr.2829303132333435
Mandag
Onsdag
Fredag
Søndag
+
+ \ No newline at end of file From 908a952d24c4ca83255d04e4cc12b508b41d1c20 Mon Sep 17 00:00:00 2001 From: Kresten Laust Date: Sun, 3 Sep 2023 13:36:14 +0200 Subject: [PATCH 02/61] Updated mockup to better reflect target html. --- .../stregsystem/purchase_heatmap.html | 140 ++++++++---------- 1 file changed, 61 insertions(+), 79 deletions(-) diff --git a/stregsystem/templates/stregsystem/purchase_heatmap.html b/stregsystem/templates/stregsystem/purchase_heatmap.html index 2788fbcd..ac9a1aef 100644 --- a/stregsystem/templates/stregsystem/purchase_heatmap.html +++ b/stregsystem/templates/stregsystem/purchase_heatmap.html @@ -14,82 +14,82 @@ + + + + + + + + + + + Mandag - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + Onsdag - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + Fredag - - - - - - - - + + + + + + + + - - - - - - - - - - - Søndag - - - - - - - - + + + + + + + + @@ -128,28 +128,10 @@ shape-rendering: geometricPrecision; padding: 0; outline: 1px solid rgba(27, 31, 35, 0.06); + fill: var(--cell-color); + background-color: var(--cell-color); } .heatmap-cell:hover { border-radius: 5px; } - .heatmap-cell[data-level="0"] { - fill: #ebedf0; - background-color: #ebedf0; - } - .heatmap-cell[data-level="1"] { - fill: #9be9a8; - background-color: #9be9a8; - } - .heatmap-cell[data-level="2"] { - fill: #40c463; - background-color: #40c463; - } - .heatmap-cell[data-level="3"] { - fill: #30a14e; - background-color: #30a14e; - } - .heatmap-cell[data-level="4"] { - fill: #216e39; - background-color: #216e39; - } \ No newline at end of file From f16239bb4018a6c1b363d82e5ecf2197e03e6511 Mon Sep 17 00:00:00 2001 From: Kresten Laust Date: Sun, 3 Sep 2023 14:54:20 +0200 Subject: [PATCH 03/61] Added empty functions for acquiring and transforming heatmap data. --- stregsystem/views.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/stregsystem/views.py b/stregsystem/views.py index dfd97d66..a5cebaa9 100644 --- a/stregsystem/views.py +++ b/stregsystem/views.py @@ -63,6 +63,27 @@ def __get_productlist(room_id): return make_active_productlist_query(Product.objects).filter(make_room_specific_query(room_id)) +def __get_purchase_heatmap_cell_color(purchase_ids: list, category_id_color: (int, int, int,)) -> (int, int, int,): + pass + + +def __organize_purchase_heatmap_data(heatmap_data: list, today: datetime.date) -> list: + # Transforms [<>, <>, ...] + # into + # [ + # [<>, <>, ...], + # [<>, <>, ...], + # ...] + return [] + + +def __get_purchase_heatmap_data(member: Member) -> list: + # Proposed format: + # Returned list: [<>, <>, ...] + # Day item: (<>, [<>, ...]) + return [((0, 255, 0), [1, 2, 3]), ((0, 200, 0), [1, 2]), ((0, 100, 0), [2])] * 23 # ~70 entries + + def roomindex(request): return HttpResponsePermanentRedirect('/1/') @@ -205,6 +226,8 @@ def usermenu(request, room, member, bought, from_sale=False): give_multibuy_hint, sale_hints = _multibuy_hint(timezone.now(), member) give_multibuy_hint = give_multibuy_hint and from_sale + heatmap_data = __organize_purchase_heatmap_data(__get_purchase_heatmap_data(member), datetime.date.today()) + if member.has_stregforbud(): return render(request, 'stregsystem/error_stregforbud.html', locals()) else: From 9f3856d380de0b0b51cf032d554166367b8580b8 Mon Sep 17 00:00:00 2001 From: Kresten Laust Date: Sun, 3 Sep 2023 15:38:47 +0200 Subject: [PATCH 04/61] Proper generation of mockup data in method. Partially finished transformation of data. --- stregsystem/views.py | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/stregsystem/views.py b/stregsystem/views.py index a5cebaa9..aa61527e 100644 --- a/stregsystem/views.py +++ b/stregsystem/views.py @@ -74,14 +74,42 @@ def __organize_purchase_heatmap_data(heatmap_data: list, today: datetime.date) - # [<>, <>, ...], # [<>, <>, ...], # ...] - return [] + + # TODO: Doesn't take current weekday into consideration. + new_list = [] + for i in range(7): + new_list.append([]) + for j in range(len(heatmap_data)): + if j % 7 == i: + new_list[i].append(heatmap_data[j]) + + return new_list def __get_purchase_heatmap_data(member: Member) -> list: # Proposed format: # Returned list: [<>, <>, ...] - # Day item: (<>, [<>, ...]) - return [((0, 255, 0), [1, 2, 3]), ((0, 200, 0), [1, 2]), ((0, 100, 0), [2])] * 23 # ~70 entries + # Day item: (<>, <>, [<>, ...]) + + mockup_list = [] + for i in range(70): + mockup_list.append((datetime.datetime.today() - datetime.timedelta(days=i), (0, 200, 0), [1, 2])) + + return mockup_list + """ + return [ + (datetime.datetime.today(), (0, 255, 0), [1, 2, 3]), + (datetime.datetime.today() - datetime.timedelta(days=1), (0, 200, 0), [1, 2]), + (datetime.datetime.today() - datetime.timedelta(days=2), (0, 100, 0), [2]), + (datetime.datetime.today() - datetime.timedelta(days=3), (0, 100, 0), [2]), + (datetime.datetime.today() - datetime.timedelta(days=4), (0, 100, 0), [2]), + (datetime.datetime.today() - datetime.timedelta(days=5), (0, 50, 0), [2]), + (datetime.datetime.today() - datetime.timedelta(days=6), (0, 50, 0), [2]), + (datetime.datetime.today() - datetime.timedelta(days=7), (0, 100, 0), [2]), + (datetime.datetime.today() - datetime.timedelta(days=8), (0, 100, 0), [2]), + (datetime.datetime.today() - datetime.timedelta(days=9), (0, 100, 0), [2]), + (datetime.datetime.today() - datetime.timedelta(days=10), (0, 100, 0), [2]), + ]""" def roomindex(request): From da2d722e39b079a7be1fd11639cdf5f98d7cf13f Mon Sep 17 00:00:00 2001 From: Kresten Laust Date: Sun, 3 Sep 2023 17:21:46 +0200 Subject: [PATCH 05/61] Finished replacing mockup with HTML generated using django templates. --- .../stregsystem/purchase_heatmap.html | 92 ++----------------- stregsystem/views.py | 36 ++++++-- 2 files changed, 39 insertions(+), 89 deletions(-) diff --git a/stregsystem/templates/stregsystem/purchase_heatmap.html b/stregsystem/templates/stregsystem/purchase_heatmap.html index ac9a1aef..10d8634d 100644 --- a/stregsystem/templates/stregsystem/purchase_heatmap.html +++ b/stregsystem/templates/stregsystem/purchase_heatmap.html @@ -3,94 +3,20 @@ Uge nr. - 28 - 29 - 30 - 31 - 32 - 33 - 34 - 35 + {% for label in column_labels %} + {{label}} + {% endfor %} + {% for weekday_label, weekday_data in rows %} - - - - - - - - - - - - Mandag - - - - - - - - - - - - - - - - - - - - - - Onsdag - - - - - - - - - - - - - - - - - - - - - - Fredag - - - - - - - - - - - - - - - - - - - + {{weekday_label}} + {% for day_data in weekday_data %} + + {% endfor %} + {% endfor %} diff --git a/stregsystem/views.py b/stregsystem/views.py index aa61527e..fe500821 100644 --- a/stregsystem/views.py +++ b/stregsystem/views.py @@ -1,4 +1,5 @@ import datetime +import random from typing import List import pytz @@ -63,11 +64,18 @@ def __get_productlist(room_id): return make_active_productlist_query(Product.objects).filter(make_room_specific_query(room_id)) -def __get_purchase_heatmap_cell_color(purchase_ids: list, category_id_color: (int, int, int,)) -> (int, int, int,): +def __get_purchase_heatmap_cell_color( + purchase_ids: list, + category_id_color: ( + int, + int, + int, + ), +) -> (int, int, int,): pass -def __organize_purchase_heatmap_data(heatmap_data: list, today: datetime.date) -> list: +def __organize_purchase_heatmap_data(heatmap_data: list, start_date: datetime.date) -> list: # Transforms [<>, <>, ...] # into # [ @@ -86,14 +94,23 @@ def __organize_purchase_heatmap_data(heatmap_data: list, today: datetime.date) - return new_list -def __get_purchase_heatmap_data(member: Member) -> list: +def __get_purchase_heatmap_data(member: Member, start_date: datetime.date) -> list: # Proposed format: # Returned list: [<>, <>, ...] # Day item: (<>, <>, [<>, ...]) + mockup_colors = [(235, 237, 240), (155, 233, 168), (64, 196, 99), (33, 110, 57)] + mockup_list = [] - for i in range(70): - mockup_list.append((datetime.datetime.today() - datetime.timedelta(days=i), (0, 200, 0), [1, 2])) + for i in range(70 - start_date.weekday()): + mockup_item_count = random.randint(0, len(mockup_colors) - 1) + mockup_list.append( + ( + datetime.datetime.today() - datetime.timedelta(days=i), + mockup_colors[mockup_item_count], + [1] * mockup_item_count, + ) + ) return mockup_list """ @@ -254,7 +271,14 @@ def usermenu(request, room, member, bought, from_sale=False): give_multibuy_hint, sale_hints = _multibuy_hint(timezone.now(), member) give_multibuy_hint = give_multibuy_hint and from_sale - heatmap_data = __organize_purchase_heatmap_data(__get_purchase_heatmap_data(member), datetime.date.today()) + raw_heatmap_data = __get_purchase_heatmap_data(member, datetime.date.today()) + raw_heatmap_data.reverse() + + heatmap_data = __organize_purchase_heatmap_data(raw_heatmap_data, datetime.date.today()) + row_labels = ["", "Mandag", "", "Onsdag", "", "Fredag", ""] + column_labels = ["26", "27", "28", "29", "30", "31", "32", "33", "34", "35"] + + rows = zip(row_labels, heatmap_data) if member.has_stregforbud(): return render(request, 'stregsystem/error_stregforbud.html', locals()) From 6198fe6e279bcacae0ff9725cf9cf9719ca8399c Mon Sep 17 00:00:00 2001 From: Kresten Laust Date: Sun, 3 Sep 2023 22:24:14 +0200 Subject: [PATCH 06/61] Removed outer div, and removed commented code. --- stregsystem/templates/stregsystem/menu.html | 4 +--- stregsystem/views.py | 14 -------------- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/stregsystem/templates/stregsystem/menu.html b/stregsystem/templates/stregsystem/menu.html index 69875a11..33dca419 100644 --- a/stregsystem/templates/stregsystem/menu.html +++ b/stregsystem/templates/stregsystem/menu.html @@ -140,9 +140,7 @@

-
- {% include "stregsystem/purchase_heatmap.html" %} -
+ {% include "stregsystem/purchase_heatmap.html" %}

{% if give_multibuy_hint %} diff --git a/stregsystem/views.py b/stregsystem/views.py index f36b43f3..1ce80690 100644 --- a/stregsystem/views.py +++ b/stregsystem/views.py @@ -115,20 +115,6 @@ def __get_purchase_heatmap_data(member: Member, start_date: datetime.date) -> li ) return mockup_list - """ - return [ - (datetime.datetime.today(), (0, 255, 0), [1, 2, 3]), - (datetime.datetime.today() - datetime.timedelta(days=1), (0, 200, 0), [1, 2]), - (datetime.datetime.today() - datetime.timedelta(days=2), (0, 100, 0), [2]), - (datetime.datetime.today() - datetime.timedelta(days=3), (0, 100, 0), [2]), - (datetime.datetime.today() - datetime.timedelta(days=4), (0, 100, 0), [2]), - (datetime.datetime.today() - datetime.timedelta(days=5), (0, 50, 0), [2]), - (datetime.datetime.today() - datetime.timedelta(days=6), (0, 50, 0), [2]), - (datetime.datetime.today() - datetime.timedelta(days=7), (0, 100, 0), [2]), - (datetime.datetime.today() - datetime.timedelta(days=8), (0, 100, 0), [2]), - (datetime.datetime.today() - datetime.timedelta(days=9), (0, 100, 0), [2]), - (datetime.datetime.today() - datetime.timedelta(days=10), (0, 100, 0), [2]), - ]""" def roomindex(request): From 485ff2560673b5d95dac5ee3ae4bef228d72cf99 Mon Sep 17 00:00:00 2001 From: Kresten Laust Date: Mon, 4 Sep 2023 00:15:05 +0200 Subject: [PATCH 07/61] Finished logic for retrieving latest purchases and transforming it. --- stregsystem/views.py | 86 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 69 insertions(+), 17 deletions(-) diff --git a/stregsystem/views.py b/stregsystem/views.py index 1ce80690..3b4f0f3e 100644 --- a/stregsystem/views.py +++ b/stregsystem/views.py @@ -66,17 +66,6 @@ def __get_productlist(room_id): return make_active_productlist_query(Product.objects).filter(make_room_specific_query(room_id)) -def __get_purchase_heatmap_cell_color( - purchase_ids: list, - category_id_color: ( - int, - int, - int, - ), -) -> (int, int, int,): - pass - - def __organize_purchase_heatmap_data(heatmap_data: list, start_date: datetime.date) -> list: # Transforms [<>, <>, ...] # into @@ -96,7 +85,66 @@ def __organize_purchase_heatmap_data(heatmap_data: list, start_date: datetime.da return new_list -def __get_purchase_heatmap_data(member: Member, start_date: datetime.date) -> list: +def __get_purchase_heatmap_day_color( + products: List[Product], + category_name_color: ( + str, + str, + str, + ), +) -> (int, int, int,): + return (50, 50, 50) + + +def __get_purchase_heatmap_data(member: Member, + end_date: datetime.datetime, + weeks_to_display: int, + category_name_color: (str, str, str,) + ) -> list: + from datetime import datetime, timedelta + # Possibly off-by-one because visual starts on sunday, but weekday property has sunday be 6. + days_to_go_back = (7 * weeks_to_display) - end_date.weekday() + cutoff_date = end_date.date() - timedelta(days=days_to_go_back) + last_sale_list = iter(member.sale_set.filter(timestamp__gte=cutoff_date, timestamp__lt=end_date).order_by('timestamp')) + + products_by_day = [] + dates_by_day = [] + + next_sale = next(last_sale_list) + next_sale_date = next_sale.timestamp.date() + + for single_date in (end_date - timedelta(days=n) for n in range(days_to_go_back)): + products_by_day.append([]) + dates_by_day.append(single_date) + + try: + while next_sale_date == single_date.date(): + products_by_day[-1].append(next_sale.product) + + next_sale = next(last_sale_list) + next_sale_date = next_sale.timestamp.date() + except StopIteration: + next_sale = None + next_sale_date = None + continue + + # Proposed format: + # Returned list: [<>, <>, ...] + # Day item: (<>, <>, [<>, ...]) + days = [] + + for day_index in range(len(products_by_day)): + day_color = __get_purchase_heatmap_day_color(products_by_day[day_index], category_name_color) + days.append((dates_by_day[day_index], day_color, [x.id for x in products_by_day[day_index]])) + + return days + + +def __get_purchase_heatmap_data_mockup(member: Member, + end_date: datetime.datetime, + weeks_to_display: int, + category_name_color: (str, str, str,) + ) -> list: # Proposed format: # Returned list: [<>, <>, ...] # Day item: (<>, <>, [<>, ...]) @@ -104,7 +152,7 @@ def __get_purchase_heatmap_data(member: Member, start_date: datetime.date) -> li mockup_colors = [(235, 237, 240), (155, 233, 168), (64, 196, 99), (33, 110, 57)] mockup_list = [] - for i in range(70 - start_date.weekday()): + for i in range(70 - end_date.weekday()): mockup_item_count = random.randint(0, len(mockup_colors) - 1) mockup_list.append( ( @@ -261,15 +309,19 @@ def usermenu(request, room, member, bought, from_sale=False): give_multibuy_hint, sale_hints = _multibuy_hint(timezone.now(), member) give_multibuy_hint = give_multibuy_hint and from_sale - raw_heatmap_data = __get_purchase_heatmap_data(member, datetime.date.today()) - raw_heatmap_data.reverse() + # heatmap logic begin + weeks_to_display = 10 + from datetime import datetime, timedelta - heatmap_data = __organize_purchase_heatmap_data(raw_heatmap_data, datetime.date.today()) + raw_heatmap_data = __get_purchase_heatmap_data_mockup(member, datetime.today(), weeks_to_display, ("beer", "energy", "soda")) + heatmap_data = __organize_purchase_heatmap_data(raw_heatmap_data[::-1], datetime.today()) row_labels = ["", "Mandag", "", "Onsdag", "", "Fredag", ""] - column_labels = ["26", "27", "28", "29", "30", "31", "32", "33", "34", "35"] + current_week = datetime.today().isocalendar()[1] + column_labels = [str(current_week - x) for x in range(weeks_to_display)][::-1] rows = zip(row_labels, heatmap_data) + # heatmap logic end if member.has_stregforbud(): return render(request, 'stregsystem/error_stregforbud.html', locals()) else: From c199651b12df23cec03faff3c4cd43246081d740 Mon Sep 17 00:00:00 2001 From: Kresten Laust Date: Mon, 4 Sep 2023 00:19:52 +0200 Subject: [PATCH 08/61] Reformatted and removed redundant imports --- stregsystem/views.py | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/stregsystem/views.py b/stregsystem/views.py index 3b4f0f3e..fc3b0671 100644 --- a/stregsystem/views.py +++ b/stregsystem/views.py @@ -86,12 +86,12 @@ def __organize_purchase_heatmap_data(heatmap_data: list, start_date: datetime.da def __get_purchase_heatmap_day_color( - products: List[Product], - category_name_color: ( - str, - str, - str, - ), + products: List[Product], + category_name_color: ( + str, + str, + str, + ), ) -> (int, int, int,): return (50, 50, 50) @@ -101,11 +101,12 @@ def __get_purchase_heatmap_data(member: Member, weeks_to_display: int, category_name_color: (str, str, str,) ) -> list: - from datetime import datetime, timedelta + from datetime import timedelta # Possibly off-by-one because visual starts on sunday, but weekday property has sunday be 6. days_to_go_back = (7 * weeks_to_display) - end_date.weekday() cutoff_date = end_date.date() - timedelta(days=days_to_go_back) - last_sale_list = iter(member.sale_set.filter(timestamp__gte=cutoff_date, timestamp__lt=end_date).order_by('timestamp')) + last_sale_list = iter( + member.sale_set.filter(timestamp__gte=cutoff_date, timestamp__lt=end_date).order_by('timestamp')) products_by_day = [] dates_by_day = [] @@ -141,9 +142,9 @@ def __get_purchase_heatmap_data(member: Member, def __get_purchase_heatmap_data_mockup(member: Member, - end_date: datetime.datetime, - weeks_to_display: int, - category_name_color: (str, str, str,) + end_date: datetime.datetime, + weeks_to_display: int, + category_name_color: (str, str, str,) ) -> list: # Proposed format: # Returned list: [<>, <>, ...] @@ -311,9 +312,10 @@ def usermenu(request, room, member, bought, from_sale=False): # heatmap logic begin weeks_to_display = 10 - from datetime import datetime, timedelta + from datetime import datetime - raw_heatmap_data = __get_purchase_heatmap_data_mockup(member, datetime.today(), weeks_to_display, ("beer", "energy", "soda")) + raw_heatmap_data = __get_purchase_heatmap_data_mockup(member, datetime.today(), weeks_to_display, + ("beer", "energy", "soda")) heatmap_data = __organize_purchase_heatmap_data(raw_heatmap_data[::-1], datetime.today()) row_labels = ["", "Mandag", "", "Onsdag", "", "Fredag", ""] current_week = datetime.today().isocalendar()[1] From affab843c9e0e49727bc1477918ef2fcce791fdf Mon Sep 17 00:00:00 2001 From: Kresten Laust Date: Mon, 4 Sep 2023 13:28:06 +0200 Subject: [PATCH 09/61] Corrected off-by-one error in day rendering. Finished database queryies for now. --- stregsystem/views.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/stregsystem/views.py b/stregsystem/views.py index fc3b0671..dbc49f57 100644 --- a/stregsystem/views.py +++ b/stregsystem/views.py @@ -103,7 +103,7 @@ def __get_purchase_heatmap_data(member: Member, ) -> list: from datetime import timedelta # Possibly off-by-one because visual starts on sunday, but weekday property has sunday be 6. - days_to_go_back = (7 * weeks_to_display) - end_date.weekday() + days_to_go_back = (7 * weeks_to_display) - (6 - end_date.weekday() - 1) cutoff_date = end_date.date() - timedelta(days=days_to_go_back) last_sale_list = iter( member.sale_set.filter(timestamp__gte=cutoff_date, timestamp__lt=end_date).order_by('timestamp')) @@ -111,12 +111,17 @@ def __get_purchase_heatmap_data(member: Member, products_by_day = [] dates_by_day = [] - next_sale = next(last_sale_list) - next_sale_date = next_sale.timestamp.date() + try: + next_sale = next(last_sale_list) + next_sale_date = next_sale.timestamp.date() + except StopIteration: + next_sale = None + next_sale_date = None + pass for single_date in (end_date - timedelta(days=n) for n in range(days_to_go_back)): products_by_day.append([]) - dates_by_day.append(single_date) + dates_by_day.append(single_date.date()) try: while next_sale_date == single_date.date(): @@ -153,11 +158,11 @@ def __get_purchase_heatmap_data_mockup(member: Member, mockup_colors = [(235, 237, 240), (155, 233, 168), (64, 196, 99), (33, 110, 57)] mockup_list = [] - for i in range(70 - end_date.weekday()): + for i in range(70 - (6 - end_date.weekday() - 1)): mockup_item_count = random.randint(0, len(mockup_colors) - 1) mockup_list.append( ( - datetime.datetime.today() - datetime.timedelta(days=i), + (datetime.datetime.today() - datetime.timedelta(days=i)).date(), mockup_colors[mockup_item_count], [1] * mockup_item_count, ) @@ -314,7 +319,7 @@ def usermenu(request, room, member, bought, from_sale=False): weeks_to_display = 10 from datetime import datetime - raw_heatmap_data = __get_purchase_heatmap_data_mockup(member, datetime.today(), weeks_to_display, + raw_heatmap_data = __get_purchase_heatmap_data(member, datetime.today(), weeks_to_display, ("beer", "energy", "soda")) heatmap_data = __organize_purchase_heatmap_data(raw_heatmap_data[::-1], datetime.today()) row_labels = ["", "Mandag", "", "Onsdag", "", "Fredag", ""] From 2df70b828cf159254ee03486b960fb3be50ba443 Mon Sep 17 00:00:00 2001 From: Kresten Laust Date: Mon, 4 Sep 2023 13:52:43 +0200 Subject: [PATCH 10/61] Reformatted code using black --- stregsystem/views.py | 59 ++++++++++++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/stregsystem/views.py b/stregsystem/views.py index dbc49f57..cb875913 100644 --- a/stregsystem/views.py +++ b/stregsystem/views.py @@ -86,27 +86,42 @@ def __organize_purchase_heatmap_data(heatmap_data: list, start_date: datetime.da def __get_purchase_heatmap_day_color( - products: List[Product], - category_name_color: ( - str, - str, - str, - ), + products: List[Product], + category_name_color: ( + str, + str, + str, + ), ) -> (int, int, int,): + red = 0 + green = 0 + blue = 0 + + for product in products: + print(product.categories) + pass + return (50, 50, 50) -def __get_purchase_heatmap_data(member: Member, - end_date: datetime.datetime, - weeks_to_display: int, - category_name_color: (str, str, str,) - ) -> list: +def __get_purchase_heatmap_data( + member: Member, + end_date: datetime.datetime, + weeks_to_display: int, + category_name_color: ( + str, + str, + str, + ), +) -> list: from datetime import timedelta + # Possibly off-by-one because visual starts on sunday, but weekday property has sunday be 6. days_to_go_back = (7 * weeks_to_display) - (6 - end_date.weekday() - 1) cutoff_date = end_date.date() - timedelta(days=days_to_go_back) last_sale_list = iter( - member.sale_set.filter(timestamp__gte=cutoff_date, timestamp__lt=end_date).order_by('timestamp')) + member.sale_set.filter(timestamp__gte=cutoff_date, timestamp__lt=end_date).order_by('timestamp') + ) products_by_day = [] dates_by_day = [] @@ -146,11 +161,16 @@ def __get_purchase_heatmap_data(member: Member, return days -def __get_purchase_heatmap_data_mockup(member: Member, - end_date: datetime.datetime, - weeks_to_display: int, - category_name_color: (str, str, str,) - ) -> list: +def __get_purchase_heatmap_data_mockup( + member: Member, + end_date: datetime.datetime, + weeks_to_display: int, + category_name_color: ( + str, + str, + str, + ), +) -> list: # Proposed format: # Returned list: [<>, <>, ...] # Day item: (<>, <>, [<>, ...]) @@ -319,8 +339,9 @@ def usermenu(request, room, member, bought, from_sale=False): weeks_to_display = 10 from datetime import datetime - raw_heatmap_data = __get_purchase_heatmap_data(member, datetime.today(), weeks_to_display, - ("beer", "energy", "soda")) + raw_heatmap_data = __get_purchase_heatmap_data( + member, datetime.today(), weeks_to_display, ("beer", "energy", "soda") + ) heatmap_data = __organize_purchase_heatmap_data(raw_heatmap_data[::-1], datetime.today()) row_labels = ["", "Mandag", "", "Onsdag", "", "Fredag", ""] current_week = datetime.today().isocalendar()[1] From 2bb08bef6f1c9f346a926f3219e70cb98a164a75 Mon Sep 17 00:00:00 2001 From: Kresten Laust Date: Tue, 5 Sep 2023 20:09:43 +0200 Subject: [PATCH 11/61] Finished implementing database lookup! Basic heatmap functionality is finished. --- stregsystem/views.py | 37 +++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/stregsystem/views.py b/stregsystem/views.py index cb875913..be34e231 100644 --- a/stregsystem/views.py +++ b/stregsystem/views.py @@ -87,21 +87,25 @@ def __organize_purchase_heatmap_data(heatmap_data: list, start_date: datetime.da def __get_purchase_heatmap_day_color( products: List[Product], - category_name_color: ( - str, - str, - str, + products_by_color: ( + List[Product], + List[Product], + List[Product], ), ) -> (int, int, int,): - red = 0 - green = 0 - blue = 0 + category_representation = [0, 0, 0] for product in products: - print(product.categories) - pass + for category_products_index in range(3): + if product in products_by_color[category_products_index]: + category_representation[category_products_index] = category_representation[category_products_index] + 1 - return (50, 50, 50) + total_category_sum = sum(category_representation) + + if total_category_sum == 0: + return 235, 237, 240 # Grey + + return tuple(category_sum / total_category_sum * 255 for category_sum in category_representation) def __get_purchase_heatmap_data( @@ -116,11 +120,10 @@ def __get_purchase_heatmap_data( ) -> list: from datetime import timedelta - # Possibly off-by-one because visual starts on sunday, but weekday property has sunday be 6. days_to_go_back = (7 * weeks_to_display) - (6 - end_date.weekday() - 1) cutoff_date = end_date.date() - timedelta(days=days_to_go_back) last_sale_list = iter( - member.sale_set.filter(timestamp__gte=cutoff_date, timestamp__lt=end_date).order_by('timestamp') + member.sale_set.filter(timestamp__gte=cutoff_date, timestamp__lte=end_date).order_by('timestamp') ) products_by_day = [] @@ -149,18 +152,24 @@ def __get_purchase_heatmap_data( next_sale_date = None continue + category_by_name = tuple(Category.objects.filter(name=cat_name) for cat_name in category_name_color) + products_by_category = tuple( + Product.objects.filter(categories__in=category_ins) for category_ins in category_by_name + ) + # Proposed format: # Returned list: [<>, <>, ...] # Day item: (<>, <>, [<>, ...]) days = [] for day_index in range(len(products_by_day)): - day_color = __get_purchase_heatmap_day_color(products_by_day[day_index], category_name_color) + day_color = __get_purchase_heatmap_day_color(products_by_day[day_index], products_by_category) days.append((dates_by_day[day_index], day_color, [x.id for x in products_by_day[day_index]])) return days +""" def __get_purchase_heatmap_data_mockup( member: Member, end_date: datetime.datetime, @@ -188,7 +197,7 @@ def __get_purchase_heatmap_data_mockup( ) ) - return mockup_list + return mockup_list""" def roomindex(request): From 4b4f79117d0fb4c4ef8801cedb782d750cb8d00c Mon Sep 17 00:00:00 2001 From: Kresten Laust Date: Wed, 6 Sep 2023 13:54:27 +0200 Subject: [PATCH 12/61] Inverse filtering --- stregsystem/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stregsystem/views.py b/stregsystem/views.py index be34e231..e6488dd0 100644 --- a/stregsystem/views.py +++ b/stregsystem/views.py @@ -123,7 +123,7 @@ def __get_purchase_heatmap_data( days_to_go_back = (7 * weeks_to_display) - (6 - end_date.weekday() - 1) cutoff_date = end_date.date() - timedelta(days=days_to_go_back) last_sale_list = iter( - member.sale_set.filter(timestamp__gte=cutoff_date, timestamp__lte=end_date).order_by('timestamp') + member.sale_set.filter(timestamp__gte=cutoff_date, timestamp__lte=end_date).order_by('-timestamp') ) products_by_day = [] @@ -152,7 +152,7 @@ def __get_purchase_heatmap_data( next_sale_date = None continue - category_by_name = tuple(Category.objects.filter(name=cat_name) for cat_name in category_name_color) + category_by_name = [Category.objects.filter(name=cat_name) for cat_name in category_name_color] products_by_category = tuple( Product.objects.filter(categories__in=category_ins) for category_ins in category_by_name ) From dd73440a89349118361bfdbccc6e896be4d7bb93 Mon Sep 17 00:00:00 2001 From: Kresten Laust Date: Wed, 6 Sep 2023 13:57:19 +0200 Subject: [PATCH 13/61] Removed mockup function --- stregsystem/views.py | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/stregsystem/views.py b/stregsystem/views.py index e6488dd0..177c0c3c 100644 --- a/stregsystem/views.py +++ b/stregsystem/views.py @@ -169,37 +169,6 @@ def __get_purchase_heatmap_data( return days -""" -def __get_purchase_heatmap_data_mockup( - member: Member, - end_date: datetime.datetime, - weeks_to_display: int, - category_name_color: ( - str, - str, - str, - ), -) -> list: - # Proposed format: - # Returned list: [<>, <>, ...] - # Day item: (<>, <>, [<>, ...]) - - mockup_colors = [(235, 237, 240), (155, 233, 168), (64, 196, 99), (33, 110, 57)] - - mockup_list = [] - for i in range(70 - (6 - end_date.weekday() - 1)): - mockup_item_count = random.randint(0, len(mockup_colors) - 1) - mockup_list.append( - ( - (datetime.datetime.today() - datetime.timedelta(days=i)).date(), - mockup_colors[mockup_item_count], - [1] * mockup_item_count, - ) - ) - - return mockup_list""" - - def roomindex(request): return HttpResponsePermanentRedirect('/1/') From baf1399d530be1e40b0086971c13eaec7396ae83 Mon Sep 17 00:00:00 2001 From: Kresten Laust Date: Wed, 6 Sep 2023 14:53:13 +0200 Subject: [PATCH 14/61] moved heatmap logic into separate file --- stregsystem/purchase_heatmap.py | 93 +++++++++++++++++++++++++++++++++ stregsystem/views.py | 93 ++------------------------------- 2 files changed, 98 insertions(+), 88 deletions(-) create mode 100644 stregsystem/purchase_heatmap.py diff --git a/stregsystem/purchase_heatmap.py b/stregsystem/purchase_heatmap.py new file mode 100644 index 00000000..95d6a451 --- /dev/null +++ b/stregsystem/purchase_heatmap.py @@ -0,0 +1,93 @@ +from typing import List + +from stregsystem.models import Product, Member, Category +from datetime import datetime, timedelta + + +def __get_purchase_heatmap_day_color( + products: List[Product], + products_by_color: ( + List[Product], + List[Product], + List[Product], + ), + max_items_day: int, +) -> (int, int, int,): + category_representation = [0, 0, 0] + + for product in products: + for category_products_index in range(3): + if product in products_by_color[category_products_index]: + category_representation[category_products_index] = category_representation[category_products_index] + 1 + + total_category_sum = sum(category_representation) + + brightness = total_category_sum / max_items_day + + if total_category_sum == 0: + return 235, 237, 240 # Grey + + return tuple(category_sum / total_category_sum * 255 * brightness for category_sum in category_representation) + + +def __get_purchase_heatmap_data( + member: Member, + end_date: datetime, + weeks_to_display: int, + category_name_color: ( + str, + str, + str, + ), +) -> list: + days_to_go_back = (7 * weeks_to_display) - (6 - end_date.weekday() - 1) + cutoff_date = end_date.date() - timedelta(days=days_to_go_back) + last_sale_list = iter( + member.sale_set.filter(timestamp__gte=cutoff_date, timestamp__lte=end_date).order_by('-timestamp') + ) + + products_by_day = [] + dates_by_day = [] + + try: + next_sale = next(last_sale_list) + next_sale_date = next_sale.timestamp.date() + except StopIteration: + next_sale = None + next_sale_date = None + pass + + max_day_items = 0 + + for single_date in (end_date - timedelta(days=n) for n in range(days_to_go_back)): + products_by_day.append([]) + dates_by_day.append(single_date.date()) + + try: + while next_sale_date == single_date.date(): + products_by_day[-1].append(next_sale.product) + + next_sale = next(last_sale_list) + next_sale_date = next_sale.timestamp.date() + except StopIteration: + next_sale = None + next_sale_date = None + + max_day_items = max(max_day_items, len(products_by_day[-1])) + + category_by_name = [Category.objects.filter(name=cat_name) for cat_name in category_name_color] + products_by_category = tuple( + Product.objects.filter(categories__in=category_ins) for category_ins in category_by_name + ) + + # Proposed format: + # Returned list: [<>, <>, ...] + # Day item: (<>, <>, [<>, ...]) + days = [] + + for day_index in range(len(products_by_day)): + day_color = __get_purchase_heatmap_day_color(products_by_day[day_index], products_by_category, max_day_items) + days.append((dates_by_day[day_index], day_color, [x.id for x in products_by_day[day_index]])) + + return days + diff --git a/stregsystem/views.py b/stregsystem/views.py index 177c0c3c..c84ffa9c 100644 --- a/stregsystem/views.py +++ b/stregsystem/views.py @@ -53,6 +53,8 @@ import json +from .purchase_heatmap import __get_purchase_heatmap_data + def __get_news(): try: @@ -85,90 +87,6 @@ def __organize_purchase_heatmap_data(heatmap_data: list, start_date: datetime.da return new_list -def __get_purchase_heatmap_day_color( - products: List[Product], - products_by_color: ( - List[Product], - List[Product], - List[Product], - ), -) -> (int, int, int,): - category_representation = [0, 0, 0] - - for product in products: - for category_products_index in range(3): - if product in products_by_color[category_products_index]: - category_representation[category_products_index] = category_representation[category_products_index] + 1 - - total_category_sum = sum(category_representation) - - if total_category_sum == 0: - return 235, 237, 240 # Grey - - return tuple(category_sum / total_category_sum * 255 for category_sum in category_representation) - - -def __get_purchase_heatmap_data( - member: Member, - end_date: datetime.datetime, - weeks_to_display: int, - category_name_color: ( - str, - str, - str, - ), -) -> list: - from datetime import timedelta - - days_to_go_back = (7 * weeks_to_display) - (6 - end_date.weekday() - 1) - cutoff_date = end_date.date() - timedelta(days=days_to_go_back) - last_sale_list = iter( - member.sale_set.filter(timestamp__gte=cutoff_date, timestamp__lte=end_date).order_by('-timestamp') - ) - - products_by_day = [] - dates_by_day = [] - - try: - next_sale = next(last_sale_list) - next_sale_date = next_sale.timestamp.date() - except StopIteration: - next_sale = None - next_sale_date = None - pass - - for single_date in (end_date - timedelta(days=n) for n in range(days_to_go_back)): - products_by_day.append([]) - dates_by_day.append(single_date.date()) - - try: - while next_sale_date == single_date.date(): - products_by_day[-1].append(next_sale.product) - - next_sale = next(last_sale_list) - next_sale_date = next_sale.timestamp.date() - except StopIteration: - next_sale = None - next_sale_date = None - continue - - category_by_name = [Category.objects.filter(name=cat_name) for cat_name in category_name_color] - products_by_category = tuple( - Product.objects.filter(categories__in=category_ins) for category_ins in category_by_name - ) - - # Proposed format: - # Returned list: [<>, <>, ...] - # Day item: (<>, <>, [<>, ...]) - days = [] - - for day_index in range(len(products_by_day)): - day_color = __get_purchase_heatmap_day_color(products_by_day[day_index], products_by_category) - days.append((dates_by_day[day_index], day_color, [x.id for x in products_by_day[day_index]])) - - return days - - def roomindex(request): return HttpResponsePermanentRedirect('/1/') @@ -315,14 +233,13 @@ def usermenu(request, room, member, bought, from_sale=False): # heatmap logic begin weeks_to_display = 10 - from datetime import datetime raw_heatmap_data = __get_purchase_heatmap_data( - member, datetime.today(), weeks_to_display, ("beer", "energy", "soda") + member, datetime.datetime.today(), weeks_to_display, ("beer", "energy", "soda") ) - heatmap_data = __organize_purchase_heatmap_data(raw_heatmap_data[::-1], datetime.today()) + heatmap_data = __organize_purchase_heatmap_data(raw_heatmap_data[::-1], datetime.datetime.today()) row_labels = ["", "Mandag", "", "Onsdag", "", "Fredag", ""] - current_week = datetime.today().isocalendar()[1] + current_week = datetime.datetime.today().isocalendar()[1] column_labels = [str(current_week - x) for x in range(weeks_to_display)][::-1] rows = zip(row_labels, heatmap_data) From b94e8c3b3cd59c4fe337402ff504057ce81743c6 Mon Sep 17 00:00:00 2001 From: Kresten Laust Date: Wed, 6 Sep 2023 22:17:17 +0200 Subject: [PATCH 15/61] Moved remaining function to separate file, and added typing --- stregsystem/purchase_heatmap.py | 52 +++++++++++++++++++++++++++++---- stregsystem/views.py | 36 ++--------------------- 2 files changed, 49 insertions(+), 39 deletions(-) diff --git a/stregsystem/purchase_heatmap.py b/stregsystem/purchase_heatmap.py index 95d6a451..04e5e7ff 100644 --- a/stregsystem/purchase_heatmap.py +++ b/stregsystem/purchase_heatmap.py @@ -1,10 +1,32 @@ -from typing import List +from typing import List, NamedTuple, Tuple from stregsystem.models import Product, Member, Category from datetime import datetime, timedelta -def __get_purchase_heatmap_day_color( +class HeatmapDay(NamedTuple): + date: datetime.date + color: Tuple[int, int, int] + products: List[Product] + + +def get_heatmap_graph_data(member) -> (List[str], List[Tuple[str, HeatmapDay]]): + __weeks_to_display = 10 + + __raw_heatmap_data = __get_purchase_heatmap_data( + member, datetime.today(), __weeks_to_display, ("beer", "energy", "soda") + ) + __heatmap_data = __organize_purchase_heatmap_data(__raw_heatmap_data[::-1], datetime.today()) + __row_labels = ["", "Mandag", "", "Onsdag", "", "Fredag", ""] + __current_week = datetime.today().isocalendar()[1] + column_labels = [str(__current_week - x) for x in range(__weeks_to_display)][::-1] + + rows = zip(__row_labels, __heatmap_data) + + return column_labels, rows + + +def __get_heatmap_day_color( products: List[Product], products_by_color: ( List[Product], @@ -39,7 +61,7 @@ def __get_purchase_heatmap_data( str, str, ), -) -> list: +) -> List[HeatmapDay]: days_to_go_back = (7 * weeks_to_display) - (6 - end_date.weekday() - 1) cutoff_date = end_date.date() - timedelta(days=days_to_go_back) last_sale_list = iter( @@ -86,8 +108,28 @@ def __get_purchase_heatmap_data( days = [] for day_index in range(len(products_by_day)): - day_color = __get_purchase_heatmap_day_color(products_by_day[day_index], products_by_category, max_day_items) - days.append((dates_by_day[day_index], day_color, [x.id for x in products_by_day[day_index]])) + day_color = __get_heatmap_day_color(products_by_day[day_index], products_by_category, max_day_items) + days.append( + HeatmapDay(dates_by_day[day_index], day_color, products_by_day[day_index]) + ) return days + +def __organize_purchase_heatmap_data(heatmap_data: list, start_date: datetime.date) -> list: + # Transforms [<>, <>, ...] + # into + # [ + # [<>, <>, ...], + # [<>, <>, ...], + # ...] + + # TODO: Doesn't take current weekday into consideration. + new_list = [] + for i in range(7): + new_list.append([]) + for j in range(len(heatmap_data)): + if j % 7 == i: + new_list[i].append(heatmap_data[j]) + + return new_list diff --git a/stregsystem/views.py b/stregsystem/views.py index c84ffa9c..447e41f9 100644 --- a/stregsystem/views.py +++ b/stregsystem/views.py @@ -23,7 +23,7 @@ from django_select2 import forms as s2forms import urllib.parse -from stregsystem import parser +from stregsystem import parser, purchase_heatmap from stregsystem.models import ( Member, Payment, @@ -67,26 +67,6 @@ def __get_news(): def __get_productlist(room_id): return make_active_productlist_query(Product.objects).filter(make_room_specific_query(room_id)) - -def __organize_purchase_heatmap_data(heatmap_data: list, start_date: datetime.date) -> list: - # Transforms [<>, <>, ...] - # into - # [ - # [<>, <>, ...], - # [<>, <>, ...], - # ...] - - # TODO: Doesn't take current weekday into consideration. - new_list = [] - for i in range(7): - new_list.append([]) - for j in range(len(heatmap_data)): - if j % 7 == i: - new_list[i].append(heatmap_data[j]) - - return new_list - - def roomindex(request): return HttpResponsePermanentRedirect('/1/') @@ -231,20 +211,8 @@ def usermenu(request, room, member, bought, from_sale=False): give_multibuy_hint, sale_hints = _multibuy_hint(timezone.now(), member) give_multibuy_hint = give_multibuy_hint and from_sale - # heatmap logic begin - weeks_to_display = 10 - - raw_heatmap_data = __get_purchase_heatmap_data( - member, datetime.datetime.today(), weeks_to_display, ("beer", "energy", "soda") - ) - heatmap_data = __organize_purchase_heatmap_data(raw_heatmap_data[::-1], datetime.datetime.today()) - row_labels = ["", "Mandag", "", "Onsdag", "", "Fredag", ""] - current_week = datetime.datetime.today().isocalendar()[1] - column_labels = [str(current_week - x) for x in range(weeks_to_display)][::-1] - - rows = zip(row_labels, heatmap_data) + column_labels, rows = purchase_heatmap.get_heatmap_graph_data(member) - # heatmap logic end if member.has_stregforbud(): return render(request, 'stregsystem/error_stregforbud.html', locals()) else: From 4eab7697c51eff1b79241dbe7dc8317160338842 Mon Sep 17 00:00:00 2001 From: Kresten Laust Date: Wed, 6 Sep 2023 22:22:58 +0200 Subject: [PATCH 16/61] Prepared table to support multiple color modes --- stregsystem/purchase_heatmap.py | 4 +--- .../templates/stregsystem/purchase_heatmap.html | 15 +++++++++++++-- stregsystem/views.py | 1 + 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/stregsystem/purchase_heatmap.py b/stregsystem/purchase_heatmap.py index 04e5e7ff..47db826c 100644 --- a/stregsystem/purchase_heatmap.py +++ b/stregsystem/purchase_heatmap.py @@ -109,9 +109,7 @@ def __get_purchase_heatmap_data( for day_index in range(len(products_by_day)): day_color = __get_heatmap_day_color(products_by_day[day_index], products_by_category, max_day_items) - days.append( - HeatmapDay(dates_by_day[day_index], day_color, products_by_day[day_index]) - ) + days.append(HeatmapDay(dates_by_day[day_index], day_color, products_by_day[day_index])) return days diff --git a/stregsystem/templates/stregsystem/purchase_heatmap.html b/stregsystem/templates/stregsystem/purchase_heatmap.html index 10d8634d..21d4c8f2 100644 --- a/stregsystem/templates/stregsystem/purchase_heatmap.html +++ b/stregsystem/templates/stregsystem/purchase_heatmap.html @@ -13,7 +13,7 @@ {{weekday_label}} {% for day_data in weekday_data %} - + {% endfor %} {% endfor %} @@ -54,10 +54,21 @@ shape-rendering: geometricPrecision; padding: 0; outline: 1px solid rgba(27, 31, 35, 0.06); + fill: var(--cell-color); background-color: var(--cell-color); } .heatmap-cell:hover { border-radius: 5px; } - \ No newline at end of file + + + \ No newline at end of file diff --git a/stregsystem/views.py b/stregsystem/views.py index 447e41f9..13eef209 100644 --- a/stregsystem/views.py +++ b/stregsystem/views.py @@ -67,6 +67,7 @@ def __get_news(): def __get_productlist(room_id): return make_active_productlist_query(Product.objects).filter(make_room_specific_query(room_id)) + def roomindex(request): return HttpResponsePermanentRedirect('/1/') From 8907d1fc9aa22dfacf07f984862eb0ecacb88eec Mon Sep 17 00:00:00 2001 From: Kresten Laust Date: Thu, 7 Sep 2023 22:58:21 +0200 Subject: [PATCH 17/61] Finished multiview, and added general view, and figure description --- stregsystem/purchase_heatmap.py | 20 +++++++--- .../stregsystem/purchase_heatmap.html | 39 ++++++++++++++++--- 2 files changed, 48 insertions(+), 11 deletions(-) diff --git a/stregsystem/purchase_heatmap.py b/stregsystem/purchase_heatmap.py index 47db826c..95bb0313 100644 --- a/stregsystem/purchase_heatmap.py +++ b/stregsystem/purchase_heatmap.py @@ -6,8 +6,8 @@ class HeatmapDay(NamedTuple): date: datetime.date - color: Tuple[int, int, int] - products: List[Product] + color: Tuple[Tuple[int, int, int], Tuple[int, int, int]] + products: List[int] def get_heatmap_graph_data(member) -> (List[str], List[Tuple[str, HeatmapDay]]): @@ -26,7 +26,7 @@ def get_heatmap_graph_data(member) -> (List[str], List[Tuple[str, HeatmapDay]]): return column_labels, rows -def __get_heatmap_day_color( +def __get_heatmap_day_color_categories( products: List[Product], products_by_color: ( List[Product], @@ -52,6 +52,13 @@ def __get_heatmap_day_color( return tuple(category_sum / total_category_sum * 255 * brightness for category_sum in category_representation) +def __get_heatmap_day_color_general(products: List[Product], max_items_day: int) -> (int, int, int): + if len(products) == 0: + return 235, 237, 240 # Grey + + return 0, int(255 * (len(products) / max_items_day)), 0 + + def __get_purchase_heatmap_data( member: Member, end_date: datetime, @@ -108,8 +115,11 @@ def __get_purchase_heatmap_data( days = [] for day_index in range(len(products_by_day)): - day_color = __get_heatmap_day_color(products_by_day[day_index], products_by_category, max_day_items) - days.append(HeatmapDay(dates_by_day[day_index], day_color, products_by_day[day_index])) + category_day_color = __get_heatmap_day_color_categories(products_by_day[day_index], products_by_category, max_day_items) + general_day_color = __get_heatmap_day_color_general(products_by_day[day_index], max_day_items) + days.append( + HeatmapDay(dates_by_day[day_index], (category_day_color, general_day_color), [product.id for product in products_by_day[day_index]]) + ) return days diff --git a/stregsystem/templates/stregsystem/purchase_heatmap.html b/stregsystem/templates/stregsystem/purchase_heatmap.html index 21d4c8f2..7cd7fefa 100644 --- a/stregsystem/templates/stregsystem/purchase_heatmap.html +++ b/stregsystem/templates/stregsystem/purchase_heatmap.html @@ -1,4 +1,4 @@ -
+
@@ -13,21 +13,38 @@ {% for day_data in weekday_data %} - + {% endfor %} {% endfor %}
{{weekday_label}} + +
+
+ +
+
=ØL. +
=Energi. +
=Soda. +
+ +
\ No newline at end of file From 6405026fa07e40a627369800e123297d80146b81 Mon Sep 17 00:00:00 2001 From: Kresten Laust Date: Thu, 7 Sep 2023 22:59:19 +0200 Subject: [PATCH 18/61] Temporarily added heatmap testdata --- stregsystem/fixtures/testdata-heatmap.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 stregsystem/fixtures/testdata-heatmap.json diff --git a/stregsystem/fixtures/testdata-heatmap.json b/stregsystem/fixtures/testdata-heatmap.json new file mode 100644 index 00000000..41fef4ed --- /dev/null +++ b/stregsystem/fixtures/testdata-heatmap.json @@ -0,0 +1 @@ +[{"model": "stregsystem.member", "pk": 1, "fields": {"active": true, "username": "tester", "year": "2017", "firstname": "Test", "lastname": "Testsen", "gender": "U", "email": "plznospam@fklub.dk", "want_spam": true, "balance": 13357337, "undo_count": 0, "notes": "This is a test user."}}, {"model": "stregsystem.member", "pk": 2, "fields": {"active": true, "username": "mlarsen", "year": "2020", "firstname": "Martin", "lastname": "Larsen", "gender": "M", "email": "mlarsen@nsa.gov", "want_spam": true, "balance": 10000, "undo_count": 0, "notes": ""}}, {"model": "stregsystem.member", "pk": 3, "fields": {"active": true, "username": "tables", "year": "2020", "firstname": "Bobby", "lastname": "Tables", "gender": "M", "email": "tables@nsa.gov", "want_spam": true, "balance": 12500, "undo_count": 0, "notes": ""}}, {"model": "stregsystem.member", "pk": 4, "fields": {"active": true, "username": "marx", "year": "2020", "firstname": "Karl", "lastname": "Marx", "gender": "M", "email": "kmarx@nsa.gov", "want_spam": true, "balance": 6900, "undo_count": 0, "notes": ""}}, {"model": "stregsystem.member", "pk": 5, "fields": {"active": true, "username": "jdoe", "year": "2020", "firstname": "John", "lastname": "Doe", "gender": "M", "email": "jdoe@nsa.gov", "want_spam": true, "balance": 42000, "undo_count": 0, "notes": ""}}, {"model": "stregsystem.mobilepayment", "pk": 1, "fields": {"member": null, "payment": null, "customer_name": "Martin Larsen", "timestamp": "2019-11-29T12:51:08.857Z", "amount": 6969, "transaction_id": "156E027485173228", "comment": "mlarsen indbetalt ", "status": "U"}}, {"model": "stregsystem.mobilepayment", "pk": 2, "fields": {"member": null, "payment": null, "customer_name": "Bobby Tables", "timestamp": "2019-11-28T16:40:55.719Z", "amount": 50000, "transaction_id": "232E027452733666", "comment": "tables eksdee", "status": "U"}}, {"model": "stregsystem.mobilepayment", "pk": 3, "fields": {"member": 4, "payment": null, "customer_name": "Karl Marx", "timestamp": "2019-11-28T14:30:58.357Z", "amount": 20000, "transaction_id": "241E027449465355", "comment": "marx", "status": "U"}}, {"model": "stregsystem.mobilepayment", "pk": 4, "fields": {"member": null, "payment": null, "customer_name": "Bobby Tables", "timestamp": "2019-11-28T16:40:55.719Z", "amount": 50000, "transaction_id": "232E027452733676", "comment": "tables eksdee", "status": "U"}}, {"model": "stregsystem.mobilepayment", "pk": 5, "fields": {"member": 5, "payment": null, "customer_name": "John Doe", "timestamp": "2019-11-27T11:15:38.529Z", "amount": 15000, "transaction_id": "016E027417049990", "comment": "jdoe", "status": "U"}}, {"model": "stregsystem.mobilepayment", "pk": 6, "fields": {"member": 1, "payment": null, "customer_name": "Tester Testsen", "timestamp": "2019-11-26T11:54:37.521Z", "amount": 20000, "transaction_id": "207E027395896809", "comment": "tester", "status": "U"}}, {"model": "stregsystem.category", "pk": 12, "fields": {"name": "beer"}}, {"model": "stregsystem.category", "pk": 13, "fields": {"name": "soda"}}, {"model": "stregsystem.category", "pk": 14, "fields": {"name": "energy"}}, {"model": "stregsystem.room", "pk": 1, "fields": {"name": "testrummet", "description": "127.0.0.1"}}, {"model": "stregsystem.room", "pk": 2, "fields": {"name": "maymayrummet", "description": "svedigt"}}, {"model": "stregsystem.product", "pk": 1, "fields": {"name": "Test \u00d8L", "price": 100, "active": true, "start_date": null, "quantity": 0, "deactivate_date": null, "alcohol_content_ml": 0.0, "caffeine_content_mg": 0, "categories": [12], "rooms": []}}, {"model": "stregsystem.product", "pk": 2, "fields": {"name": "Kold Kaffe", "price": 200, "active": true, "start_date": null, "quantity": 0, "deactivate_date": null, "alcohol_content_ml": 0.0, "caffeine_content_mg": 0, "categories": [14], "rooms": []}}, {"model": "stregsystem.product", "pk": 3, "fields": {"name": "Fad\u00f8l", "price": 1500, "active": true, "start_date": null, "quantity": 0, "deactivate_date": null, "alcohol_content_ml": 0.0, "caffeine_content_mg": 0, "categories": [12], "rooms": [1]}}, {"model": "stregsystem.product", "pk": 13, "fields": {"name": "Sportscola", "price": 300, "active": true, "start_date": null, "quantity": 0, "deactivate_date": null, "alcohol_content_ml": 0.0, "caffeine_content_mg": 0, "categories": [13], "rooms": []}}, {"model": "stregsystem.oldprice", "pk": 1, "fields": {"product": 1, "price": 100, "changed_on": "2017-03-13T12:43:28.044Z"}}, {"model": "stregsystem.oldprice", "pk": 2, "fields": {"product": 2, "price": 200, "changed_on": "2017-03-13T12:43:40.784Z"}}, {"model": "stregsystem.oldprice", "pk": 3, "fields": {"product": 2, "price": 200, "changed_on": "2017-03-13T12:43:42.151Z"}}, {"model": "stregsystem.oldprice", "pk": 11, "fields": {"product": 13, "price": 300, "changed_on": "2023-09-06T06:59:00.505Z"}}, {"model": "stregsystem.oldprice", "pk": 12, "fields": {"product": 1, "price": 100, "changed_on": "2023-09-06T06:59:12.033Z"}}, {"model": "stregsystem.oldprice", "pk": 13, "fields": {"product": 2, "price": 200, "changed_on": "2023-09-06T06:59:28.392Z"}}, {"model": "stregsystem.oldprice", "pk": 14, "fields": {"product": 3, "price": 1500, "changed_on": "2023-09-06T06:59:50.330Z"}}, {"model": "stregsystem.sale", "pk": 1, "fields": {"member": 1, "product": 1, "room": 1, "timestamp": "2017-03-13T12:52:52.142Z", "price": 100}}, {"model": "stregsystem.sale", "pk": 2, "fields": {"member": 1, "product": 2, "room": 1, "timestamp": "2017-03-13T12:54:12.423Z", "price": 200}}, {"model": "stregsystem.sale", "pk": 3, "fields": {"member": 1, "product": 13, "room": 1, "timestamp": "2017-03-13T13:38:10.573Z", "price": 300}}, {"model": "stregsystem.sale", "pk": 8, "fields": {"member": 1, "product": 1, "room": 1, "timestamp": "2023-07-03T06:31:38.412Z", "price": 100}}, {"model": "stregsystem.sale", "pk": 9, "fields": {"member": 1, "product": 1, "room": 1, "timestamp": "2023-07-03T06:31:58.628Z", "price": 100}}, {"model": "stregsystem.sale", "pk": 10, "fields": {"member": 1, "product": 1, "room": 1, "timestamp": "2023-07-05T06:32:17.029Z", "price": 100}}, {"model": "stregsystem.sale", "pk": 11, "fields": {"member": 1, "product": 2, "room": 1, "timestamp": "2023-07-05T06:32:24.962Z", "price": 200}}, {"model": "stregsystem.sale", "pk": 12, "fields": {"member": 1, "product": 2, "room": 1, "timestamp": "2023-07-08T06:32:33.665Z", "price": 200}}, {"model": "stregsystem.sale", "pk": 13, "fields": {"member": 1, "product": 2, "room": 1, "timestamp": "2023-07-08T06:32:39.906Z", "price": 200}}, {"model": "stregsystem.sale", "pk": 14, "fields": {"member": 1, "product": 2, "room": 1, "timestamp": "2023-07-11T06:32:50.954Z", "price": 200}}, {"model": "stregsystem.sale", "pk": 15, "fields": {"member": 1, "product": 1, "room": 1, "timestamp": "2023-07-11T06:32:55.453Z", "price": 100}}, {"model": "stregsystem.sale", "pk": 16, "fields": {"member": 1, "product": 1, "room": 1, "timestamp": "2023-07-15T06:33:08.914Z", "price": 100}}, {"model": "stregsystem.sale", "pk": 17, "fields": {"member": 1, "product": 13, "room": 1, "timestamp": "2023-07-15T06:33:14.566Z", "price": 300}}, {"model": "stregsystem.sale", "pk": 18, "fields": {"member": 1, "product": 13, "room": 1, "timestamp": "2023-07-18T06:33:30.689Z", "price": 300}}, {"model": "stregsystem.sale", "pk": 19, "fields": {"member": 1, "product": 13, "room": 1, "timestamp": "2023-07-18T06:33:32.977Z", "price": 300}}, {"model": "stregsystem.sale", "pk": 20, "fields": {"member": 1, "product": 13, "room": 1, "timestamp": "2023-07-18T06:33:35.945Z", "price": 300}}, {"model": "stregsystem.sale", "pk": 21, "fields": {"member": 1, "product": 13, "room": 1, "timestamp": "2023-07-22T06:33:43.515Z", "price": 300}}, {"model": "stregsystem.sale", "pk": 22, "fields": {"member": 1, "product": 2, "room": 1, "timestamp": "2023-07-22T06:33:46.335Z", "price": 200}}, {"model": "stregsystem.sale", "pk": 23, "fields": {"member": 1, "product": 2, "room": 1, "timestamp": "2023-07-25T06:33:55.529Z", "price": 200}}, {"model": "stregsystem.sale", "pk": 24, "fields": {"member": 1, "product": 1, "room": 1, "timestamp": "2023-07-25T06:33:58.663Z", "price": 100}}, {"model": "stregsystem.sale", "pk": 25, "fields": {"member": 1, "product": 3, "room": 1, "timestamp": "2023-07-30T06:34:35.145Z", "price": 1500}}, {"model": "stregsystem.sale", "pk": 26, "fields": {"member": 1, "product": 3, "room": 1, "timestamp": "2023-07-30T06:34:38.892Z", "price": 1500}}, {"model": "stregsystem.sale", "pk": 27, "fields": {"member": 1, "product": 2, "room": 1, "timestamp": "2023-08-03T06:34:48.584Z", "price": 200}}, {"model": "stregsystem.sale", "pk": 28, "fields": {"member": 1, "product": 2, "room": 1, "timestamp": "2023-08-03T06:34:51.434Z", "price": 200}}, {"model": "stregsystem.sale", "pk": 29, "fields": {"member": 1, "product": 2, "room": 1, "timestamp": "2023-08-06T06:37:45.688Z", "price": 200}}, {"model": "stregsystem.sale", "pk": 30, "fields": {"member": 1, "product": 1, "room": 1, "timestamp": "2023-08-06T06:37:47.321Z", "price": 100}}, {"model": "stregsystem.sale", "pk": 31, "fields": {"member": 1, "product": 1, "room": 1, "timestamp": "2023-08-09T06:37:53.897Z", "price": 100}}, {"model": "stregsystem.sale", "pk": 32, "fields": {"member": 1, "product": 1, "room": 1, "timestamp": "2023-08-11T06:46:22.735Z", "price": 100}}, {"model": "stregsystem.sale", "pk": 33, "fields": {"member": 1, "product": 1, "room": 1, "timestamp": "2023-08-13T06:46:35.149Z", "price": 100}}, {"model": "stregsystem.sale", "pk": 34, "fields": {"member": 1, "product": 1, "room": 1, "timestamp": "2023-08-16T06:46:42.036Z", "price": 100}}, {"model": "stregsystem.sale", "pk": 35, "fields": {"member": 1, "product": 1, "room": 1, "timestamp": "2023-08-19T06:47:40.094Z", "price": 100}}, {"model": "stregsystem.sale", "pk": 36, "fields": {"member": 1, "product": 1, "room": 1, "timestamp": "2023-08-19T06:47:44.657Z", "price": 100}}, {"model": "stregsystem.sale", "pk": 37, "fields": {"member": 1, "product": 1, "room": 1, "timestamp": "2023-08-21T06:48:15.721Z", "price": 100}}, {"model": "stregsystem.sale", "pk": 38, "fields": {"member": 1, "product": 1, "room": 1, "timestamp": "2023-08-24T06:49:42.682Z", "price": 100}}, {"model": "stregsystem.sale", "pk": 39, "fields": {"member": 1, "product": 1, "room": 1, "timestamp": "2023-08-26T06:49:49.263Z", "price": 100}}, {"model": "stregsystem.sale", "pk": 40, "fields": {"member": 1, "product": 1, "room": 1, "timestamp": "2023-08-26T06:49:51.322Z", "price": 100}}, {"model": "stregsystem.sale", "pk": 41, "fields": {"member": 1, "product": 1, "room": 1, "timestamp": "2023-08-26T06:49:53.011Z", "price": 100}}, {"model": "stregsystem.sale", "pk": 42, "fields": {"member": 1, "product": 1, "room": 1, "timestamp": "2023-08-29T06:50:02.353Z", "price": 100}}, {"model": "stregsystem.sale", "pk": 43, "fields": {"member": 1, "product": 1, "room": 1, "timestamp": "2023-08-31T06:50:08.571Z", "price": 100}}, {"model": "stregsystem.sale", "pk": 44, "fields": {"member": 1, "product": 1, "room": 1, "timestamp": "2023-09-02T06:50:25.448Z", "price": 100}}, {"model": "stregsystem.sale", "pk": 45, "fields": {"member": 1, "product": 1, "room": 1, "timestamp": "2023-09-04T06:50:34.380Z", "price": 100}}, {"model": "stregsystem.sale", "pk": 46, "fields": {"member": 1, "product": 1, "room": 1, "timestamp": "2023-09-04T06:50:36.176Z", "price": 100}}, {"model": "stregsystem.sale", "pk": 47, "fields": {"member": 1, "product": 1, "room": 1, "timestamp": "2023-09-04T06:50:37.993Z", "price": 100}}, {"model": "stregsystem.news", "pk": 1, "fields": {"title": "Varm kaffe udg\u00e5r", "text": "Det er med stor forn\u00f8jelse, at vi annoncerer sortimentsudskiftningen af \"varm kaffe\" med vores nye og forbedrede produkt, \"kold kaffe\". Dette valg er truffet efter at have modtaget flere klager fra \u00e6rede fembers, der desv\u00e6rre har oplevet at br\u00e6nde sig p\u00e5 javaen.", "pub_date": "2023-08-07T21:46:04Z", "stop_date": "2043-08-07T11:00:00Z"}}, {"model": "contenttypes.contenttype", "pk": 39, "fields": {"app_label": "stregsystem", "model": "member"}}, {"model": "contenttypes.contenttype", "pk": 40, "fields": {"app_label": "stregsystem", "model": "payment"}}, {"model": "contenttypes.contenttype", "pk": 41, "fields": {"app_label": "stregsystem", "model": "mobilepayment"}}, {"model": "contenttypes.contenttype", "pk": 42, "fields": {"app_label": "stregsystem", "model": "category"}}, {"model": "contenttypes.contenttype", "pk": 43, "fields": {"app_label": "stregsystem", "model": "room"}}, {"model": "contenttypes.contenttype", "pk": 44, "fields": {"app_label": "stregsystem", "model": "product"}}, {"model": "contenttypes.contenttype", "pk": 45, "fields": {"app_label": "stregsystem", "model": "namedproduct"}}, {"model": "contenttypes.contenttype", "pk": 46, "fields": {"app_label": "stregsystem", "model": "oldprice"}}, {"model": "contenttypes.contenttype", "pk": 47, "fields": {"app_label": "stregsystem", "model": "sale"}}, {"model": "contenttypes.contenttype", "pk": 48, "fields": {"app_label": "stregsystem", "model": "news"}}, {"model": "contenttypes.contenttype", "pk": 49, "fields": {"app_label": "stregreport", "model": "breadrazzia"}}, {"model": "contenttypes.contenttype", "pk": 50, "fields": {"app_label": "stregreport", "model": "razziaentry"}}, {"model": "contenttypes.contenttype", "pk": 51, "fields": {"app_label": "kiosk", "model": "kioskitem"}}, {"model": "contenttypes.contenttype", "pk": 52, "fields": {"app_label": "admin", "model": "logentry"}}, {"model": "contenttypes.contenttype", "pk": 53, "fields": {"app_label": "auth", "model": "permission"}}, {"model": "contenttypes.contenttype", "pk": 54, "fields": {"app_label": "auth", "model": "group"}}, {"model": "contenttypes.contenttype", "pk": 55, "fields": {"app_label": "auth", "model": "user"}}, {"model": "contenttypes.contenttype", "pk": 56, "fields": {"app_label": "contenttypes", "model": "contenttype"}}, {"model": "contenttypes.contenttype", "pk": 57, "fields": {"app_label": "sessions", "model": "session"}}, {"model": "sessions.session", "pk": "et7wpr3rx6lsf4x9swuf4qd8ehn7r7l4", "fields": {"session_data": "NDllNWNlMTg2ZjQ3YjhhMjc3NThkZGIyYjJhODc0NWMxODdkNTFkNDp7Il9hdXRoX3VzZXJfaWQiOiIxIiwiX2F1dGhfdXNlcl9iYWNrZW5kIjoiZGphbmdvLmNvbnRyaWIuYXV0aC5iYWNrZW5kcy5Nb2RlbEJhY2tlbmQiLCJfYXV0aF91c2VyX2hhc2giOiI0OTZlYWE5OWRhY2JmYWViNzYwZDFjMmQ4YzBhZmI5NTg0ZDRhNGQ2In0=", "expire_date": "2023-09-20T06:56:51.561Z"}}, {"model": "auth.permission", "pk": 161, "fields": {"name": "Can add member", "content_type": 39, "codename": "add_member"}}, {"model": "auth.permission", "pk": 162, "fields": {"name": "Can change member", "content_type": 39, "codename": "change_member"}}, {"model": "auth.permission", "pk": 163, "fields": {"name": "Can delete member", "content_type": 39, "codename": "delete_member"}}, {"model": "auth.permission", "pk": 164, "fields": {"name": "Can view member", "content_type": 39, "codename": "view_member"}}, {"model": "auth.permission", "pk": 165, "fields": {"name": "Can add payment", "content_type": 40, "codename": "add_payment"}}, {"model": "auth.permission", "pk": 166, "fields": {"name": "Can change payment", "content_type": 40, "codename": "change_payment"}}, {"model": "auth.permission", "pk": 167, "fields": {"name": "Can delete payment", "content_type": 40, "codename": "delete_payment"}}, {"model": "auth.permission", "pk": 168, "fields": {"name": "Can view payment", "content_type": 40, "codename": "view_payment"}}, {"model": "auth.permission", "pk": 169, "fields": {"name": "Import batch payments", "content_type": 40, "codename": "import_batch_payments"}}, {"model": "auth.permission", "pk": 170, "fields": {"name": "Can add mobile payment", "content_type": 41, "codename": "add_mobilepayment"}}, {"model": "auth.permission", "pk": 171, "fields": {"name": "Can change mobile payment", "content_type": 41, "codename": "change_mobilepayment"}}, {"model": "auth.permission", "pk": 172, "fields": {"name": "Can delete mobile payment", "content_type": 41, "codename": "delete_mobilepayment"}}, {"model": "auth.permission", "pk": 173, "fields": {"name": "Can view mobile payment", "content_type": 41, "codename": "view_mobilepayment"}}, {"model": "auth.permission", "pk": 174, "fields": {"name": "MobilePaytool access", "content_type": 41, "codename": "mobilepaytool_access"}}, {"model": "auth.permission", "pk": 175, "fields": {"name": "Can add category", "content_type": 42, "codename": "add_category"}}, {"model": "auth.permission", "pk": 176, "fields": {"name": "Can change category", "content_type": 42, "codename": "change_category"}}, {"model": "auth.permission", "pk": 177, "fields": {"name": "Can delete category", "content_type": 42, "codename": "delete_category"}}, {"model": "auth.permission", "pk": 178, "fields": {"name": "Can view category", "content_type": 42, "codename": "view_category"}}, {"model": "auth.permission", "pk": 179, "fields": {"name": "Can add room", "content_type": 43, "codename": "add_room"}}, {"model": "auth.permission", "pk": 180, "fields": {"name": "Can change room", "content_type": 43, "codename": "change_room"}}, {"model": "auth.permission", "pk": 181, "fields": {"name": "Can delete room", "content_type": 43, "codename": "delete_room"}}, {"model": "auth.permission", "pk": 182, "fields": {"name": "Can view room", "content_type": 43, "codename": "view_room"}}, {"model": "auth.permission", "pk": 183, "fields": {"name": "Can add product", "content_type": 44, "codename": "add_product"}}, {"model": "auth.permission", "pk": 184, "fields": {"name": "Can change product", "content_type": 44, "codename": "change_product"}}, {"model": "auth.permission", "pk": 185, "fields": {"name": "Can delete product", "content_type": 44, "codename": "delete_product"}}, {"model": "auth.permission", "pk": 186, "fields": {"name": "Can view product", "content_type": 44, "codename": "view_product"}}, {"model": "auth.permission", "pk": 187, "fields": {"name": "Can add named product", "content_type": 45, "codename": "add_namedproduct"}}, {"model": "auth.permission", "pk": 188, "fields": {"name": "Can change named product", "content_type": 45, "codename": "change_namedproduct"}}, {"model": "auth.permission", "pk": 189, "fields": {"name": "Can delete named product", "content_type": 45, "codename": "delete_namedproduct"}}, {"model": "auth.permission", "pk": 190, "fields": {"name": "Can view named product", "content_type": 45, "codename": "view_namedproduct"}}, {"model": "auth.permission", "pk": 191, "fields": {"name": "Can add old price", "content_type": 46, "codename": "add_oldprice"}}, {"model": "auth.permission", "pk": 192, "fields": {"name": "Can change old price", "content_type": 46, "codename": "change_oldprice"}}, {"model": "auth.permission", "pk": 193, "fields": {"name": "Can delete old price", "content_type": 46, "codename": "delete_oldprice"}}, {"model": "auth.permission", "pk": 194, "fields": {"name": "Can view old price", "content_type": 46, "codename": "view_oldprice"}}, {"model": "auth.permission", "pk": 195, "fields": {"name": "Can add sale", "content_type": 47, "codename": "add_sale"}}, {"model": "auth.permission", "pk": 196, "fields": {"name": "Can change sale", "content_type": 47, "codename": "change_sale"}}, {"model": "auth.permission", "pk": 197, "fields": {"name": "Can delete sale", "content_type": 47, "codename": "delete_sale"}}, {"model": "auth.permission", "pk": 198, "fields": {"name": "Can view sale", "content_type": 47, "codename": "view_sale"}}, {"model": "auth.permission", "pk": 199, "fields": {"name": "Can access sales reports", "content_type": 47, "codename": "access_sales_reports"}}, {"model": "auth.permission", "pk": 200, "fields": {"name": "Can add news", "content_type": 48, "codename": "add_news"}}, {"model": "auth.permission", "pk": 201, "fields": {"name": "Can change news", "content_type": 48, "codename": "change_news"}}, {"model": "auth.permission", "pk": 202, "fields": {"name": "Can delete news", "content_type": 48, "codename": "delete_news"}}, {"model": "auth.permission", "pk": 203, "fields": {"name": "Can view news", "content_type": 48, "codename": "view_news"}}, {"model": "auth.permission", "pk": 204, "fields": {"name": "Can add bread razzia", "content_type": 49, "codename": "add_breadrazzia"}}, {"model": "auth.permission", "pk": 205, "fields": {"name": "Can change bread razzia", "content_type": 49, "codename": "change_breadrazzia"}}, {"model": "auth.permission", "pk": 206, "fields": {"name": "Can delete bread razzia", "content_type": 49, "codename": "delete_breadrazzia"}}, {"model": "auth.permission", "pk": 207, "fields": {"name": "Can view bread razzia", "content_type": 49, "codename": "view_breadrazzia"}}, {"model": "auth.permission", "pk": 208, "fields": {"name": "Can host a foobar or bread razzia", "content_type": 49, "codename": "host_razzia"}}, {"model": "auth.permission", "pk": 209, "fields": {"name": "Can add razzia entry", "content_type": 50, "codename": "add_razziaentry"}}, {"model": "auth.permission", "pk": 210, "fields": {"name": "Can change razzia entry", "content_type": 50, "codename": "change_razziaentry"}}, {"model": "auth.permission", "pk": 211, "fields": {"name": "Can delete razzia entry", "content_type": 50, "codename": "delete_razziaentry"}}, {"model": "auth.permission", "pk": 212, "fields": {"name": "Can view razzia entry", "content_type": 50, "codename": "view_razziaentry"}}, {"model": "auth.permission", "pk": 213, "fields": {"name": "Can add kiosk item", "content_type": 51, "codename": "add_kioskitem"}}, {"model": "auth.permission", "pk": 214, "fields": {"name": "Can change kiosk item", "content_type": 51, "codename": "change_kioskitem"}}, {"model": "auth.permission", "pk": 215, "fields": {"name": "Can delete kiosk item", "content_type": 51, "codename": "delete_kioskitem"}}, {"model": "auth.permission", "pk": 216, "fields": {"name": "Can view kiosk item", "content_type": 51, "codename": "view_kioskitem"}}, {"model": "auth.permission", "pk": 217, "fields": {"name": "Can add log entry", "content_type": 52, "codename": "add_logentry"}}, {"model": "auth.permission", "pk": 218, "fields": {"name": "Can change log entry", "content_type": 52, "codename": "change_logentry"}}, {"model": "auth.permission", "pk": 219, "fields": {"name": "Can delete log entry", "content_type": 52, "codename": "delete_logentry"}}, {"model": "auth.permission", "pk": 220, "fields": {"name": "Can view log entry", "content_type": 52, "codename": "view_logentry"}}, {"model": "auth.permission", "pk": 221, "fields": {"name": "Can add permission", "content_type": 53, "codename": "add_permission"}}, {"model": "auth.permission", "pk": 222, "fields": {"name": "Can change permission", "content_type": 53, "codename": "change_permission"}}, {"model": "auth.permission", "pk": 223, "fields": {"name": "Can delete permission", "content_type": 53, "codename": "delete_permission"}}, {"model": "auth.permission", "pk": 224, "fields": {"name": "Can view permission", "content_type": 53, "codename": "view_permission"}}, {"model": "auth.permission", "pk": 225, "fields": {"name": "Can add group", "content_type": 54, "codename": "add_group"}}, {"model": "auth.permission", "pk": 226, "fields": {"name": "Can change group", "content_type": 54, "codename": "change_group"}}, {"model": "auth.permission", "pk": 227, "fields": {"name": "Can delete group", "content_type": 54, "codename": "delete_group"}}, {"model": "auth.permission", "pk": 228, "fields": {"name": "Can view group", "content_type": 54, "codename": "view_group"}}, {"model": "auth.permission", "pk": 229, "fields": {"name": "Can add user", "content_type": 55, "codename": "add_user"}}, {"model": "auth.permission", "pk": 230, "fields": {"name": "Can change user", "content_type": 55, "codename": "change_user"}}, {"model": "auth.permission", "pk": 231, "fields": {"name": "Can delete user", "content_type": 55, "codename": "delete_user"}}, {"model": "auth.permission", "pk": 232, "fields": {"name": "Can view user", "content_type": 55, "codename": "view_user"}}, {"model": "auth.permission", "pk": 233, "fields": {"name": "Can add content type", "content_type": 56, "codename": "add_contenttype"}}, {"model": "auth.permission", "pk": 234, "fields": {"name": "Can change content type", "content_type": 56, "codename": "change_contenttype"}}, {"model": "auth.permission", "pk": 235, "fields": {"name": "Can delete content type", "content_type": 56, "codename": "delete_contenttype"}}, {"model": "auth.permission", "pk": 236, "fields": {"name": "Can view content type", "content_type": 56, "codename": "view_contenttype"}}, {"model": "auth.permission", "pk": 237, "fields": {"name": "Can add session", "content_type": 57, "codename": "add_session"}}, {"model": "auth.permission", "pk": 238, "fields": {"name": "Can change session", "content_type": 57, "codename": "change_session"}}, {"model": "auth.permission", "pk": 239, "fields": {"name": "Can delete session", "content_type": 57, "codename": "delete_session"}}, {"model": "auth.permission", "pk": 240, "fields": {"name": "Can view session", "content_type": 57, "codename": "view_session"}}, {"model": "auth.user", "pk": 1, "fields": {"password": "pbkdf2_sha256$150000$fUAN4hBdKvOp$+1qkLByq1ae/ybzfplIUZnLQvQzHh2NrYLHvzg028Hc=", "last_login": "2023-09-06T06:56:51.478Z", "is_superuser": true, "username": "tester", "first_name": "test", "last_name": "testsen", "email": "", "is_staff": true, "is_active": true, "date_joined": "2017-03-06T10:53:54Z", "groups": [], "user_permissions": []}}, {"model": "auth.user", "pk": 2, "fields": {"password": "pbkdf2_sha256$150000$iTYq6tgl0wkZ$sayKG1202U4C7vm6zdtogsSP+OxVoQZtoxtO1cXFWdg=", "last_login": null, "is_superuser": true, "username": "autopayment", "first_name": "auto", "last_name": "payment", "email": "", "is_staff": true, "is_active": true, "date_joined": "2021-09-13T13:43:22Z", "groups": [], "user_permissions": []}}, {"model": "admin.logentry", "pk": 22, "fields": {"action_time": "2023-09-06T06:57:13.120Z", "user": 1, "content_type": 42, "object_id": "12", "object_repr": "beer", "action_flag": 1, "change_message": "[{\"added\": {}}]"}}, {"model": "admin.logentry", "pk": 23, "fields": {"action_time": "2023-09-06T06:57:19.110Z", "user": 1, "content_type": 42, "object_id": "13", "object_repr": "soda", "action_flag": 1, "change_message": "[{\"added\": {}}]"}}, {"model": "admin.logentry", "pk": 24, "fields": {"action_time": "2023-09-06T06:57:26.413Z", "user": 1, "content_type": 42, "object_id": "14", "object_repr": "energy", "action_flag": 1, "change_message": "[{\"added\": {}}]"}}, {"model": "admin.logentry", "pk": 25, "fields": {"action_time": "2023-09-06T06:59:00.864Z", "user": 1, "content_type": 44, "object_id": "13", "object_repr": "+ Sportscola (3.00)", "action_flag": 2, "change_message": "[{\"changed\": {\"fields\": [\"name\", \"categories\"]}}]"}}, {"model": "admin.logentry", "pk": 26, "fields": {"action_time": "2023-09-06T06:59:12.297Z", "user": 1, "content_type": 44, "object_id": "1", "object_repr": "+ Test \u00d8L (1.00)", "action_flag": 2, "change_message": "[{\"changed\": {\"fields\": [\"categories\"]}}]"}}, {"model": "admin.logentry", "pk": 27, "fields": {"action_time": "2023-09-06T06:59:28.472Z", "user": 1, "content_type": 44, "object_id": "2", "object_repr": "+ Kold Kaffe (2.00)", "action_flag": 2, "change_message": "[{\"changed\": {\"fields\": [\"categories\"]}}]"}}, {"model": "admin.logentry", "pk": 28, "fields": {"action_time": "2023-09-06T06:59:50.422Z", "user": 1, "content_type": 44, "object_id": "3", "object_repr": "+ Fad\u00f8l (15.00)", "action_flag": 2, "change_message": "[{\"changed\": {\"fields\": [\"categories\"]}}]"}}] \ No newline at end of file From d8f05767336cf6575060110a95776960ccb839eb Mon Sep 17 00:00:00 2001 From: Kresten Laust Date: Sat, 9 Sep 2023 16:43:56 +0200 Subject: [PATCH 19/61] Made general-view the primary view, and improved template code. Removed redundant dependency in views.py --- stregsystem/purchase_heatmap.py | 12 +++++-- .../stregsystem/purchase_heatmap.html | 36 ++++++++++--------- stregsystem/views.py | 2 -- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/stregsystem/purchase_heatmap.py b/stregsystem/purchase_heatmap.py index 95bb0313..4de1e739 100644 --- a/stregsystem/purchase_heatmap.py +++ b/stregsystem/purchase_heatmap.py @@ -115,10 +115,16 @@ def __get_purchase_heatmap_data( days = [] for day_index in range(len(products_by_day)): - category_day_color = __get_heatmap_day_color_categories(products_by_day[day_index], products_by_category, max_day_items) general_day_color = __get_heatmap_day_color_general(products_by_day[day_index], max_day_items) + category_day_color = __get_heatmap_day_color_categories( + products_by_day[day_index], products_by_category, max_day_items + ) days.append( - HeatmapDay(dates_by_day[day_index], (category_day_color, general_day_color), [product.id for product in products_by_day[day_index]]) + HeatmapDay( + dates_by_day[day_index], + (general_day_color, category_day_color), + [product.id for product in products_by_day[day_index]], + ) ) return days @@ -132,7 +138,7 @@ def __organize_purchase_heatmap_data(heatmap_data: list, start_date: datetime.da # [<>, <>, ...], # ...] - # TODO: Doesn't take current weekday into consideration. + # TODO: Doesn't take current weekday into consideration. But still works? new_list = [] for i in range(7): new_list.append([]) diff --git a/stregsystem/templates/stregsystem/purchase_heatmap.html b/stregsystem/templates/stregsystem/purchase_heatmap.html index 7cd7fefa..8735f36e 100644 --- a/stregsystem/templates/stregsystem/purchase_heatmap.html +++ b/stregsystem/templates/stregsystem/purchase_heatmap.html @@ -13,7 +13,7 @@ {{weekday_label}} {% for day_data in weekday_data %} - + {% endfor %} @@ -22,15 +22,17 @@
- -
-
=ØL. -
=Energi. -
=Soda. +
+
=få køb. +
=mange køb.
-
@@ -82,20 +84,20 @@ \ No newline at end of file diff --git a/stregsystem/views.py b/stregsystem/views.py index 13eef209..cc1c1ae3 100644 --- a/stregsystem/views.py +++ b/stregsystem/views.py @@ -53,8 +53,6 @@ import json -from .purchase_heatmap import __get_purchase_heatmap_data - def __get_news(): try: From d6e9593d4c95a2188b1365fc4034891a880dcedd Mon Sep 17 00:00:00 2001 From: Kresten Laust Date: Sat, 9 Sep 2023 17:20:39 +0200 Subject: [PATCH 20/61] Inverted brightness, to make darker colors show more activity. Offset general by 1 to avoid black squares. --- stregsystem/purchase_heatmap.py | 4 ++-- stregsystem/templates/stregsystem/purchase_heatmap.html | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/stregsystem/purchase_heatmap.py b/stregsystem/purchase_heatmap.py index 4de1e739..5e1f522b 100644 --- a/stregsystem/purchase_heatmap.py +++ b/stregsystem/purchase_heatmap.py @@ -49,14 +49,14 @@ def __get_heatmap_day_color_categories( if total_category_sum == 0: return 235, 237, 240 # Grey - return tuple(category_sum / total_category_sum * 255 * brightness for category_sum in category_representation) + return tuple(255 - (category_sum / total_category_sum * 255 * brightness) for category_sum in category_representation) def __get_heatmap_day_color_general(products: List[Product], max_items_day: int) -> (int, int, int): if len(products) == 0: return 235, 237, 240 # Grey - return 0, int(255 * (len(products) / max_items_day)), 0 + return 0, int(255 - (255 * (len(products) / (max_items_day + 1)))), 0 def __get_purchase_heatmap_data( diff --git a/stregsystem/templates/stregsystem/purchase_heatmap.html b/stregsystem/templates/stregsystem/purchase_heatmap.html index 8735f36e..33006287 100644 --- a/stregsystem/templates/stregsystem/purchase_heatmap.html +++ b/stregsystem/templates/stregsystem/purchase_heatmap.html @@ -23,8 +23,8 @@
-
=få køb. -
=mange køb. +
=få køb. +
=mange køb.
@@ -89,7 +89,7 @@ \ No newline at end of file diff --git a/stregsystem/views.py b/stregsystem/views.py index dc6c2224..b3f85835 100644 --- a/stregsystem/views.py +++ b/stregsystem/views.py @@ -53,7 +53,7 @@ import json -from .purchase_heatmap import ItemCountHeatmapColorMode, ColorCategorizedHeatmapColorMode, get_purchase_data_for_heatmap +from .purchase_heatmap import ItemCountHeatmapColorMode, ColorCategorizedHeatmapColorMode def __get_news(): @@ -215,14 +215,25 @@ def usermenu(request, room, member, bought, from_sale=False): # Heatmap - begin __weeks_to_display = 10 - __raw_heatmap_data, max_items_day = get_purchase_data_for_heatmap( - member, datetime.today(), __weeks_to_display + __raw_heatmap_data = purchase_heatmap.get_purchase_data_ordered_by_date( + member, datetime.datetime.today(), __weeks_to_display ) + + __max_items_bought = purchase_heatmap.get_max_product_count(__raw_heatmap_data) + __products_in_color_categories = tuple( + ColorCategorizedHeatmapColorMode.get_category_objects(category_name) + for category_name in ("beer", "energy", "soda") + ) + heatmap_modes = [ - ItemCountHeatmapColorMode(), - ColorCategorizedHeatmapColorMode() # ("beer", "energy", "soda") + ItemCountHeatmapColorMode(__max_items_bought), + ColorCategorizedHeatmapColorMode(__max_items_bought, __products_in_color_categories), ] - column_labels, rows = purchase_heatmap.get_heatmap_graph_data(member) + __reorganized_heatmap_data = purchase_heatmap.convert_purchase_data_to_heatmap_day( + __raw_heatmap_data, heatmap_modes + ) + + column_labels, rows = purchase_heatmap.get_heatmap_graph_data(__weeks_to_display, __reorganized_heatmap_data) # Heatmap - end if member.has_stregforbud(): From d59af25d3ece64a21decfd0523a9eabfd4afd7c5 Mon Sep 17 00:00:00 2001 From: Kresten Laust Date: Sat, 16 Sep 2023 00:14:12 +0200 Subject: [PATCH 24/61] Proposed solution for displaying purchased products --- .../stregsystem/purchase_heatmap.html | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/stregsystem/templates/stregsystem/purchase_heatmap.html b/stregsystem/templates/stregsystem/purchase_heatmap.html index 3db8125f..f5515d95 100644 --- a/stregsystem/templates/stregsystem/purchase_heatmap.html +++ b/stregsystem/templates/stregsystem/purchase_heatmap.html @@ -13,7 +13,7 @@ {{weekday_label}} {% for day_data in weekday_data %} - @@ -109,4 +109,30 @@ document.getElementById(`heatmap-table-mode-description-${heatmap_mode_names[index]}`).style.display = 'inline-flex'; current_heatmap_mode = index; } + + function updateProductCounts(products, clear=false){ + const product_counts = {}; + + for (const num of products){ + product_counts[num] = product_counts[num] ? product_counts[num] + 1 : 1; + } + + for (const [product_id, count] of Object.entries(product_counts)){ + changeProductCount(product_id, clear ? 0 : count); + } + } + + function changeProductCount(product_id, count){ + let productForm = document.querySelector(`input[name='product_id'][value='${product_id}']`).parentElement; + + var productCountElement = productForm.getElementsByClassName("product-count")[0]; + if (productCountElement === undefined){ + productCountElement = document.createElement("a"); + productCountElement.setAttribute("class", "product-count"); + productForm.appendChild(productCountElement); + } + + productCountElement.style.display = count === 0 ? 'none' : 'inline'; + productCountElement.innerText = `${count}` + } \ No newline at end of file From 2c80d85c68fafc95648da5814eeef63eb8b65639 Mon Sep 17 00:00:00 2001 From: Kresten Laust Date: Sat, 16 Sep 2023 14:32:20 +0200 Subject: [PATCH 25/61] Removed iter from query loop --- stregsystem/purchase_heatmap.py | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/stregsystem/purchase_heatmap.py b/stregsystem/purchase_heatmap.py index 728d50cc..7ace0e87 100644 --- a/stregsystem/purchase_heatmap.py +++ b/stregsystem/purchase_heatmap.py @@ -134,33 +134,21 @@ def get_purchase_data_ordered_by_date( days_to_go_back = (7 * weeks_to_display) - (6 - end_date.weekday() - 1) cutoff_date = end_date.date() - timedelta(days=days_to_go_back) - last_sale_list = iter( + last_sale_list = list( member.sale_set.filter(timestamp__gte=cutoff_date, timestamp__lte=end_date).order_by('-timestamp') ) products_by_day = [] dates_by_day = [] - try: - next_sale = next(last_sale_list) - next_sale_date = next_sale.timestamp.date() - except StopIteration: - next_sale = None - next_sale_date = None - + sale_index = 0 for single_date in (end_date - timedelta(days=n) for n in range(days_to_go_back)): products_by_day.append([]) dates_by_day.append(single_date.date()) - try: - while next_sale_date == single_date.date(): - products_by_day[-1].append(next_sale.product) - - next_sale = next(last_sale_list) - next_sale_date = next_sale.timestamp.date() - except StopIteration: - next_sale = None - next_sale_date = None + while last_sale_list[sale_index].timestamp.date() == single_date.date(): + products_by_day[-1].append(last_sale_list[sale_index].product) + sale_index += 1 return list(zip(dates_by_day, products_by_day)) From 25e3beb5ff440e9cb60c25d3d11938f4dcbe0ce7 Mon Sep 17 00:00:00 2001 From: Kresten Laust Date: Sat, 16 Sep 2023 14:59:34 +0200 Subject: [PATCH 26/61] Selects related product field immediately instead of making duplicate queries --- stregsystem/purchase_heatmap.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/stregsystem/purchase_heatmap.py b/stregsystem/purchase_heatmap.py index 7ace0e87..85f2ca3d 100644 --- a/stregsystem/purchase_heatmap.py +++ b/stregsystem/purchase_heatmap.py @@ -135,7 +135,9 @@ def get_purchase_data_ordered_by_date( cutoff_date = end_date.date() - timedelta(days=days_to_go_back) last_sale_list = list( - member.sale_set.filter(timestamp__gte=cutoff_date, timestamp__lte=end_date).order_by('-timestamp') + member.sale_set.filter(timestamp__gte=cutoff_date, timestamp__lte=end_date) + .select_related('product') + .order_by('-timestamp') ) products_by_day = [] From 2f5221b9439af7a0d0a7d7b9f180af0043d258ac Mon Sep 17 00:00:00 2001 From: Kresten Laust Date: Sat, 16 Sep 2023 17:38:04 +0200 Subject: [PATCH 27/61] Improved appearance --- .../stregsystem/purchase_heatmap.html | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/stregsystem/templates/stregsystem/purchase_heatmap.html b/stregsystem/templates/stregsystem/purchase_heatmap.html index f5515d95..cb266133 100644 --- a/stregsystem/templates/stregsystem/purchase_heatmap.html +++ b/stregsystem/templates/stregsystem/purchase_heatmap.html @@ -1,4 +1,7 @@
+
+

Forbrugsoversigt (Antal)

+
@@ -22,7 +25,7 @@ {% endfor %}
-
+