11 Commits

Author SHA1 Message Date
555608d8c6 Fix syntax
All checks were successful
Django CI/CD / build-and-push (push) Successful in 1m14s
2023-11-18 09:52:17 +01:00
a7293c659d CI: Ignore README.md 2023-11-18 09:33:31 +01:00
f36e692361 Do not run for pull requests 2023-11-18 09:33:10 +01:00
fe97f540a0 Fix CI being blocked 2023-11-18 09:32:41 +01:00
c35b539c42 Merge sessions and notes
All checks were successful
Django CI/CD / build-and-push (push) Successful in 1m9s
2023-11-17 21:20:33 +01:00
bbe5e072b2 Don't display prices if zero 2023-11-17 21:10:56 +01:00
6fc2f623dc Apply djlint 2023-11-17 21:06:57 +01:00
9481bd5fef Add pre-commit
All checks were successful
Django CI/CD / build-and-push (push) Successful in 1m33s
2023-11-17 09:34:51 +01:00
4083165123 Use the black profile for isort 2023-11-17 09:15:18 +01:00
45bb2681c7 Use isort on migrations 2023-11-17 09:15:06 +01:00
dbb8ec3f9a Handle empty edition_id 2023-11-17 09:14:25 +01:00
30 changed files with 518 additions and 427 deletions

View File

@ -3,12 +3,10 @@ name: Django CI/CD
on: on:
push: push:
branches: [ main ] branches: [ main ]
pull_request: paths-ignore: [ 'README.md' ]
branches: [ main ]
jobs: jobs:
build-and-push: build-and-push:
needs: test
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout

10
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,10 @@
repos:
- repo: https://github.com/psf/black
rev: 22.12.0
hooks:
- id: black
- repo: https://github.com/pycqa/isort
rev: 5.12.0
hooks:
- id: isort
name: isort (python)

View File

@ -1,7 +1,7 @@
# Generated by Django 4.1.5 on 2023-01-19 18:30 # Generated by Django 4.1.5 on 2023-01-19 18:30
from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -1,7 +1,7 @@
# Generated by Django 4.1.5 on 2023-02-18 16:29 # Generated by Django 4.1.5 on 2023-02-18 16:29
from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -1,7 +1,7 @@
# Generated by Django 4.1.5 on 2023-02-18 19:06 # Generated by Django 4.1.5 on 2023-02-18 19:06
from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -1,7 +1,7 @@
# Generated by Django 4.1.5 on 2023-02-18 19:59 # Generated by Django 4.1.5 on 2023-02-18 19:59
from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -1,7 +1,7 @@
# Generated by Django 4.1.5 on 2023-11-06 11:10 # Generated by Django 4.1.5 on 2023-11-06 11:10
from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -1,7 +1,7 @@
# Generated by Django 4.1.5 on 2023-11-06 18:14 # Generated by Django 4.1.5 on 2023-11-06 18:14
from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models
def rename_duplicates(apps, schema_editor): def rename_duplicates(apps, schema_editor):

View File

@ -1,7 +1,7 @@
# Generated by Django 4.1.5 on 2023-11-06 16:53 # Generated by Django 4.1.5 on 2023-11-06 16:53
from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -1,7 +1,7 @@
# Generated by Django 4.1.5 on 2023-11-14 08:41 # Generated by Django 4.1.5 on 2023-11-14 08:41
from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -1,6 +1,7 @@
# Generated by Django 4.1.5 on 2023-11-14 11:05 # Generated by Django 4.1.5 on 2023-11-14 11:05
from django.db import migrations, models from django.db import migrations, models
from games.models import Purchase from games.models import Purchase

View File

@ -1,7 +1,7 @@
# Generated by Django 4.1.5 on 2023-11-14 21:19 # Generated by Django 4.1.5 on 2023-11-14 21:19
from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -1,7 +1,7 @@
# Generated by Django 4.1.5 on 2023-11-15 13:51 # Generated by Django 4.1.5 on 2023-11-15 13:51
from django.db import migrations, models
import django.utils.timezone import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -808,6 +808,10 @@ select {
margin-bottom: 2.5rem; margin-bottom: 2.5rem;
} }
.mb-2 {
margin-bottom: 0.5rem;
}
.mb-4 { .mb-4 {
margin-bottom: 1rem; margin-bottom: 1rem;
} }
@ -1405,6 +1409,11 @@ th label {
background-color: rgb(17 24 39 / var(--tw-bg-opacity)); background-color: rgb(17 24 39 / var(--tw-bg-opacity));
} }
:is(.dark .dark\:text-slate-400) {
--tw-text-opacity: 1;
color: rgb(148 163 184 / var(--tw-text-opacity));
}
:is(.dark .dark\:text-slate-500) { :is(.dark .dark\:text-slate-500) {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgb(100 116 139 / var(--tw-text-opacity)); color: rgb(100 116 139 / var(--tw-text-opacity));

View File

@ -1,25 +1,24 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load static %} {% load static %}
{% block title %}
{% block title %}{{ title }}{% endblock title %} {{ title }}
{% endblock title %}
{% block content %} {% block content %}
<form method="post" enctype="multipart/form-data"> <form method="post" enctype="multipart/form-data">
<table class="mx-auto"> <table class="mx-auto">
{% csrf_token %} {% csrf_token %}
{{ form.as_table }}
{{ form.as_table }} <tr>
<tr> <td></td>
<td></td> <td>
<td><input type="submit" value="Submit"/></td> <input type="submit" value="Submit" />
</tr> </td>
</tr>
</table> </table>
</form> </form>
{% endblock content %} {% endblock content %}
{% block scripts %} {% block scripts %}
{% if script_name %} {% if script_name %}
<script type="module" src="{% static 'js/'|add:script_name %}"></script> <script type="module" src="{% static 'js/'|add:script_name %}"></script>
{% endif %} {% endif %}
{% endblock scripts %} {% endblock scripts %}

View File

@ -1,29 +1,32 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load static %} {% load static %}
{% block title %}
{% block title %}{{ title }}{% endblock title %} {{ title }}
{% endblock title %}
{% block content %} {% block content %}
<form method="post" enctype="multipart/form-data"> <form method="post" enctype="multipart/form-data">
<table class="mx-auto"> <table class="mx-auto">
{% csrf_token %} {% csrf_token %}
{{ form.as_table }}
{{ form.as_table }} <tr>
<tr> <td></td>
<td></td> <td>
<td><input type="submit" name="submit" value="Submit"/></td> <input type="submit" name="submit" value="Submit" />
</tr> </td>
<tr> </tr>
<td></td> <tr>
<td><input type="submit" name="submit_and_redirect" value="Submit & Create Purchase"/></td> <td></td>
</tr> <td>
<input type="submit"
name="submit_and_redirect"
value="Submit & Create Purchase" />
</td>
</tr>
</table> </table>
</form> </form>
{% endblock content %} {% endblock content %}
{% block scripts %} {% block scripts %}
{% if script_name %} {% if script_name %}
<script type="module" src="{% static 'js/'|add:script_name %}"></script> <script type="module" src="{% static 'js/'|add:script_name %}"></script>
{% endif %} {% endif %}
{% endblock scripts %} {% endblock scripts %}

View File

@ -1,29 +1,32 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load static %} {% load static %}
{% block title %}
{% block title %}{{ title }}{% endblock title %} {{ title }}
{% endblock title %}
{% block content %} {% block content %}
<form method="post" enctype="multipart/form-data"> <form method="post" enctype="multipart/form-data">
<table class="mx-auto"> <table class="mx-auto">
{% csrf_token %} {% csrf_token %}
{{ form.as_table }}
{{ form.as_table }} <tr>
<tr> <td></td>
<td></td> <td>
<td><input type="submit" name="submit" value="Submit"/></td> <input type="submit" name="submit" value="Submit" />
</tr> </td>
<tr> </tr>
<td></td> <tr>
<td><input type="submit" name="submit_and_redirect" value="Submit & Create Edition"/></td> <td></td>
</tr> <td>
<input type="submit"
name="submit_and_redirect"
value="Submit & Create Edition" />
</td>
</tr>
</table> </table>
</form> </form>
{% endblock content %} {% endblock content %}
{% block scripts %} {% block scripts %}
{% if script_name %} {% if script_name %}
<script type="module" src="{% static 'js/'|add:script_name %}"></script> <script type="module" src="{% static 'js/'|add:script_name %}"></script>
{% endif %} {% endif %}
{% endblock scripts %} {% endblock scripts %}

View File

@ -1,29 +1,32 @@
{% extends "base.html" %} {% extends "base.html" %}
{% load static %} {% load static %}
{% block title %}
{% block title %}{{ title }}{% endblock title %} {{ title }}
{% endblock title %}
{% block content %} {% block content %}
<form method="post" enctype="multipart/form-data"> <form method="post" enctype="multipart/form-data">
<table class="mx-auto"> <table class="mx-auto">
{% csrf_token %} {% csrf_token %}
{{ form.as_table }}
{{ form.as_table }} <tr>
<tr> <td></td>
<td></td> <td>
<td><input type="submit" name="submit" value="Submit"/></td> <input type="submit" name="submit" value="Submit" />
</tr> </td>
<tr> </tr>
<td></td> <tr>
<td><input type="submit" name="submit_and_redirect" value="Submit & Create Session"/></td> <td></td>
</tr> <td>
<input type="submit"
name="submit_and_redirect"
value="Submit & Create Session" />
</td>
</tr>
</table> </table>
</form> </form>
{% endblock content %} {% endblock content %}
{% block scripts %} {% block scripts %}
{% if script_name %} {% if script_name %}
<script type="module" src="{% static 'js/'|add:script_name %}"></script> <script type="module" src="{% static 'js/'|add:script_name %}"></script>
{% endif %} {% endif %}
{% endblock scripts %} {% endblock scripts %}

View File

@ -1,35 +1,38 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}
{% block title %}{{ title }}{% endblock title %} {{ title }}
{% endblock title %}
{% block content %} {% block content %}
<form method="post" enctype="multipart/form-data"> <form method="post" enctype="multipart/form-data">
<table class="mx-auto"> <table class="mx-auto">
{% csrf_token %} {% csrf_token %}
{% for field in form %}
{% for field in form %} <tr>
<tr> <th>{{ field.label_tag }}</th>
<th>{{ field.label_tag }}</th> {% if field.name == "note" %}
{% if field.name == "note" %} <td>{{ field }}</td>
<td>{{ field }}</td> {% else %}
{% else %} <td>{{ field }}</td>
<td>{{ field }}</td> {% endif %}
{% endif %} {% if field.name == "timestamp_start" or field.name == "timestamp_end" %}
{% if field.name == "timestamp_start" or field.name == "timestamp_end" %} <td>
<td> <div class="basic-button-container">
<div class="basic-button-container"> <button class="basic-button" data-target="{{ field.name }}" data-type="now">Set to now</button>
<button class="basic-button" data-target="{{field.name}}" data-type="now">Set to now</button> <button class="basic-button"
<button class="basic-button" data-target="{{field.name}}" data-type="toggle">Toggle text</button> data-target="{{ field.name }}"
<button class="basic-button" data-target="{{field.name}}" data-type="copy">Copy</button> data-type="toggle">Toggle text</button>
</div> <button class="basic-button" data-target="{{ field.name }}" data-type="copy">Copy</button>
</td> </div>
{% endif %} </td>
</tr> {% endif %}
{% endfor %} </tr>
<tr> {% endfor %}
<td></td> <tr>
<td><input type="submit" value="Submit"/></td> <td></td>
</tr> <td>
<input type="submit" value="Submit" />
</td>
</tr>
</table> </table>
</form> </form>
{% load static %} {% load static %}

View File

@ -1,72 +1,101 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
{% load static %} {% load static %}
<head> <head>
<meta charset="utf-8"/> <meta charset="utf-8" />
<meta name="description" content="Self-hosted time-tracker."/> <meta name="description" content="Self-hosted time-tracker." />
<meta name="keywords" content="time, tracking, video games, self-hosted"/> <meta name="keywords" content="time, tracking, video games, self-hosted" />
<meta name="viewport" content="width=device-width, initial-scale=1"/> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Timetracker - {% block title %}Untitled{% endblock title %}</title> <title>Timetracker -
{% block title %}
Untitled
{% endblock title %}
</title>
<script src="{% static 'js/htmx.min.js' %}"></script> <script src="{% static 'js/htmx.min.js' %}"></script>
<link rel="stylesheet" href="{% static 'base.css' %}" /> <link rel="stylesheet" href="{% static 'base.css' %}" />
</head> </head>
<body class="dark" hx-indicator="#indicator" hx-boost="true"> <body class="dark" hx-indicator="#indicator" hx-boost="true">
<img id="indicator" src="{% static 'icons/loading.png' %}" class="absolute right-3 top-3 animate-spin htmx-indicator" /> <img id="indicator"
src="{% static 'icons/loading.png' %}"
class="absolute right-3 top-3 animate-spin htmx-indicator" />
<div class="dark:bg-gray-800 min-h-screen"> <div class="dark:bg-gray-800 min-h-screen">
<nav class="mb-4 bg-white dark:bg-gray-900 border-gray-200 rounded"> <nav class="mb-4 bg-white dark:bg-gray-900 border-gray-200 rounded">
<div class="container flex flex-wrap items-center justify-between mx-auto"> <div class="container flex flex-wrap items-center justify-between mx-auto">
<a href="{% url 'list_sessions_recent' %}" class="flex items-center"> <a href="{% url 'list_sessions_recent' %}" class="flex items-center">
<span class="text-4xl"><img src="{% static 'icons/schedule.png' %}" width="48" class="mr-4" /></span> <span class="text-4xl">
<img src="{% static 'icons/schedule.png' %}" width="48" class="mr-4" />
</span>
<span class="self-center text-xl font-semibold whitespace-nowrap text-white">Timetracker</span> <span class="self-center text-xl font-semibold whitespace-nowrap text-white">Timetracker</span>
</a> </a>
<div class="w-full md:block md:w-auto"> <div class="w-full md:block md:w-auto">
<ul <ul class="flex flex-col md:flex-row p-4 mt-4 dark:text-white">
class="flex flex-col md:flex-row p-4 mt-4 dark:text-white">
<li class="relative group"> <li class="relative group">
<a class="block py-2 pl-3 pr-4 hover:underline" href="{% url 'add_game' %}">New</a> <a class="block py-2 pl-3 pr-4 hover:underline"
href="{% url 'add_game' %}">New</a>
<ul class="absolute hidden text-gray-700 pt-1 group-hover:block w-auto whitespace-nowrap"> <ul class="absolute hidden text-gray-700 pt-1 group-hover:block w-auto whitespace-nowrap">
{% if purchase_available %} {% if purchase_available %}
<li><a class="bg-gray-200 hover:bg-gray-400 py-2 px-4 block whitespace-no-wrap" href="{% url 'add_device' %}">Device</a></li> <li>
<a class="bg-gray-200 hover:bg-gray-400 py-2 px-4 block whitespace-no-wrap"
href="{% url 'add_device' %}">Device</a>
</li>
{% endif %} {% endif %}
<li><a class="bg-gray-200 hover:bg-gray-400 py-2 px-4 block whitespace-no-wrap" href="{% url 'add_game' %}">Game</a></li> <li>
<a class="bg-gray-200 hover:bg-gray-400 py-2 px-4 block whitespace-no-wrap"
href="{% url 'add_game' %}">Game</a>
</li>
{% if game_available and platform_available %} {% if game_available and platform_available %}
<li><a class="bg-gray-200 hover:bg-gray-400 py-2 px-4 block whitespace-no-wrap" href="{% url 'add_edition' %}">Edition</a></li> <li>
<a class="bg-gray-200 hover:bg-gray-400 py-2 px-4 block whitespace-no-wrap"
href="{% url 'add_edition' %}">Edition</a>
</li>
{% endif %} {% endif %}
<li><a class="bg-gray-200 hover:bg-gray-400 py-2 px-4 block whitespace-no-wrap" href="{% url 'add_platform' %}">Platform</a></li> <li>
<a class="bg-gray-200 hover:bg-gray-400 py-2 px-4 block whitespace-no-wrap"
href="{% url 'add_platform' %}">Platform</a>
</li>
{% if edition_available %} {% if edition_available %}
<li><a class="bg-gray-200 hover:bg-gray-400 py-2 px-4 block whitespace-no-wrap" href="{% url 'add_purchase' %}">Purchase</a></li> <li>
<a class="bg-gray-200 hover:bg-gray-400 py-2 px-4 block whitespace-no-wrap"
href="{% url 'add_purchase' %}">Purchase</a>
</li>
{% endif %} {% endif %}
{% if purchase_available %} {% if purchase_available %}
<li><a class="bg-gray-200 hover:bg-gray-400 py-2 px-4 block whitespace-no-wrap" href="{% url 'add_session' %}">Session</a></li> <li>
<a class="bg-gray-200 hover:bg-gray-400 py-2 px-4 block whitespace-no-wrap"
href="{% url 'add_session' %}">Session</a>
</li>
{% endif %} {% endif %}
</ul> </ul>
</li> </li>
{% if session_count > 0 %} {% if session_count > 0 %}
<li class="relative group"> <li class="relative group">
<a class="block py-2 pl-3 pr-4 hover:underline" href="{% url 'stats_current_year' %}">Stats</a> <a class="block py-2 pl-3 pr-4 hover:underline"
<ul class="absolute hidden text-gray-700 pt-1 group-hover:block"> href="{% url 'stats_current_year' %}">Stats</a>
{% for year in stats_dropdown_year_range %} <ul class="absolute hidden text-gray-700 pt-1 group-hover:block">
<li> {% for year in stats_dropdown_year_range %}
<a class="bg-gray-200 hover:bg-gray-400 py-2 px-4 block whitespace-no-wrap" href="{% url 'stats_by_year' year %}">{{ year }}</a> <li>
</li> <a class="bg-gray-200 hover:bg-gray-400 py-2 px-4 block whitespace-no-wrap"
{% endfor %} href="{% url 'stats_by_year' year %}">{{ year }}</a>
</ul> </li>
</li> {% endfor %}
<li><a class="block py-2 pl-3 pr-4 hover:underline" href="{% url 'list_sessions' %}">All Sessions</a></li> </ul>
</li>
<li>
<a class="block py-2 pl-3 pr-4 hover:underline"
href="{% url 'list_sessions' %}">All Sessions</a>
</li>
{% endif %} {% endif %}
</ul> </ul>
</div> </div>
</div> </div>
</nav> </nav>
{% block content %}No content here.{% endblock content %} {% block content %}
No content here.
{% endblock content %}
</div> </div>
{% load version %} {% load version %}
<span class="fixed left-2 bottom-2 text-xs text-slate-300 dark:text-slate-600">{% version %} ({% version_date %})</span> <span class="fixed left-2 bottom-2 text-xs text-slate-300 dark:text-slate-600">{% version %} ({% version_date %})</span>
{% block scripts %}{% endblock scripts %} {% block scripts %}
{% endblock scripts %}
</body> </body>
</html> </html>

View File

@ -2,25 +2,12 @@
title title
text text
{% endcomment %} {% endcomment %}
<a <a href="{{ link }}"
href="{{ link }}" title="{{ title }}"
title="{{ title }}" class="truncate max-w-xs py-1 px-2 text-xs bg-green-600 hover:bg-green-700 focus:ring-green-500 focus:ring-offset-blue-200 text-white transition ease-in duration-200 text-center font-semibold shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 rounded-sm">
class="truncate max-w-xs py-1 px-2 text-xs bg-green-600 hover:bg-green-700 focus:ring-green-500 focus:ring-offset-blue-200 text-white transition ease-in duration-200 text-center font-semibold shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 rounded-sm" {% comment %} <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="self-center w-6 h-6 inline">
> <path stroke-linecap="round" stroke-linejoin="round" d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.348a1.125 1.125 0 010 1.971l-11.54 6.347a1.125 1.125 0 01-1.667-.985V5.653z" />
{% comment %} <svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="self-center w-6 h-6 inline"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.348a1.125 1.125 0 010 1.971l-11.54 6.347a1.125 1.125 0 01-1.667-.985V5.653z"
/>
</svg> </svg>
{% endcomment %} {% endcomment %}
{{ text }} {{ text }}
</a> </a>

View File

@ -2,25 +2,17 @@
title title
text text
{% endcomment %} {% endcomment %}
<button <button type="button"
type="button" title="{{ title }}"
title="{{ title }}" autofocus
autofocus class="truncate max-w-xs sm:max-w-md lg:max-w-lg py-1 px-2 bg-green-600 hover:bg-green-700 focus:ring-green-500 focus:ring-offset-blue-200 text-white transition ease-in duration-200 text-center text-base font-semibold shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 rounded-lg">
class="truncate max-w-xs sm:max-w-md lg:max-w-lg py-1 px-2 bg-green-600 hover:bg-green-700 focus:ring-green-500 focus:ring-offset-blue-200 text-white transition ease-in duration-200 text-center text-base font-semibold shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 rounded-lg" <svg xmlns="http://www.w3.org/2000/svg"
> fill="none"
<svg viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg" stroke-width="1.5"
fill="none" stroke="currentColor"
viewBox="0 0 24 24" class="self-center w-6 h-6 inline">
stroke-width="1.5" <path stroke-linecap="round" stroke-linejoin="round" d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.348a1.125 1.125 0 010 1.971l-11.54 6.347a1.125 1.125 0 01-1.667-.985V5.653z" />
stroke="currentColor" </svg>
class="self-center w-6 h-6 inline" {{ text }}
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M5.25 5.653c0-.856.917-1.398 1.667-.986l11.54 6.348a1.125 1.125 0 010 1.971l-11.54 6.347a1.125 1.125 0 01-1.667-.985V5.653z"
/>
</svg>
{{ text }}
</button> </button>

View File

