diff --git a/README.md b/README.md index fec5aab3..83c8f791 100644 --- a/README.md +++ b/README.md @@ -12,16 +12,13 @@ Cooking website. - `python manage.py migrate` - `python manage.py runserver` -### Docker - -- Install Docker and Docker Compose -- `docker-compose build` -- `docker-compose up` -- `docker-compose exec app python manage.py migrate` +The app can also be built and run with Docker. ## Development commands * Lint code: `flake8` +* Blacken the code `black .` +* Sort the imports with `isort --ac .` * Run unit tests: `python manage.py test` * Create superuser: `python manage.py createsuperuser` * Coverage: `coverage run manage.py test` diff --git a/assets/templates/base.html b/assets/templates/base.html index 7c1022e7..adf03181 100644 --- a/assets/templates/base.html +++ b/assets/templates/base.html @@ -97,6 +97,11 @@ ! {% endif %} +{# #} +{# #} +{# Statistics New#} +{# #} +{# #} {# #} {# News and updates#} {# {% if user.requires_information_updates %}#} diff --git a/assets/templates/dining_lists/statistics.html b/assets/templates/dining_lists/statistics.html new file mode 100644 index 00000000..768e28ec --- /dev/null +++ b/assets/templates/dining_lists/statistics.html @@ -0,0 +1,77 @@ +{% extends "base.html" %} + +{% block content %} +

Date range

+

+ Currently set: + {{ range_from|date:"SHORT_DATE_FORMAT" }} – {{ range_to|date:"SHORT_DATE_FORMAT" }} +

+ +
+ + + + + +
+ +
+ « Previous + + + + Next » + + + Reset + + +

Dining counts

+ + + + + + + + + + + {% for association, queryset in per_association.items %} + + + + + + + {% endfor %} + + + + + + + +
AssociationListsIndividual diners*Total diner count**
{{ association.name }}{{ queryset.lists.count }}{{ queryset.users.count }}{{ queryset.entries.count }}
Total{{ lists.count }}{{ users.count }}{{ entries.count }}
+ +

+ *Individual diners: the number of individuals from each association who joined + a dining list in the given period. + When the same individual joined multiple lists this is counted as 1. + An individual is counted when + they joined any dining list, not necessarily a list of their own association. + Some individuals are member of multiple associations, in which case they are counted once for each + association. Therefore, the sum of all associations can be more than the total number of individuals. + Unverified members and guest diners are not counted. +

+ +

+ **Total diner count: the sum of all diners for the lists that are owned + by the association, + in the given period. +

+ + +{% endblock %} diff --git a/dining/urls.py b/dining/urls.py index c12cced2..f9531688 100644 --- a/dining/urls.py +++ b/dining/urls.py @@ -1,16 +1,15 @@ from django.urls import include, path -from . import views -from .views import DayView, index +from dining import views urlpatterns = [ - path("", index, name="index"), + path("", views.index, name="index"), path("csv/", views.DailyDinersCSVView.as_view(), name="diners_csv"), path( "///", include( [ - path("", DayView.as_view(), name="day_view"), + path("", views.DayView.as_view(), name="day_view"), path("add/", views.NewSlotView.as_view(), name="new_slot"), path( "/", @@ -54,4 +53,5 @@ path( "entries//delete/", views.EntryDeleteView.as_view(), name="entry_delete" ), + path("statistics/", views.StatisticsView.as_view(), name="statistics"), ] diff --git a/dining/views.py b/dining/views.py index 529c1e0f..06e43577 100644 --- a/dining/views.py +++ b/dining/views.py @@ -642,3 +642,62 @@ def form_valid(self, form): else: messages.success(self.request, "Diners have been informed") return super().form_valid(form) + + +class StatisticsView(LoginRequiredMixin, TemplateView): + template_name = "dining_lists/statistics.html" + + def get_range(self): + """Returns the user-defined date range or a default range.""" + today = timezone.now().date() + # Default boundary is August 1st. We could also do September 1st but then we + # might miss some early year dining lists. + range_from = date(today.year - 1 if today.month < 8 else today.year, 8, 1) + range_to = range_from.replace(year=range_from.year + 1) + try: + range_from = date.fromisoformat(self.request.GET.get("from") or "") + except ValueError: + pass + try: + range_to = date.fromisoformat(self.request.GET.get("to") or "") + except ValueError: + pass + return range_from, range_to + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + range_from, range_to = self.get_range() + + # Dining lists in the period + lists = DiningList.objects.filter(date__gte=range_from, date__lt=range_to) + # Users who dined in the given period (excludes external entries) + users = User.objects.filter( + diningentry__dining_list__in=lists, diningentry__external_name="" + ).distinct() + # Entries in given period + entries = DiningEntry.objects.filter(dining_list__in=lists) + # Filter on association + per_association = { + a: { + "users": users.filter( + usermembership__association=a, usermembership__is_verified=True + ), + "lists": lists.filter(association=a), + "entries": entries.filter(dining_list__association=a), + } + for a in Association.objects.order_by("name") + } + + context.update( + { + "range_from": range_from, + "range_to": range_to, + "lists": lists, + "users": users, + "entries": entries, + "per_association": per_association, + "next": range_to + (range_to - range_from), + "prev": range_from - (range_to - range_from), + } + ) + return context diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 9aee677f..00000000 --- a/docker-compose.yml +++ /dev/null @@ -1,33 +0,0 @@ -version: '3.7' - -# Docker configuration for development -# -# If you need local adjustments, use a Docker compose override file. - -services: - app: - build: . - command: python manage.py runserver 0.0.0.0:8000 - environment: - DINING_DEBUG: 'true' - DINING_SECRET_KEY: 'hello' - DINING_DATABASE_URL: postgres://dining:dining@db/dining - volumes: - - .:/app/src - ports: - - "8000:8000" - depends_on: - - db - db: - image: postgres:12 - environment: - POSTGRES_USER: dining - POSTGRES_PASSWORD: dining - POSTGRES_DB: dining - volumes: - - db:/var/lib/postgresql/data - ports: - - "5432:5432" - -volumes: - db: