Compare commits
	
		
			7 Commits
		
	
	
		
			fda4913c97
			...
			b28c42d945
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						
						
							
						
						b28c42d945
	
				 | 
					
					
						|||
| 
						
						
							
						
						3099f02145
	
				 | 
					
					
						|||
| 
						
						
							
						
						74b9d0421c
	
				 | 
					
					
						|||
| 
						
						
							
						
						c61adad180
	
				 | 
					
					
						|||
| 
						
						
							
						
						298ecb4092
	
				 | 
					
					
						|||
| 
						
						
							
						
						020e12e20b
	
				 | 
					
					
						|||
| 
						
						
							
						
						6ef56bfed5
	
				 | 
					
					
						
@ -1,8 +1,11 @@
 | 
			
		||||
repos:
 | 
			
		||||
-   repo: https://github.com/psf/black
 | 
			
		||||
    rev: 24.8.0
 | 
			
		||||
    hooks:
 | 
			
		||||
    -   id: black
 | 
			
		||||
# disable due to incomaptible formatting between
 | 
			
		||||
# black and ruff
 | 
			
		||||
# TODO: replace with ruff when it works on NixOS
 | 
			
		||||
# -   repo: https://github.com/psf/black
 | 
			
		||||
#     rev: 24.8.0
 | 
			
		||||
#     hooks:
 | 
			
		||||
#     -   id: black
 | 
			
		||||
- repo: https://github.com/pycqa/isort
 | 
			
		||||
  rev: 5.13.2
 | 
			
		||||
  hooks:
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,31 @@
 | 
			
		||||
from random import choices
 | 
			
		||||
from string import ascii_lowercase
 | 
			
		||||
from typing import Any
 | 
			
		||||
 | 
			
		||||
from django.template.loader import render_to_string
 | 
			
		||||