@ -1,21 +1,13 @@
<a href="{{ edit_url }}"> <a href="{{ edit_url }}">
<button <button type="button"
type="button" title="Edit"
title="Edit" class="ml-1 py-1 px-2 flex justify-center items-center bg-violet-600 hover:bg-violet-700 focus:ring-violet-500 focus:ring-offset-violet-200 text-white transition ease-in duration-200 text-center text-base font-semibold shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 w-7 h-4 rounded-lg">
class="ml-1 py-1 px-2 flex justify-center items-center bg-violet-600 hover:bg-violet-700 focus:ring-violet-500 focus:ring-offset-violet-200 text-white transition ease-in duration-200 text-center text-base font-semibold shadow-md focus:outline-none focus:ring-2 focus:ring-offset-2 w-7 h-4 rounded-lg" <svg xmlns="http://www.w3.org/2000/svg"
> viewBox="0 0 20 20"
<svg fill="currentColor"
xmlns="http://www.w3.org/2000/svg" class="w-5 h-5">
viewBox="0 0 20 20" <path d="M5.433 13.917l1.262-3.155A4 4 0 017.58 9.42l6.92-6.918a2.121 2.121 0 013 3l-6.92 6.918c-.383.383-.84.685-1.343.886l-3.154 1.262a.5.5 0 01-.65-.65z" />
fill="currentColor" <path d="M3.5 5.75c0-.69.56-1.25 1.25-1.25H10A.75.75 0 0010 3H4.75A2.75 2.75 0 002 5.75v9.5A2.75 2.75 0 004.75 18h9.5A2.75 2.75 0 0017 15.25V10a.75.75 0 00-1.5 0v5.25c0 .69-.56 1.25-1.25 1.25h-9.5c-.69 0-1.25-.56-1.25-1.25v-9.5z" />
class="w-5 h-5" </svg>
> </button>
<path
d="M5.433 13.917l1.262-3.155A4 4 0 017.58 9.42l6.92-6.918a2.121 2.121 0 013 3l-6.92 6.918c-.383.383-.84.685-1.343.886l-3.154 1.262a.5.5 0 01-.65-.65z"
/>
<path
d="M3.5 5.75c0-.69.56-1.25 1.25-1.25H10A.75.75 0 0010 3H4.75A2.75 2.75 0 002 5.75v9.5A2.75 2.75 0 004.75 18h9.5A2.75 2.75 0 0017 15.25V10a.75.75 0 00-1.5 0v5.25c0 .69-.56 1.25-1.25 1.25h-9.5c-.69 0-1.25-.56-1.25-1.25v-9.5z"
/>
</svg>
</button>
</a> </a>

View File

@ -1,7 +1,7 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}
{% block title %}{{ title }}{% endblock title %} {{ title }}
{% endblock title %}
{% block content %} {% block content %}
<div class="text-slate-300 mx-auto max-w-screen-lg text-center"> <div class="text-slate-300 mx-auto max-w-screen-lg text-center">
{% if session_count > 0 %} {% if session_count > 0 %}

View File

@ -1,80 +1,68 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load static %} {% load static %}
{% block title %}
{% block title %}{{ title }}{% endblock title %} {{ title }}
{% endblock title %}
{% block content %} {% block content %}
{% if dataset.count >= 1 %}
{% if dataset.count >= 1 %} <div class="mx-auto text-center my-4">
<div class="mx-auto text-center my-4"> <a id="last-session-start"
<a href="{% url 'start_session_same_as_last' last.id %}"
id="last-session-start" hx-get="{% url 'start_session_same_as_last' last.id %}"
href="{% url 'start_session_same_as_last' last.id %}" hx-swap="afterbegin"
hx-get="{% url 'start_session_same_as_last' last.id %}" hx-target=".responsive-table tbody"
hx-swap="afterbegin" hx-select=".responsive-table tbody tr:first-child"
hx-target=".responsive-table tbody" onClick="document.querySelector('#last-session-start').classList.add('invisible')"
hx-select=".responsive-table tbody tr:first-child" class="{% if last.timestamp_end == null %}invisible{% endif %}">
onClick="document.querySelector('#last-session-start').classList.add('invisible')" {% include 'components/button_start.html' with text=last.purchase title="Start session of last played game" only %}
class="{% if last.timestamp_end == null %}invisible{% endif %}" </a>
> </div>
{% include 'components/button_start.html' with text=last.purchase title="Start session of last played game" only %} {% endif %}
</a> {% if dataset.count != 0 %}
</div> <table class="responsive-table">
{% endif %} <thead>
<tr>
{% if dataset.count != 0 %} <th class="px-2 sm:px-4 md:px-6 md:py-2">Name</th>
<table class="responsive-table"> <th class="hidden sm:table-cell px-2 sm:px-4 md:px-6 md:py-2">Start</th>
<thead> <th class="hidden lg:table-cell px-2 sm:px-4 md:px-6 md:py-2">End</th>
<tr> <th class="px-2 sm:px-4 md:px-6 md:py-2">Duration</th>
<th class="px-2 sm:px-4 md:px-6 md:py-2">Name</th> </tr>
<th class="hidden sm:table-cell px-2 sm:px-4 md:px-6 md:py-2">Start</th> </thead>
<th class="hidden lg:table-cell px-2 sm:px-4 md:px-6 md:py-2">End</th> <tbody>
<th class="px-2 sm:px-4 md:px-6 md:py-2">Duration</th> {% for data in dataset %}
</tr> <tr>
</thead> <td class="px-2 sm:px-4 md:px-6 md:py-2 purchase-name truncate max-w-20char md:max-w-40char">
<tbody> <a class="underline decoration-slate-500 sm:decoration-2"
{% for data in dataset %} href="{% url 'view_game' data.purchase.edition.game.id %}">
<tr> {{ data.purchase.edition }}
<td </a>
class="px-2 sm:px-4 md:px-6 md:py-2 purchase-name truncate max-w-20char md:max-w-40char" </td>
> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono hidden sm:table-cell">
<a {{ data.timestamp_start | date:"d/m/Y H:i" }}
class="underline decoration-slate-500 sm:decoration-2" </td>
href="{% url 'view_game' data.purchase.edition.game.id %}"> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono hidden lg:table-cell">
{{ data.purchase.edition }} {% if data.unfinished %}
</a> <a href="{% url 'update_session' data.id %}"
</td> hx-get="{% url 'update_session' data.id %}"
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono hidden sm:table-cell"> hx-swap="outerHTML"
{{ data.timestamp_start | date:"d/m/Y H:i" }} hx-target=".responsive-table tbody tr:first-child"
</td> hx-select=".responsive-table tbody tr:first-child"
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono hidden lg:table-cell"> hx-indicator="#indicator"
{% if data.unfinished %} onClick="document.querySelector('#last-session-start').classList.remove('invisible')">
<a <span class="text-yellow-300">Finish now?</span>
href="{% url 'update_session' data.id %}" </a>
hx-get="{% url 'update_session' data.id %}" {% elif data.duration_manual %}
hx-swap="outerHTML" --
hx-target=".responsive-table tbody tr:first-child" {% else %}
hx-select=".responsive-table tbody tr:first-child" {{ data.timestamp_end | date:"d/m/Y H:i" }}
hx-indicator="#indicator" {% endif %}
onClick="document.querySelector('#last-session-start').classList.remove('invisible')" </td>
> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ data.duration_formatted }}</td>
<span class="text-yellow-300">Finish now?</span> </tr>
</a> {% endfor %}
{% elif data.duration_manual %} </tbody>
-- </table>
{% else %} {% else %}
{{ data.timestamp_end | date:"d/m/Y H:i" }} <div class="mx-auto text-center text-slate-300 text-xl">No sessions found.</div>
{% endif %} {% endif %}
</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">
{{ data.duration_formatted }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<div class="mx-auto text-center text-slate-300 text-xl">No sessions found.</div>
{% endif %}
{% endblock content %} {% endblock content %}

View File

@ -1,17 +1,19 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}
{% block title %}{{ title }}{% endblock title %} {{ title }}
{% endblock title %}
{% load static %} {% load static %}
{% block content %} {% block content %}
<div class="dark:text-white max-w-sm sm:max-w-xl lg:max-w-3xl mx-auto"> <div class="dark:text-white max-w-sm sm:max-w-xl lg:max-w-3xl mx-auto">
<div class="flex justify-center items-center"> <div class="flex justify-center items-center">
<form method="get" class="text-center"> <form method="get" class="text-center">
<label class="text-5xl text-center inline-block mb-10" for="yearSelect">Stats for:</label> <label class="text-5xl text-center inline-block mb-10" for="yearSelect">Stats for:</label>
<select name="year" id="yearSelect" onchange="this.form.submit();" class="mx-2"> <select name="year"
id="yearSelect"
onchange="this.form.submit();"
class="mx-2">
{% for year_item in stats_dropdown_year_range %} {% for year_item in stats_dropdown_year_range %}
<option value="{{ year_item }}" {% if year == year_item %}selected{% endif %}>{{ year_item }}</option> <option value="{{ year_item }}" {% if year == year_item %}selected{% endif %}>{{ year_item }}</option>
{% endfor %} {% endfor %}
</select> </select>
</form> </form>
@ -62,11 +64,15 @@
</tr> </tr>
<tr> <tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2">Refunded</td> <td class="px-2 sm:px-4 md:px-6 md:py-2">Refunded</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ all_purchased_refunded_this_year.count }} ({{ refunded_percent }}%)</td> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">
{{ all_purchased_refunded_this_year.count }} ({{ refunded_percent }}%)
</td>
</tr> </tr>
<tr> <tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2">Unfinished</td> <td class="px-2 sm:px-4 md:px-6 md:py-2">Unfinished</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ purchased_unfinished.count }} ({{ unfinished_purchases_percent }}%)</td> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">
{{ purchased_unfinished.count }} ({{ unfinished_purchases_percent }}%)
</td>
</tr> </tr>
<tr> <tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2">Backlog Decrease</td> <td class="px-2 sm:px-4 md:px-6 md:py-2">Backlog Decrease</td>
@ -90,14 +96,13 @@
</thead> </thead>
<tbody> <tbody>
{% for game in top_10_games_by_playtime %} {% for game in top_10_games_by_playtime %}
<tr> <tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono"> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">
<a class="underline decoration-slate-500 sm:decoration-2" href="{% url 'view_game' game.id %}">{{ game.name }} <a class="underline decoration-slate-500 sm:decoration-2"
href="{% url 'view_game' game.id %}">{{ game.name }}</a>
</a> </td>
</td> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ game.formatted_playtime }}</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ game.formatted_playtime }}</td> </tr>
</tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
@ -111,10 +116,10 @@
</thead> </thead>
<tbody> <tbody>
{% for item in total_playtime_per_platform %} {% for item in total_playtime_per_platform %}
<tr> <tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ item.platform_name }}</td> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ item.platform_name }}</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ item.formatted_playtime }}</td> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ item.formatted_playtime }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
@ -128,10 +133,13 @@
</thead> </thead>
<tbody> <tbody>
{% for purchase in all_finished_this_year %} {% for purchase in all_finished_this_year %}
<tr> <tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono"><a class="underline decoration-slate-500 sm:decoration-2" href="{% url 'edit_purchase' purchase.id %}">{{ purchase.edition.name }}</a></td> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ purchase.date_finished | date:"d/m/Y" }}</td> <a class="underline decoration-slate-500 sm:decoration-2"
</tr> href="{% url 'edit_purchase' purchase.id %}">{{ purchase.edition.name }}</a>
</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ purchase.date_finished | date:"d/m/Y" }}</td>
</tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
@ -145,10 +153,13 @@
</thead> </thead>
<tbody> <tbody>
{% for purchase in this_year_finished_this_year %} {% for purchase in this_year_finished_this_year %}
<tr> <tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono"><a class="underline decoration-slate-500 sm:decoration-2" href="{% url 'edit_purchase' purchase.id %}">{{ purchase.edition.name }}</a></td> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ purchase.date_finished | date:"d/m/Y" }}</td> <a class="underline decoration-slate-500 sm:decoration-2"
</tr> href="{% url 'edit_purchase' purchase.id %}">{{ purchase.edition.name }}</a>
</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ purchase.date_finished | date:"d/m/Y" }}</td>
</tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
@ -162,10 +173,13 @@
</thead> </thead>
<tbody> <tbody>
{% for purchase in purchased_this_year_finished_this_year %} {% for purchase in purchased_this_year_finished_this_year %}
<tr> <tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono"><a class="underline decoration-slate-500 sm:decoration-2" href="{% url 'edit_purchase' purchase.id %}">{{ purchase.edition.name }}</a></td> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ purchase.date_finished | date:"d/m/Y" }}</td> <a class="underline decoration-slate-500 sm:decoration-2"
</tr> href="{% url 'edit_purchase' purchase.id %}">{{ purchase.edition.name }}</a>
</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ purchase.date_finished | date:"d/m/Y" }}</td>
</tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
@ -180,18 +194,17 @@
</thead> </thead>
<tbody> <tbody>
{% for purchase in all_purchased_this_year %} {% for purchase in all_purchased_this_year %}
<tr> <tr>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono"> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">
<a class="underline decoration-slate-500 sm:decoration-2" href="{% url 'edit_purchase' purchase.id %}"> <a class="underline decoration-slate-500 sm:decoration-2"
{{ purchase.edition.name }} href="{% url 'edit_purchase' purchase.id %}">
{% if purchase.type != "game" %} {{ purchase.edition.name }}
({{ purchase.name }}, {{ purchase.get_type_display }}) {% if purchase.type != "game" %}({{ purchase.name }}, {{ purchase.get_type_display }}){% endif %}
{% endif %} </a>
</a> </td>
</td> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ purchase.price }}</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ purchase.price }}</td> <td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ purchase.date_purchased | date:"d/m/Y" }}</td>
<td class="px-2 sm:px-4 md:px-6 md:py-2 font-mono">{{ purchase.date_purchased | date:"d/m/Y" }}</td> </tr>
</tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>

View File