from django.utils.safestring import mark_safe
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def Popover(
 | 
			
		||||
    wrapped_content: str,
 | 
			
		||||
    popover_content: str = "",
 | 
			
		||||
) -> str:
 | 
			
		||||
    id = randomid()
 | 
			
		||||
    if popover_content == "":
 | 
			
		||||
        popover_content = wrapped_content
 | 
			
		||||
    content = f"<span data-popover-target={id}>{wrapped_content}</span>"
 | 
			
		||||
    result = mark_safe(
 | 
			
		||||
        str(content)
 | 
			
		||||
        + render_to_string(
 | 
			
		||||
            "components/popover.html",
 | 
			
		||||
            {
 | 
			
		||||
                "id": id,
 | 
			
		||||
                "children": popover_content,
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
    return result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def safe_division(numerator: int | float, denominator: int | float) -> int | float:
 | 
			
		||||
    """
 | 
			
		||||
@ -31,3 +57,24 @@ def safe_getattr(obj: object, attr_chain: str, default: Any | None = None) -> ob
 | 
			
		||||
        except AttributeError:
 | 
			
		||||
            return default
 | 
			
		||||
    return obj
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def truncate(input_string: str, length: int = 30, ellipsis: str = "…") -> str:
 | 
			
		||||
    return (
 | 
			
		||||
        (f"{input_string[:length-len(ellipsis)]}{ellipsis}")
 | 
			
		||||
        if len(input_string) > 30
 | 
			
		||||
        else input_string
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def truncate_with_popover(input_string: str) -> str:
 | 
			
		||||
    if (truncated := truncate(input_string)) != input_string:
 | 
			
		||||
        print(f"Not the same after: {truncated=}")
 | 
			
		||||
        return Popover(wrapped_content=truncated, popover_content=input_string)
 | 
			
		||||
    else:
 | 
			
		||||
        print("Strings are the same!")
 | 
			
		||||
        return input_string
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def randomid(seed: str = "", length: int = 10) -> str:
 | 
			
		||||
    return seed + "".join(choices(ascii_lowercase, k=length))
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										89
									
								
								games/deviceviews.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								games/deviceviews.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,89 @@
 | 
			
		||||
from typing import Any
 | 
			
		||||
 | 
			
		||||
from django.contrib.auth.decorators import login_required
 | 
			
		||||
from django.core.paginator import Paginator
 | 
			
		||||
from django.http import HttpRequest, HttpResponse
 | 
			
		||||
from django.shortcuts import get_object_or_404, redirect, render
 | 
			
		||||
from django.template.loader import render_to_string
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
 | 
			
		||||
from games.forms import DeviceForm
 | 
			
		||||
from games.models import Device
 | 
			
		||||
from games.views import dateformat
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
def list_devices(request: HttpRequest) -> HttpResponse:
 | 
			
		||||
    context: dict[Any, Any] = {}
 | 
			
		||||
    page_number = request.GET.get("page", 1)
 | 
			
		||||
    limit = request.GET.get("limit", 10)
 | 
			
		||||
    devices = Device.objects.order_by("-created_at")
 | 
			
		||||
    page_obj = None
 | 
			
		||||
    if int(limit) != 0:
 | 
			
		||||
        paginator = Paginator(devices, limit)
 | 
			
		||||
        page_obj = paginator.get_page(page_number)
 | 
			
		||||
        devices = page_obj.object_list
 | 
			
		||||
 | 
			
		||||
    context = {
 | 
			
		||||
        "title": "Manage devices",
 | 
			
		||||
        "page_obj": page_obj or None,
 | 
			
		||||
        "elided_page_range": (
 | 
			
		||||
            page_obj.paginator.get_elided_page_range(
 | 
			
		||||
                page_number, on_each_side=1, on_ends=1
 | 
			
		||||
            )
 | 
			
		||||
            if page_obj
 | 
			
		||||
            else None
 | 
			
		||||
        ),
 | 
			
		||||
        "data": {
 | 
			
		||||
            "columns": [
 | 
			
		||||
                "Name",
 | 
			
		||||
                "Type",
 | 
			
		||||
                "Created",
 | 
			
		||||
                "Actions",
 | 
			
		||||
            ],
 | 
			
		||||
            "rows": [
 | 
			
		||||
                [
 | 
			
		||||
                    device.name,
 | 
			
		||||
                    device.get_type_display(),
 | 
			
		||||
                    device.created_at.strftime(dateformat),
 | 
			
		||||
                    render_to_string(
 | 
			
		||||
                        "components/button_group_sm.html",
 | 
			
		||||
                        {
 | 
			
		||||
                            "buttons": [
 | 
			
		||||
                                {
 | 
			
		||||
                                    "href": reverse("edit_device", args=[device.pk]),
 | 
			
		||||
                                    "text": "Edit",
 | 
			
		||||
                                },
 | 
			
		||||
                                {
 | 
			
		||||
                                    "href": reverse("delete_device", args=[device.pk]),
 | 
			
		||||
                                    "text": "Delete",
 | 
			
		||||
                                    "color": "red",
 | 
			
		||||
                                },
 | 
			
		||||
                            ]
 | 
			
		||||
                        },
 | 
			
		||||
                    ),
 | 
			
		||||
                ]
 | 
			
		||||
                for device in devices
 | 
			
		||||
            ],
 | 
			
		||||
        },
 | 
			
		||||
    }
 | 
			
		||||
    return render(request, "list_purchases.html", context)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
def edit_device(request: HttpRequest, device_id: int = 0) -> HttpResponse:
 | 
			
		||||
    device = get_object_or_404(Device, id=device_id)
 | 
			
		||||
    form = DeviceForm(request.POST or None, instance=device)
 | 
			
		||||
    if form.is_valid():
 | 
			
		||||
        form.save()
 | 
			
		||||
        return redirect("list_devices")
 | 
			
		||||
 | 
			
		||||
    context: dict[str, Any] = {"form": form, "title": "Edit device"}
 | 
			
		||||
    return render(request, "add.html", context)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
def delete_device(request: HttpRequest, device_id: int) -> HttpResponse:
 | 
			
		||||
    device = get_object_or_404(Device, id=device_id)
 | 
			
		||||
    device.delete()
 | 
			
		||||
    return redirect("list_sessions")
 | 
			
		||||
							
								
								
									
										109
									
								
								games/editionviews.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										109
									
								
								games/editionviews.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,109 @@
 | 
			
		||||
from typing import Any
 | 
			
		||||
 | 
			
		||||
from django.contrib.auth.decorators import login_required
 | 
			
		||||
from django.core.paginator import Paginator
 | 
			
		||||
from django.http import HttpRequest, HttpResponse
 | 
			
		||||
from django.shortcuts import get_object_or_404, redirect, render
 | 
			
		||||
from django.template.loader import render_to_string
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
 | 
			
		||||
from common.utils import truncate_with_popover
 | 
			
		||||
from games.forms import EditionForm
 | 
			
		||||
from games.models import Edition
 | 
			
		||||
from games.views import dateformat
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
def list_editions(request: HttpRequest) -> HttpResponse:
 | 
			
		||||
    context: dict[Any, Any] = {}
 | 
			
		||||
    page_number = request.GET.get("page", 1)
 | 
			
		||||
    limit = request.GET.get("limit", 10)
 | 
			
		||||
    editions = Edition.objects.order_by("-created_at")
 | 
			
		||||
    page_obj = None
 | 
			
		||||
    if int(limit) != 0:
 | 
			
		||||
        paginator = Paginator(editions, limit)
 | 
			
		||||
        page_obj = paginator.get_page(page_number)
 | 
			
		||||
        editions = page_obj.object_list
 | 
			
		||||
 | 
			
		||||
    context = {
 | 
			
		||||
        "title": "Manage editions",
 | 
			
		||||
        "page_obj": page_obj or None,
 | 
			
		||||
        "elided_page_range": (
 | 
			
		||||
            page_obj.paginator.get_elided_page_range(
 | 
			
		||||
                page_number, on_each_side=1, on_ends=1
 | 
			
		||||
            )
 | 
			
		||||
            if page_obj
 | 
			
		||||
            else None
 | 
			
		||||
        ),
 | 
			
		||||
        "data": {
 | 
			
		||||
            "columns": [
 | 
			
		||||
                "Game",
 | 
			
		||||
                "Name",
 | 
			
		||||
                "Sort Name",
 | 
			
		||||
                "Platform",
 | 
			
		||||
                "Year",
 | 
			
		||||
                "Wikidata",
 | 
			
		||||
                "Created",
 | 
			
		||||
                "Actions",
 | 
			
		||||
            ],
 | 
			
		||||
            "rows": [
 | 
			
		||||
                [
 | 
			
		||||
                    truncate_with_popover(edition.game.name),
 | 
			
		||||
                    truncate_with_popover(
 | 
			
		||||
                        edition.name
 | 
			
		||||
                        if edition.game.name != edition.name
 | 
			
		||||
                        else "(identical)"
 | 
			
		||||
                    ),
 | 
			
		||||
                    truncate_with_popover(
 | 
			
		||||
                        edition.sort_name
 | 
			
		||||
                        if edition.sort_name is not None
 | 
			
		||||
                        and edition.game.name != edition.sort_name
 | 
			
		||||
                        else "(identical)"
 | 
			
		||||
                    ),
 | 
			
		||||
                    truncate_with_popover(str(edition.platform)),
 | 
			
		||||
                    edition.year_released,
 | 
			
		||||
                    edition.wikidata,
 | 
			
		||||
                    edition.created_at.strftime(dateformat),
 | 
			
		||||
                    render_to_string(
 | 
			
		||||
                        "components/button_group_sm.html",
 | 
			
		||||
                        {
 | 
			
		||||
                            "buttons": [
 | 
			
		||||
                                {
 | 
			
		||||
                                    "href": reverse("edit_edition", args=[edition.pk]),
 | 
			
		||||
                                    "text": "Edit",
 | 
			
		||||
                                },
 | 
			
		||||
                                {
 | 
			
		||||
                                    "href": reverse(
 | 
			
		||||
                                        "delete_edition", args=[edition.pk]
 | 
			
		||||
                                    ),
 | 
			
		||||
                                    "text": "Delete",
 | 
			
		||||
                                    "color": "red",
 | 
			
		||||
                                },
 | 
			
		||||
                            ]
 | 
			
		||||
                        },
 | 
			
		||||
                    ),
 | 
			
		||||
                ]
 | 
			
		||||
                for edition in editions
 | 
			
		||||
            ],
 | 
			
		||||
        },
 | 
			
		||||
    }
 | 
			
		||||
    return render(request, "list_purchases.html", context)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
def edit_device(request: HttpRequest, edition_id: int = 0) -> HttpResponse:
 | 
			
		||||
    edition = get_object_or_404(Edition, id=edition_id)
 | 
			
		||||
    form = EditionForm(request.POST or None, instance=edition)
 | 
			
		||||
    if form.is_valid():
 | 
			
		||||
        form.save()
 | 
			
		||||
        return redirect("list_editions")
 | 
			
		||||
 | 
			
		||||
    context: dict[str, Any] = {"form": form, "title": "Edit edition"}
 | 
			
		||||
    return render(request, "add.html", context)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
def delete_edition(request: HttpRequest, edition_id: int) -> HttpResponse:
 | 
			
		||||
    edition = get_object_or_404(Edition, id=edition_id)
 | 
			
		||||
    edition.delete()
 | 
			
		||||
    return redirect("list_editions")
 | 
			
		||||
							
								
								
									
										73
									
								
								games/gameviews.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								games/gameviews.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,73 @@
 | 
			
		||||
from typing import Any
 | 
			
		||||
 | 
			
		||||
from django.contrib.auth.decorators import login_required
 | 
			
		||||
from django.core.paginator import Paginator
 | 
			
		||||
from django.http import HttpRequest, HttpResponse
 | 
			
		||||
from django.shortcuts import render
 | 
			
		||||
from django.template.loader import render_to_string
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
 | 
			
		||||
from games.models import Game
 | 
			
		||||
from games.views import dateformat
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
def list_games(request: HttpRequest) -> HttpResponse:
 | 
			
		||||
    context: dict[Any, Any] = {}
 | 
			
		||||
    page_number = request.GET.get("page", 1)
 | 
			
		||||
    limit = request.GET.get("limit", 10)
 | 
			
		||||
    games = Game.objects.order_by("-created_at")
 | 
			
		||||
    page_obj = None
 | 
			
		||||
    if int(limit) != 0:
 | 
			
		||||
        paginator = Paginator(games, limit)
 | 
			
		||||
        page_obj = paginator.get_page(page_number)
 | 
			
		||||
        games = page_obj.object_list
 | 
			
		||||
 | 
			
		||||
    context = {
 | 
			
		||||
        "title": "Manage games",
 | 
			
		||||
        "page_obj": page_obj or None,
 | 
			
		||||
        "elided_page_range": (
 | 
			
		||||
            page_obj.paginator.get_elided_page_range(
 | 
			
		||||
                page_number, on_each_side=1, on_ends=1
 | 
			
		||||
            )
 | 
			
		||||
            if page_obj
 | 
			
		||||
            else None
 | 
			
		||||
        ),
 | 
			
		||||
        "data": {
 | 
			
		||||
            "columns": [
 | 
			
		||||
                "Name",
 | 
			
		||||
                "Sort Name",
 | 
			
		||||
                "Year",
 | 
			
		||||
                "Wikidata",
 | 
			
		||||
                "Created",
 | 
			
		||||
                "Actions",
 | 
			
		||||
            ],
 | 
			
		||||
            "rows": [
 | 
			
		||||
                [
 | 
			
		||||
                    game.name,
 | 
			
		||||
                    game.sort_name,
 | 
			
		||||
                    game.year_released,
 | 
			
		||||
                    game.wikidata,
 | 
			
		||||
                    game.created_at.strftime(dateformat),
 | 
			
		||||
                    render_to_string(
 | 
			
		||||
                        "components/button_group_sm.html",
 | 
			
		||||
                        {
 | 
			
		||||
                            "buttons": [
 | 
			
		||||
                                {
 | 
			
		||||
                                    "href": reverse("edit_game", args=[game.pk]),
 | 
			
		||||
                                    "text": "Edit",
 | 
			
		||||
                                },
 | 
			
		||||
                                {
 | 
			
		||||
                                    "href": reverse("delete_game", args=[game.pk]),
 | 
			
		||||
                                    "text": "Delete",
 | 
			
		||||
                                    "color": "red",
 | 
			
		||||
                                },
 | 
			
		||||
                            ]
 | 
			
		||||
                        },
 | 
			
		||||
                    ),
 | 
			
		||||
                ]
 | 
			
		||||
                for game in games
 | 
			
		||||
            ],
 | 
			
		||||
        },
 | 
			
		||||
    }
 | 
			
		||||
    return render(request, "list_purchases.html", context)
 | 
			
		||||
							
								
								
									
										25
									
								
								games/migrations/0035_alter_session_device.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								games/migrations/0035_alter_session_device.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
			
		||||
# Generated by Django 5.1 on 2024-08-11 15:50
 | 
			
		||||
 | 
			
		||||
import django.db.models.deletion
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ("games", "0034_purchase_date_dropped_purchase_infinite"),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name="session",
 | 
			
		||||
            name="device",
 | 
			
		||||
            field=models.ForeignKey(
 | 
			
		||||
                blank=True,
 | 
			
		||||
                default=None,
 | 
			
		||||
                null=True,
 | 
			
		||||
                on_delete=django.db.models.deletion.SET_DEFAULT,
 | 
			
		||||
                to="games.device",
 | 
			
		||||
            ),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
							
								
								
									
										19
									
								
								games/migrations/0036_alter_edition_platform.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								games/migrations/0036_alter_edition_platform.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
			
		||||
# Generated by Django 5.1 on 2024-08-11 16:48
 | 
			
		||||
 | 
			
		||||
import django.db.models.deletion
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ('games', '0035_alter_session_device'),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AlterField(
 | 
			
		||||
            model_name='edition',
 | 
			
		||||
            name='platform',
 | 
			
		||||
            field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, to='games.platform'),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
@ -39,7 +39,7 @@ class Edition(models.Model):
 | 
			
		||||
    name = models.CharField(max_length=255)
 | 
			
		||||
    sort_name = models.CharField(max_length=255, null=True, blank=True, default=None)
 | 
			
		||||
    platform = models.ForeignKey(
 | 
			
		||||
        Platform, on_delete=models.CASCADE, null=True, blank=True, default=None
 | 
			
		||||
        Platform, on_delete=models.SET_DEFAULT, null=True, blank=True, default=None
 | 
			
		||||
    )
 | 
			
		||||
    year_released = models.IntegerField(null=True, blank=True, default=None)
 | 
			
		||||
    wikidata = models.CharField(max_length=50, null=True, blank=True, default=None)
 | 
			
		||||
@ -182,7 +182,7 @@ class Session(models.Model):
 | 
			
		||||
    duration_calculated = models.DurationField(blank=True, null=True)
 | 
			
		||||
    device = models.ForeignKey(
 | 
			
		||||
        "Device",
 | 
			
		||||
        on_delete=models.CASCADE,
 | 
			
		||||
        on_delete=models.SET_DEFAULT,
 | 
			
		||||
        null=True,
 | 
			
		||||
        blank=True,
 | 
			
		||||
        default=None,
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										80
									
								
								games/platformviews.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								games/platformviews.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,80 @@
 | 
			
		||||
from typing import Any
 | 
			
		||||
 | 
			
		||||
from django.contrib.auth.decorators import login_required
 | 
			
		||||
from django.core.paginator import Paginator
 | 
			
		||||
from django.http import HttpRequest, HttpResponse
 | 
			
		||||
from django.shortcuts import get_object_or_404, redirect, render
 | 
			
		||||
from django.template.loader import render_to_string
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
 | 
			
		||||
from games.models import Platform
 | 
			
		||||
from games.views import dateformat
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
def list_platforms(request: HttpRequest) -> HttpResponse:
 | 
			
		||||
    context: dict[Any, Any] = {}
 | 
			
		||||
    page_number = request.GET.get("page", 1)
 | 
			
		||||
    limit = request.GET.get("limit", 10)
 | 
			
		||||
    platforms = Platform.objects.order_by("-created_at")
 | 
			
		||||
    page_obj = None
 | 
			
		||||
    if int(limit) != 0:
 | 
			
		||||
        paginator = Paginator(platforms, limit)
 | 
			
		||||
        page_obj = paginator.get_page(page_number)
 | 
			
		||||
        platforms = page_obj.object_list
 | 
			
		||||
 | 
			
		||||
    context = {
 | 
			
		||||
        "title": "Manage platforms",
 | 
			
		||||
        "page_obj": page_obj or None,
 | 
			
		||||
        "elided_page_range": (
 | 
			
		||||
            page_obj.paginator.get_elided_page_range(
 | 
			
		||||
                page_number, on_each_side=1, on_ends=1
 | 
			
		||||
            )
 | 
			
		||||
            if page_obj
 | 
			
		||||
            else None
 | 
			
		||||
        ),
 | 
			
		||||
        "data": {
 | 
			
		||||
            "columns": [
 | 
			
		||||
                "Name",
 | 
			
		||||
                "Group",
 | 
			
		||||
                "Created",
 | 
			
		||||
                "Actions",
 | 
			
		||||
            ],
 | 
			
		||||
            "rows": [
 | 
			
		||||
                [
 | 
			
		||||
                    platform.name,
 | 
			
		||||
                    platform.group,
 | 
			
		||||
                    platform.created_at.strftime(dateformat),
 | 
			
		||||
                    render_to_string(
 | 
			
		||||
                        "components/button_group_sm.html",
 | 
			
		||||
                        {
 | 
			
		||||
                            "buttons": [
 | 
			
		||||
                                {
 | 
			
		||||
                                    "href": reverse(
 | 
			
		||||
                                        "edit_platform", args=[platform.pk]
 | 
			
		||||
                                    ),
 | 
			
		||||
                                    "text": "Edit",
 | 
			
		||||
                                },
 | 
			
		||||
                                {
 | 
			
		||||
                                    "href": reverse(
 | 
			
		||||
                                        "delete_platform", args=[platform.pk]
 | 
			
		||||
                                    ),
 | 
			
		||||
                                    "text": "Delete",
 | 
			
		||||
                                    "color": "red",
 | 
			
		||||
                                },
 | 
			
		||||
                            ]
 | 
			
		||||
                        },
 | 
			
		||||
                    ),
 | 
			
		||||
                ]
 | 
			
		||||
                for platform in platforms
 | 
			
		||||
            ],
 | 
			
		||||
        },
 | 
			
		||||
    }
 | 
			
		||||
    return render(request, "list_purchases.html", context)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
def delete_platform(request: HttpRequest, platform_id: int) -> HttpResponse:
 | 
			
		||||
    platform = get_object_or_404(Platform, id=platform_id)
 | 
			
		||||
    platform.delete()
 | 
			
		||||
    return redirect("list_platforms")
 | 
			
		||||
@ -1736,10 +1736,6 @@ input:checked + .toggle-bg {
 | 
			
		||||
  white-space: nowrap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.text-ellipsis {
 | 
			
		||||
  text-overflow: ellipsis;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.whitespace-nowrap {
 | 
			
		||||
  white-space: nowrap;
 | 
			
		||||
}
 | 
			
		||||
@ -2215,10 +2211,6 @@ input:checked + .toggle-bg {
 | 
			
		||||
  min-width: 30ch;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.max-w-30char {
 | 
			
		||||
  max-width: 30ch;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.\[a-zA-Z\:\\-\] {
 | 
			
		||||
  a-z-a--z: \-;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,3 @@
 | 
			
		||||
<!-- needs data-popover-target on triggering block -->
 | 
			
		||||
<!-- id -->
 | 
			
		||||
<!-- children -->
 | 
			
		||||
<div data-popover
 | 
			
		||||
     id="{{ id }}"
 | 
			
		||||
     role="tooltip"
 | 
			
		||||
 | 
			
		||||
@ -1,17 +1,8 @@
 | 
			
		||||
{% fragment as default_content %}
 | 
			
		||||
{% load randomid %}
 | 
			
		||||
{% for td in data %}
 | 
			
		||||
    {% if forloop.first %}
 | 
			
		||||
        <th scope="row"
 | 
			
		||||
            class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white min-w-30char">
 | 
			
		||||
            {% randomid td as th_popover_id %}
 | 
			
		||||
            <span data-popover-target="{{ th_popover_id }}">{{ td|truncatechars:30 }}</span>
 | 
			
		||||
            {% if td|length > 30 %}
 | 
			
		||||
                {% #popover id=th_popover_id %}
 | 
			
		||||
                {{ td }}
 | 
			
		||||
                {% /popover %}
 | 
			
		||||
            {% endif %}
 | 
			
		||||
        </th>
 | 
			
		||||
            class="px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white">{{ td }}</th>
 | 
			
		||||
    {% else %}
 | 
			
		||||
        {% #table_td %}
 | 
			
		||||
        {{ td }}
 | 
			
		||||
 | 
			
		||||
@ -95,19 +95,19 @@
 | 
			
		||||
                        <ul class="py-2 text-sm text-gray-700 dark:text-gray-400"
 | 
			
		||||
                            aria-labelledby="dropdownLargeButton">
 | 
			
		||||
                            <li>
 | 
			
		||||
                                <a href="{% url 'add_device' %}"
 | 
			
		||||
                                <a href="{% url 'list_devices' %}"
 | 
			
		||||
                                   class="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">Devices</a>
 | 
			
		||||
                            </li>
 | 
			
		||||
                            <li>
 | 
			
		||||
                                <a href="{% url 'add_game' %}"
 | 
			
		||||
                                <a href="{% url 'list_games' %}"
 | 
			
		||||
                                   class="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">Games</a>
 | 
			
		||||
                            </li>
 | 
			
		||||
                            <li>
 | 
			
		||||
                                <a href="{% url 'add_edition' %}"
 | 
			
		||||
                                <a href="{% url 'list_editions' %}"
 | 
			
		||||
                                   class="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">Editions</a>
 | 
			
		||||
                            </li>
 | 
			
		||||
                            <li>
 | 
			
		||||
                                <a href="{% url 'add_platform' %}"
 | 
			
		||||
                                <a href="{% url 'list_platforms' %}"
 | 
			
		||||
                                   class="block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white">Platforms</a>
 | 
			
		||||
                            </li>
 | 
			
		||||
                            <li>
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,23 @@
 | 
			
		||||
from django.urls import path
 | 
			
		||||
 | 
			
		||||
from games import purchaseviews, sessionviews, views
 | 
			
		||||
from games import (
 | 
			
		||||
    deviceviews,
 | 
			
		||||
    editionviews,
 | 
			
		||||
    gameviews,
 | 
			
		||||
    platformviews,
 | 
			
		||||
    purchaseviews,
 | 
			
		||||
    sessionviews,
 | 
			
		||||
    views,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
    path("", views.index, name="index"),
 | 
			
		||||
    path("device/add", views.add_device, name="add_device"),
 | 
			
		||||
    path(
 | 
			
		||||
        "device/delete/<int:device_id>", deviceviews.delete_device, name="delete_device"
 | 
			
		||||
    ),
 | 
			
		||||
    path("device/edit/<int:device_id>", deviceviews.edit_device, name="edit_device"),
 | 
			
		||||
    path("device/list", deviceviews.list_devices, name="list_devices"),
 | 
			
		||||
    path("edition/add", views.add_edition, name="add_edition"),
 | 
			
		||||
    path(
 | 
			
		||||
        "edition/add/for-game/<int:game_id>",
 | 
			
		||||
@ -12,12 +25,25 @@ urlpatterns = [
 | 
			
		||||
        name="add_edition_for_game",
 | 
			
		||||
    ),
 | 
			
		||||
    path("edition/<int:edition_id>/edit", views.edit_edition, name="edit_edition"),
 | 
			
		||||
    path("edition/list", editionviews.list_editions, name="list_editions"),
 | 
			
		||||
    path(
 | 
			
		||||
        "edition/<int:edition_id>/delete",
 | 
			
		||||
        editionviews.delete_edition,
 | 
			
		||||
        name="delete_edition",
 | 
			
		||||
    ),
 | 
			
		||||
    path("game/add", views.add_game, name="add_game"),
 | 
			
		||||
    path("game/<int:game_id>/edit", views.edit_game, name="edit_game"),
 | 
			
		||||
    path("game/<int:game_id>/view", views.view_game, name="view_game"),
 | 
			
		||||
    path("game/<int:game_id>/delete", views.delete_game, name="delete_game"),
 | 
			
		||||
    path("game/list", gameviews.list_games, name="list_games"),
 | 
			
		||||
    path("platform/add", views.add_platform, name="add_platform"),
 | 
			
		||||
    path("platform/<int:platform_id>/edit", views.edit_platform, name="edit_platform"),
 | 
			
		||||
    path(
 | 
			
		||||
        "platform/<int:platform_id>/delete",
 | 
			
		||||
        platformviews.delete_platform,
 | 
			
		||||
        name="delete_platform",
 | 
			
		||||
    ),
 | 
			
		||||
    path("platform/list", platformviews.list_platforms, name="list_platforms"),
 | 
			
		||||
    path("purchase/add", views.add_purchase, name="add_purchase"),
 | 
			
		||||
    path("purchase/<int:purchase_id>/edit", views.edit_purchase, name="edit_purchase"),
 | 
			
		||||
    path(
 | 
			
		||||
@ -77,12 +103,6 @@ urlpatterns = [
 | 
			
		||||
        name="list_sessions_end_session",
 | 
			
		||||
    ),
 | 
			
		||||
    path("session/list", sessionviews.list_sessions, name="list_sessions"),
 | 
			
		||||
    path(
 | 
			
		||||
        "session/list/recent",
 | 
			
		||||
        views.list_sessions,
 | 
			
		||||
        {"filter": "recent"},
 | 
			
		||||
        name="list_sessions_recent",
 | 
			
		||||
    ),
 | 
			
		||||
    path(
 | 
			
		||||
        "session/list/by-purchase/<int:purchase_id>",
 | 
			
		||||
        views.list_sessions,
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,3 @@
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
from typing import Any, Callable
 | 
			
		||||
 | 
			
		||||
from django.contrib.auth.decorators import login_required
 | 
			
		||||
@ -225,11 +224,11 @@ def view_game(request: HttpRequest, game_id: int) -> HttpResponse:
 | 
			
		||||
@use_custom_redirect
 | 
			
		||||
def edit_platform(request: HttpRequest, platform_id: int) -> HttpResponse:
 | 
			
		||||
    context = {}
 | 
			
		||||
    purchase = get_object_or_404(Purchase, id=platform_id)
 | 
			
		||||
    form = PlatformForm(request.POST or None, instance=purchase)
 | 
			
		||||
    platform = get_object_or_404(Platform, id=platform_id)
 | 
			
		||||
    form = PlatformForm(request.POST or None, instance=platform)
 | 
			
		||||
    if form.is_valid():
 | 
			
		||||
        form.save()
 | 
			
		||||
        return redirect("list_sessions")
 | 
			
		||||
        return redirect("list_platforms")
 | 
			
		||||
    context["title"] = "Edit Platform"
 | 
			
		||||
    context["form"] = form
 | 
			
		||||
    return render(request, "add.html", context)
 | 
			
		||||
@ -342,12 +341,6 @@ def list_sessions(
 | 
			
		||||
    elif filter == "ownership_type":
 | 
			
		||||
        dataset = all_sessions.filter(purchase__ownership_type=ownership_type)
 | 
			
		||||
        context["ownership_type"] = dict(Purchase.OWNERSHIP_TYPES)[ownership_type]
 | 
			
		||||
    elif filter == "recent":
 | 
			
		||||
        current_year = timezone.now().year
 | 
			
		||||
        first_day_of_year = timezone.make_aware(datetime(current_year, 1, 1))
 | 
			
		||||
        dataset = all_sessions.filter(timestamp_start__gte=first_day_of_year).order_by(
 | 
			
		||||
            "-timestamp_start"
 | 
			
		||||
        )
 | 
			
		||||
        context["title"] = "This year"
 | 
			
		||||
    else:
 | 
			
		||||
        dataset = all_sessions
 | 
			
		||||
@ -757,15 +750,11 @@ def stats(request: HttpRequest, year: int = 0) -> HttpResponse:
 | 
			
		||||
        "all_finished_this_year_count": purchases_finished_this_year.count(),
 | 
			
		||||
        "this_year_finished_this_year": purchases_finished_this_year_released_this_year.select_related(
 | 
			
		||||
            "edition"
 | 
			
		||||
        ).order_by(
 | 
			
		||||
            "date_finished"
 | 
			
		||||
        ),
 | 
			
		||||
        ).order_by("date_finished"),
 | 
			
		||||
        "this_year_finished_this_year_count": purchases_finished_this_year_released_this_year.count(),
 | 
			
		||||
        "purchased_this_year_finished_this_year": purchased_this_year_finished_this_year.select_related(
 | 
			
		||||
            "edition"
 | 
			
		||||
        ).order_by(
 | 
			
		||||
            "date_finished"
 | 
			
		||||
        ),
 | 
			
		||||
        ).order_by("date_finished"),
 | 
			
		||||
        "total_sessions": this_year_sessions.count(),
 | 
			
		||||
        "unique_days": unique_days["dates"],
 | 
			
		||||
        "unique_days_percent": int(unique_days["dates"] / 365 * 100),
 | 
			
		||||
@ -949,4 +938,4 @@ def add_device(request: HttpRequest) -> HttpResponse:
 | 
			
		||||
 | 
			
		||||
@login_required
 | 
			
		||||
def index(request: HttpRequest) -> HttpResponse:
 | 
			
		||||
    return redirect("list_sessions_recent")
 | 
			
		||||
    return redirect("list_sessions")
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user