@ -1,9 +1,8 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %}
{% block title %}{{ title }}{% endblock title %} {{ title }}
{% endblock title %}
{% load static %} {% load static %}
{% block content %} {% block content %}
<div class="dark:text-white max-w-sm sm:max-w-xl lg:max-w-3xl mx-auto"> <div class="dark:text-white max-w-sm sm:max-w-xl lg:max-w-3xl mx-auto">
<h1 class="text-4xl flex items-center"> <h1 class="text-4xl flex items-center">
@ -14,83 +13,66 @@
</h1> </h1>
<h2 class="text-lg my-2 ml-2"> <h2 class="text-lg my-2 ml-2">
{{ hours_sum }} <span class="dark:text-slate-500">total</span> {{ hours_sum }} <span class="dark:text-slate-500">total</span>
{{ session_average }} <span class="dark:text-slate-500">avg</span> {{ session_average }} <span class="dark:text-slate-500">avg</span>
({{ playrange }}) </h2> ({{ playrange }})
</h2>
<hr class="border-slate-500"> <hr class="border-slate-500">
<h1 class="text-3xl mt-4 mb-1">Editions <span class="dark:text-slate-500">({{ edition_count }})</span> and Purchases <span class="dark:text-slate-500">({{ purchase_count }})</span></h1> <h1 class="text-3xl mt-4 mb-1">
Editions <span class="dark:text-slate-500">({{ edition_count }})</span> and Purchases <span class="dark:text-slate-500">({{ purchase_count }})</span>
</h1>
<ul> <ul>
{% for edition in editions %} {% for edition in editions %}
<li class="sm:pl-2 flex items-center"> <li class="sm:pl-2 flex items-center">
{{ edition.name }} ({{ edition.platform }}, {{ edition.year_released }}) {{ edition.name }} ({{ edition.platform }}, {{ edition.year_released }})
{% if edition.wikidata %} {% if edition.wikidata %}
<span class="hidden sm:inline"> <span class="hidden sm:inline">
<a href="https://www.wikidata.org/wiki/{{ edition.wikidata }}"> <a href="https://www.wikidata.org/wiki/{{ edition.wikidata }}">
<img class="inline mx-2 w-6" src="{% static 'icons/wikidata.png' %}"/> <img class="inline mx-2 w-6" src="{% static 'icons/wikidata.png' %}" />
</a> </a>
</span> </span>
{% endif %} {% endif %}
{% url 'edit_edition' edition.id as edit_url %} {% url 'edit_edition' edition.id as edit_url %}
{% include 'components/edit_button.html' with edit_url=edit_url %} {% include 'components/edit_button.html' with edit_url=edit_url %}
</li> </li>
<ul> <ul>
{% for purchase in edition.game_purchases %} {% for purchase in edition.game_purchases %}
<li class="sm:pl-6 flex items-center"> <li class="sm:pl-6 flex items-center">
{{ purchase.get_ownership_type_display }}, {{ purchase.date_purchased | date:"Y" }}, {{ purchase.price }} {{ purchase.price_currency}} {{ purchase.get_ownership_type_display }}, {{ purchase.date_purchased | date:"Y" }}
{% url 'edit_purchase' purchase.id as edit_url %} {% if purchase.price != 0 %}({{ purchase.price }} {{ purchase.price_currency }}){% endif %}
{% include 'components/edit_button.html' with edit_url=edit_url %} {% url 'edit_purchase' purchase.id as edit_url %}
</li> {% include 'components/edit_button.html' with edit_url=edit_url %}
<ul> </li>
{% for related_purchase in purchase.nongame_related_purchases %} <ul>
<li class="sm:pl-12 flex items-center"> {% for related_purchase in purchase.nongame_related_purchases %}
{{ related_purchase.name }} ({{ related_purchase.get_type_display }}, {{ purchase.platform }}, {{ related_purchase.date_purchased | date:"Y" }}, {{ related_purchase.price }} {{ related_purchase.price_currency}}) <li class="sm:pl-12 flex items-center">
{% url 'edit_purchase' related_purchase.id as edit_url %} {{ related_purchase.name }} ({{ related_purchase.get_type_display }}, {{ purchase.platform }}, {{ related_purchase.date_purchased | date:"Y" }}, {{ related_purchase.price }} {{ related_purchase.price_currency }})
{% include 'components/edit_button.html' with edit_url=edit_url %} {% url 'edit_purchase' related_purchase.id as edit_url %}
</li> {% include 'components/edit_button.html' with edit_url=edit_url %}
{% endfor %} </li>
</ul> {% endfor %}
</ul>
{% endfor %} {% endfor %}
</ul> </ul>
{% endfor %} {% endfor %}
</ul> </ul>
<h1 class="text-3xl mt-4 mb-1 flex gap-2 items-center"> <h1 class="text-3xl mt-4 mb-1 flex gap-2 items-center">
Sessions Sessions
<span class="dark:text-slate-500"> <span class="dark:text-slate-500">({{ sessions.count }})</span>
({{ sessions.count }})
</span>
{% url 'start_game_session' game.id as add_session_link %} {% url 'start_game_session' game.id as add_session_link %}
{% include 'components/button.html' with title="Start new session" text="New" link=add_session_link %} {% include 'components/button.html' with title="Start new session" text="New" link=add_session_link %}
and Notes <span class="dark:text-slate-500">({{ sessions_with_notes_count }})</span>
</h1> </h1>
<ul> <ul>
{% for session in sessions %} {% for session in sessions %}
<li class="sm:pl-2 flex items-center"> <li class="sm:pl-2 mt-4 mb-2 dark:text-slate-400 flex items-center">
{{ session.timestamp_start | date:"d/m/Y" }} {{ session.timestamp_start | date:"d/m/Y H:m" }}
({{ session.device.get_type_display | default:"Unknown" }}, {{ session.duration_formatted }}) ({{ session.device.get_type_display | default:"Unknown" }}, {{ session.duration_formatted }})
{% url 'edit_session' session.id as edit_url %} {% url 'edit_session' session.id as edit_url %}
{% include 'components/edit_button.html' with edit_url=edit_url %} {% include 'components/edit_button.html' with edit_url=edit_url %}
</li> </li>
<li class="sm:pl-4 italic">{{ session.note|linebreaks }}</li>
{% endfor %} {% endfor %}
</ul> </ul>
<h1 class="text-3xl mt-4 mb-1">Notes <span class="dark:text-slate-500">({{ sessions_with_notes.count }})</span></h1>
<ul>
{% for session in sessions_with_notes %}
<li class="sm:pl-2">
<ul>
<li class="block dark:text-slate-500">
<span class="flex items-center">
{{ session.timestamp_start | date:"d/m/Y H:m" }}
{% url 'edit_session' session.id as edit_session_url %}
{% include 'components/edit_button.html' with edit_url=edit_session_url %}
</span>
</li>
<li class="sm:pl-4 italic">
{{ session.note|linebreaks }}
</li>
</ul>
</li>
{% endfor %}
</ul>
</div> </div>
{% endblock content %} {% endblock content %}

View File

@ -1,13 +1,18 @@
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Any, Callable from typing import Any, Callable
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Count, F, Prefetch, Sum from django.db.models import Count, F, Prefetch, Sum
from django.db.models.functions import TruncDate from django.db.models.functions import TruncDate
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect from django.http import (
HttpRequest,
HttpResponse,
HttpResponseBadRequest,
HttpResponseRedirect,
)
from django.shortcuts import redirect, render from django.shortcuts import redirect, render
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django.core.exceptions import ObjectDoesNotExist
from common.time import format_duration from common.time import format_duration
from common.utils import safe_division from common.utils import safe_division
@ -174,7 +179,7 @@ def view_game(request, game_id=None):
"purchase_count": Purchase.objects.filter(edition__game=game).count(), "purchase_count": Purchase.objects.filter(edition__game=game).count(),
"session_average": round(total_hours / int(session_count), 1), "session_average": round(total_hours / int(session_count), 1),
"session_count": session_count, "session_count": session_count,
"sessions_with_notes": sessions.exclude(note=""), "sessions_with_notes_count": sessions.exclude(note="").count(),
"sessions": sessions.order_by("-timestamp_start"), "sessions": sessions.order_by("-timestamp_start"),
"title": f"Game Overview - {game.name}", "title": f"Game Overview - {game.name}",
"hours_sum": total_hours, "hours_sum": total_hours,
@ -212,6 +217,8 @@ def edit_edition(request, edition_id=None):
def related_purchase_by_edition(request): def related_purchase_by_edition(request):
edition_id = request.GET.get("edition") edition_id = request.GET.get("edition")
if not edition_id:
return HttpResponseBadRequest("Invalid edition_id")
form = PurchaseForm() form = PurchaseForm()
form.fields["related_purchase"].queryset = Purchase.objects.filter( form.fields["related_purchase"].queryset = Purchase.objects.filter(
edition_id=edition_id, type=Purchase.GAME edition_id=edition_id, type=Purchase.GAME

152
poetry.lock generated
View File

@ -1,10 +1,9 @@
# This file is automatically @generated by Poetry and should not be changed by hand. # This file is automatically @generated by Poetry 1.7.0 and should not be changed by hand.
[[package]] [[package]]
name = "asgiref" name = "asgiref"
version = "3.7.2" version = "3.7.2"
description = "ASGI specs, helper code, and adapters" description = "ASGI specs, helper code, and adapters"
category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -19,7 +18,6 @@ tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"]
name = "black" name = "black"
version = "22.12.0" version = "22.12.0"
description = "The uncompromising code formatter." description = "The uncompromising code formatter."
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -49,11 +47,21 @@ d = ["aiohttp (>=3.7.4)"]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
uvloop = ["uvloop (>=0.15.2)"] uvloop = ["uvloop (>=0.15.2)"]
[[package]]
name = "cfgv"
version = "3.4.0"
description = "Validate configuration and produce human readable error messages."
optional = false
python-versions = ">=3.8"
files = [
{file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"},
{file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"},
]
[[package]] [[package]]
name = "click" name = "click"
version = "8.1.7" version = "8.1.7"
description = "Composable command line interface toolkit" description = "Composable command line interface toolkit"
category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -68,7 +76,6 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""}
name = "colorama" name = "colorama"
version = "0.4.6" version = "0.4.6"
description = "Cross-platform colored terminal text." description = "Cross-platform colored terminal text."
category = "main"
optional = false optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
files = [ files = [
@ -80,7 +87,6 @@ files = [
name = "cssbeautifier" name = "cssbeautifier"
version = "1.14.11" version = "1.14.11"
description = "CSS unobfuscator and beautifier." description = "CSS unobfuscator and beautifier."
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -92,11 +98,21 @@ editorconfig = ">=0.12.2"
jsbeautifier = "*" jsbeautifier = "*"
six = ">=1.13.0" six = ">=1.13.0"
[[package]]
name = "distlib"
version = "0.3.7"
description = "Distribution utilities"
optional = false
python-versions = "*"
files = [
{file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"},
{file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"},
]
[[package]] [[package]]
name = "django" name = "django"
version = "4.2.7" version = "4.2.7"
description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design."
category = "main"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -117,7 +133,6 @@ bcrypt = ["bcrypt"]
name = "django-extensions" name = "django-extensions"
version = "3.2.3" version = "3.2.3"
description = "Extensions for Django" description = "Extensions for Django"
category = "dev"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
files = [ files = [
@ -132,7 +147,6 @@ Django = ">=3.2"
name = "djhtml" name = "djhtml"
version = "1.5.2" version = "1.5.2"
description = "Django/Jinja template indenter" description = "Django/Jinja template indenter"
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -146,7 +160,6 @@ dev = ["black", "flake8", "isort", "nox", "pre-commit"]
name = "djlint" name = "djlint"
version = "1.34.0" version = "1.34.0"
description = "HTML Template Linter and Formatter" description = "HTML Template Linter and Formatter"
category = "dev"
optional = false optional = false
python-versions = ">=3.8.0,<4.0.0" python-versions = ">=3.8.0,<4.0.0"
files = [ files = [
@ -171,7 +184,6 @@ tqdm = ">=4.62.2,<5.0.0"
name = "editorconfig" name = "editorconfig"
version = "0.12.3" version = "0.12.3"
description = "EditorConfig File Locator and Interpreter for Python" description = "EditorConfig File Locator and Interpreter for Python"
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -179,11 +191,26 @@ files = [
{file = "EditorConfig-0.12.3.tar.gz", hash = "sha256:57f8ce78afcba15c8b18d46b5170848c88d56fd38f05c2ec60dbbfcb8996e89e"}, {file = "EditorConfig-0.12.3.tar.gz", hash = "sha256:57f8ce78afcba15c8b18d46b5170848c88d56fd38f05c2ec60dbbfcb8996e89e"},
] ]
[[package]]
name = "filelock"
version = "3.13.1"
description = "A platform independent file lock."
optional = false
python-versions = ">=3.8"
files = [
{file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"},
{file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"},
]
[package.extras]
docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.24)"]
testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"]
typing = ["typing-extensions (>=4.8)"]
[[package]] [[package]]
name = "gunicorn" name = "gunicorn"
version = "20.1.0" version = "20.1.0"
description = "WSGI HTTP Server for UNIX" description = "WSGI HTTP Server for UNIX"
category = "main"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
files = [ files = [
@ -204,7 +231,6 @@ tornado = ["tornado (>=0.2)"]
name = "h11" name = "h11"
version = "0.14.0" version = "0.14.0"
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -216,7 +242,6 @@ files = [
name = "html-tag-names" name = "html-tag-names"
version = "0.1.2" version = "0.1.2"
description = "List of known HTML tag names" description = "List of known HTML tag names"
category = "dev"
optional = false optional = false
python-versions = ">=3.7,<4.0" python-versions = ">=3.7,<4.0"
files = [ files = [
@ -228,7 +253,6 @@ files = [
name = "html-void-elements" name = "html-void-elements"
version = "0.1.0" version = "0.1.0"
description = "List of HTML void tag names." description = "List of HTML void tag names."
category = "dev"
optional = false optional = false
python-versions = ">=3.7,<4.0" python-versions = ">=3.7,<4.0"
files = [ files = [
@ -236,11 +260,24 @@ files = [
{file = "html_void_elements-0.1.0-py3-none-any.whl", hash = "sha256:784cf39db03cdeb017320d9301009f8f3480f9d7b254d0974272e80e0cb5e0d2"}, {file = "html_void_elements-0.1.0-py3-none-any.whl", hash = "sha256:784cf39db03cdeb017320d9301009f8f3480f9d7b254d0974272e80e0cb5e0d2"},
] ]
[[package]]
name = "identify"
version = "2.5.31"
description = "File identification library for Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "identify-2.5.31-py2.py3-none-any.whl", hash = "sha256:90199cb9e7bd3c5407a9b7e81b4abec4bb9d249991c79439ec8af740afc6293d"},
{file = "identify-2.5.31.tar.gz", hash = "sha256:7736b3c7a28233637e3c36550646fc6389bedd74ae84cb788200cc8e2dd60b75"},
]
[package.extras]
license = ["ukkonen"]
[[package]] [[package]]
name = "iniconfig" name = "iniconfig"
version = "2.0.0" version = "2.0.0"
description = "brain-dead simple config-ini parsing" description = "brain-dead simple config-ini parsing"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -252,7 +289,6 @@ files = [
name = "isort" name = "isort"
version = "5.12.0" version = "5.12.0"
description = "A Python utility / library to sort Python imports." description = "A Python utility / library to sort Python imports."
category = "dev"
optional = false optional = false
python-versions = ">=3.8.0" python-versions = ">=3.8.0"
files = [ files = [
@ -270,7 +306,6 @@ requirements-deprecated-finder = ["pip-api", "pipreqs"]
name = "jsbeautifier" name = "jsbeautifier"
version = "1.14.11" version = "1.14.11"
description = "JavaScript unobfuscator and beautifier." description = "JavaScript unobfuscator and beautifier."
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -285,7 +320,6 @@ six = ">=1.13.0"
name = "json5" name = "json5"
version = "0.9.14" version = "0.9.14"
description = "A Python implementation of the JSON5 data format." description = "A Python implementation of the JSON5 data format."
category = "dev"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -300,7 +334,6 @@ dev = ["hypothesis"]
name = "markupsafe" name = "markupsafe"
version = "2.1.3" version = "2.1.3"
description = "Safely add untrusted strings to HTML/XML markup." description = "Safely add untrusted strings to HTML/XML markup."
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -370,7 +403,6 @@ files = [
name = "mypy" name = "mypy"
version = "0.991" version = "0.991"
description = "Optional static typing for Python" description = "Optional static typing for Python"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -420,7 +452,6 @@ reports = ["lxml"]
name = "mypy-extensions" name = "mypy-extensions"
version = "1.0.0" version = "1.0.0"
description = "Type system extensions for programs checked with the mypy type checker." description = "Type system extensions for programs checked with the mypy type checker."
category = "dev"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
files = [ files = [
@ -428,11 +459,24 @@ files = [
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
] ]
[[package]]
name = "nodeenv"
version = "1.8.0"
description = "Node.js virtual environment builder"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*"
files = [
{file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"},
{file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"},
]
[package.dependencies]
setuptools = "*"
[[package]] [[package]]
name = "packaging" name = "packaging"
version = "23.2" version = "23.2"
description = "Core utilities for Python packages" description = "Core utilities for Python packages"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -444,7 +488,6 @@ files = [
name = "pathspec" name = "pathspec"
version = "0.11.2" version = "0.11.2"
description = "Utility library for gitignore style pattern matching of file paths." description = "Utility library for gitignore style pattern matching of file paths."
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -454,14 +497,13 @@ files = [
[[package]] [[package]]
name = "platformdirs" name = "platformdirs"
version = "4.0.0" version = "3.11.0"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
{file = "platformdirs-4.0.0-py3-none-any.whl", hash = "sha256:118c954d7e949b35437270383a3f2531e99dd93cf7ce4dc8340d3356d30f173b"}, {file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"},
{file = "platformdirs-4.0.0.tar.gz", hash = "sha256:cb633b2bcf10c51af60beb0ab06d2f1d69064b43abf4c185ca6b28865f3f9731"}, {file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"},
] ]
[package.extras] [package.extras]
@ -472,7 +514,6 @@ test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-co
name = "pluggy" name = "pluggy"
version = "1.3.0" version = "1.3.0"
description = "plugin and hook calling mechanisms for python" description = "plugin and hook calling mechanisms for python"
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -484,11 +525,28 @@ files = [
dev = ["pre-commit", "tox"] dev = ["pre-commit", "tox"]
testing = ["pytest", "pytest-benchmark"] testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "pre-commit"
version = "3.5.0"
description = "A framework for managing and maintaining multi-language pre-commit hooks."
optional = false
python-versions = ">=3.8"
files = [
{file = "pre_commit-3.5.0-py2.py3-none-any.whl", hash = "sha256:841dc9aef25daba9a0238cd27984041fa0467b4199fc4852e27950664919f660"},
{file = "pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32"},
]
[package.dependencies]
cfgv = ">=2.0.0"
identify = ">=1.0.0"
nodeenv = ">=0.11.1"
pyyaml = ">=5.1"
virtualenv = ">=20.10.0"
[[package]] [[package]]
name = "pytest" name = "pytest"
version = "7.4.3" version = "7.4.3"
description = "pytest: simple powerful testing with Python" description = "pytest: simple powerful testing with Python"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -509,7 +567,6 @@ testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "no
name = "pyyaml" name = "pyyaml"
version = "6.0.1" version = "6.0.1"
description = "YAML parser and emitter for Python" description = "YAML parser and emitter for Python"
category = "dev"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
files = [ files = [
@ -569,7 +626,6 @@ files = [
name = "regex" name = "regex"
version = "2023.10.3" version = "2023.10.3"
description = "Alternative regular expression module, to replace re." description = "Alternative regular expression module, to replace re."
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -667,7 +723,6 @@ files = [
name = "setuptools" name = "setuptools"
version = "68.2.2" version = "68.2.2"
description = "Easily download, build, install, upgrade, and uninstall Python packages" description = "Easily download, build, install, upgrade, and uninstall Python packages"
category = "main"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -684,7 +739,6 @@ testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jar
name = "six" name = "six"
version = "1.16.0" version = "1.16.0"
description = "Python 2 and 3 compatibility utilities" description = "Python 2 and 3 compatibility utilities"
category = "dev"
optional = false optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
files = [ files = [
@ -696,7 +750,6 @@ files = [
name = "sqlparse" name = "sqlparse"
version = "0.4.4" version = "0.4.4"
description = "A non-validating SQL parser." description = "A non-validating SQL parser."
category = "main"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
files = [ files = [
@ -713,7 +766,6 @@ test = ["pytest", "pytest-cov"]
name = "tqdm" name = "tqdm"
version = "4.66.1" version = "4.66.1"
description = "Fast, Extensible Progress Meter" description = "Fast, Extensible Progress Meter"
category = "dev"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -734,7 +786,6 @@ telegram = ["requests"]
name = "typing-extensions" name = "typing-extensions"
version = "4.8.0" version = "4.8.0"
description = "Backported and Experimental Type Hints for Python 3.8+" description = "Backported and Experimental Type Hints for Python 3.8+"
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -746,7 +797,6 @@ files = [
name = "tzdata" name = "tzdata"
version = "2023.3" version = "2023.3"
description = "Provider of IANA time zone data" description = "Provider of IANA time zone data"
category = "main"
optional = false optional = false
python-versions = ">=2" python-versions = ">=2"
files = [ files = [
@ -758,7 +808,6 @@ files = [
name = "uvicorn" name = "uvicorn"
version = "0.20.0" version = "0.20.0"
description = "The lightning-fast ASGI server." description = "The lightning-fast ASGI server."
category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -773,11 +822,30 @@ h11 = ">=0.8"
[package.extras] [package.extras]
standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"]
[[package]]
name = "virtualenv"
version = "20.24.6"
description = "Virtual Python Environment builder"
optional = false
python-versions = ">=3.7"
files = [
{file = "virtualenv-20.24.6-py3-none-any.whl", hash = "sha256:520d056652454c5098a00c0f073611ccbea4c79089331f60bf9d7ba247bb7381"},
{file = "virtualenv-20.24.6.tar.gz", hash = "sha256:02ece4f56fbf939dbbc33c0715159951d6bf14aaf5457b092e4548e1382455af"},
]
[package.dependencies]
distlib = ">=0.3.7,<1"
filelock = ">=3.12.2,<4"
platformdirs = ">=3.9.1,<4"
[package.extras]
docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"]
[[package]] [[package]]
name = "werkzeug" name = "werkzeug"
version = "2.3.8" version = "2.3.8"
description = "The comprehensive WSGI web application library." description = "The comprehensive WSGI web application library."
category = "dev"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
@ -793,5 +861,5 @@ watchdog = ["watchdog (>=2.3)"]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.11" python-versions = "^3.12"
content-hash = "c82d09d48e81655bd981938540184878d986253d41858277180d3a48244fd069" content-hash = "32e7c40e7148530effb10ebd5d67a4f1c8fe30794a4d3b5d213d4f30048c79ea"

View File

@ -23,6 +23,10 @@ werkzeug = "^2.2.2"
djhtml = "^1.5.2" djhtml = "^1.5.2"
djlint = "^1.19.11" djlint = "^1.19.11"
isort = "^5.11.4" isort = "^5.11.4"
pre-commit = "^3.5.0"
[tool.isort]
profile = "black"
[build-system] [build-system]
requires = ["poetry-core"] requires = ["poetry-core"]