Compare commits
	
		
			11 Commits
		
	
	
		
			1.0.2
			...
			2640a49734
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						
						
							
						
						2640a49734
	
				 | 
					
					
						|||
| 
						
						
							
						
						65c175afb2
	
				 | 
					
					
						|||
| 
						
						
							
						
						0814071a26
	
				 | 
					
					
						|||
| 
						
						
							
						
						5f845f866e
	
				 | 
					
					
						|||
| 
						
						
							
						
						c3d4697470
	
				 | 
					
					
						|||
| 77293f03e9 | |||
| 1fa364e2ec | |||
| 4a6f4a2f9a | |||
| 9590988b6a | |||
| 938c82a395 | |||
| 33939f631c | 
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@ -5,5 +5,6 @@ __pycache__
 | 
			
		||||
node_modules
 | 
			
		||||
package-lock.json
 | 
			
		||||
db.sqlite3
 | 
			
		||||
static
 | 
			
		||||
static/admin/
 | 
			
		||||
static/django_extensions/
 | 
			
		||||
dist/
 | 
			
		||||
@ -1,3 +1,10 @@
 | 
			
		||||
## 1.0.3 / 2023-02-20 17:16+01:00
 | 
			
		||||
 | 
			
		||||
* Add wikidata ID and year for editions
 | 
			
		||||
* Add icons for game, edition, purchase filters
 | 
			
		||||
* Allow filtering by game, edition, purchase from the session list
 | 
			
		||||
* Allow editing filtered entities from session list
 | 
			
		||||
 | 
			
		||||
## 1.0.2 / 2023-02-18 21:48+01:00
 | 
			
		||||
 | 
			
		||||
* Add support for device info (https://git.kucharczyk.xyz/lukas/timetracker/issues/49)
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,7 @@ RUN npm install && \
 | 
			
		||||
 | 
			
		||||
FROM python:3.10.9-slim-bullseye
 | 
			
		||||
 | 
			
		||||
ENV VERSION_NUMBER 1.0.2
 | 
			
		||||
ENV VERSION_NUMBER 1.0.3
 | 
			
		||||
ENV PROD 1
 | 
			
		||||
ENV PYTHONUNBUFFERED=1
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -14,9 +14,33 @@ textarea {
 | 
			
		||||
 | 
			
		||||
#session-table {
 | 
			
		||||
  display: grid;
 | 
			
		||||
  grid-template-columns: 3fr 1fr repeat(2, 2fr) 0.5fr 1fr;
 | 
			
		||||
  grid-template-columns: 3fr 2fr repeat(2, 1fr) 0.5fr 1fr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.purchase-name > span:nth-child(2) {
 | 
			
		||||
  @apply ml-4
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.purchase-name > span:nth-child(2) > a > img {
 | 
			
		||||
  @apply opacity-0 transition-opacity duration-500
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.purchase-name:hover > span:nth-child(2) > a > img {
 | 
			
		||||
  @apply opacity-50
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.purchase-name > span:nth-child(2) > a > img:hover {
 | 
			
		||||
  @apply opacity-100
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#button-container button {
 | 
			
		||||
  @apply mx-1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
th {
 | 
			
		||||
  @apply text-left;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
th label {
 | 
			
		||||
  @apply mr-4;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -20,8 +20,13 @@ class SessionForm(forms.ModelForm):
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EditionChoiceField(forms.ModelChoiceField):
 | 
			
		||||
    def label_from_instance(self, obj) -> str:
 | 
			
		||||
        return f"{obj.name} ({obj.platform}, {obj.year_released})"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PurchaseForm(forms.ModelForm):
 | 
			
		||||
    edition = forms.ModelChoiceField(queryset=Edition.objects.order_by("name"))
 | 
			
		||||
    edition = EditionChoiceField(queryset=Edition.objects.order_by("name"))
 | 
			
		||||
    platform = forms.ModelChoiceField(queryset=Platform.objects.order_by("name"))
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
@ -38,9 +43,11 @@ class PurchaseForm(forms.ModelForm):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EditionForm(forms.ModelForm):
 | 
			
		||||
    platform = forms.ModelChoiceField(queryset=Platform.objects.order_by("name"))
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = Edition
 | 
			
		||||
        fields = ["game", "name", "platform"]
 | 
			
		||||
        fields = ["game", "name", "platform", "year_released", "wikidata"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class GameForm(forms.ModelForm):
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,23 @@
 | 
			
		||||
# Generated by Django 4.1.5 on 2023-02-20 14:55
 | 
			
		||||
 | 
			
		||||
from django.db import migrations, models
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Migration(migrations.Migration):
 | 
			
		||||
 | 
			
		||||
    dependencies = [
 | 
			
		||||
        ("games", "0014_device_session_device"),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    operations = [
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name="edition",
 | 
			
		||||
            name="wikidata",
 | 
			
		||||
            field=models.CharField(blank=True, default=None, max_length=50, null=True),
 | 
			
		||||
        ),
 | 
			
		||||
        migrations.AddField(
 | 
			
		||||
            model_name="edition",
 | 
			
		||||
            name="year_released",
 | 
			
		||||
            field=models.IntegerField(default=2023),
 | 
			
		||||
        ),
 | 
			
		||||
    ]
 | 
			
		||||
@ -20,6 +20,8 @@ class Edition(models.Model):
 | 
			
		||||
    game = models.ForeignKey("Game", on_delete=models.CASCADE)
 | 
			
		||||
    name = models.CharField(max_length=255)
 | 
			
		||||
    platform = models.ForeignKey("Platform", on_delete=models.CASCADE)
 | 
			
		||||
    year_released = models.IntegerField(default=datetime.today().year)
 | 
			
		||||
    wikidata = models.CharField(max_length=50, null=True, blank=True, default=None)
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return self.name
 | 
			
		||||
@ -56,7 +58,10 @@ class Purchase(models.Model):
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    def __str__(self):
 | 
			
		||||
        return f"{self.edition} ({self.platform}, {self.get_ownership_type_display()})"
 | 
			
		||||
        platform_info = self.platform
 | 
			
		||||
        if self.platform != self.edition.platform:
 | 
			
		||||
            platform_info = f"{self.edition.platform} version on {self.platform}"
 | 
			
		||||
        return f"{self.edition} ({platform_info}, {self.edition.year_released}, {self.get_ownership_type_display()})"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Platform(models.Model):
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								games/static/icons/edition_black.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 292 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								games/static/icons/edition_white.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 321 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								games/static/icons/game_black.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 22 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								games/static/icons/game_white.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 306 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								games/static/icons/purchase_black.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 312 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								games/static/icons/purchase_white.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 364 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								games/static/icons/schedule.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 42 KiB  | 
@ -3,11 +3,14 @@
 | 
			
		||||
{% block title %}{{ title }}{% endblock title %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    <form method="post" enctype="multipart/form-data" class="mx-auto">
 | 
			
		||||
    <form method="post" enctype="multipart/form-data">
 | 
			
		||||
        <table class="mx-auto">
 | 
			
		||||
        {% csrf_token %}
 | 
			
		||||
 | 
			
		||||
        {{ form.as_p }}
 | 
			
		||||
 | 
			
		||||
        <input type="submit" value="Submit"/>
 | 
			
		||||
        {{ form.as_table }}
 | 
			
		||||
        <tr>
 | 
			
		||||
            <td><input type="submit" value="Submit"/></td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        </table>
 | 
			
		||||
    </form>
 | 
			
		||||
{% endblock content %}
 | 
			
		||||
@ -18,7 +18,7 @@
 | 
			
		||||
            <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">
 | 
			
		||||
                    <a href="{% url 'list_sessions_recent' %}" class="flex items-center">
 | 
			
		||||
                        <span class="text-4xl">⌚</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>
 | 
			
		||||
                    </a>
 | 
			
		||||
                    <div class="w-full md:block md:w-auto">
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,7 @@
 | 
			
		||||
{% extends 'base.html' %}
 | 
			
		||||
 | 
			
		||||
{% load static %}
 | 
			
		||||
 | 
			
		||||
{% block title %}{{ title }}{% endblock title %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
@ -10,13 +12,28 @@
 | 
			
		||||
            {% if dataset.count >= 1 %}
 | 
			
		||||
            <div class="mb-4">Total playtime: {{ total_duration }} over {{ dataset.count }} sessions.</div>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
            {% if purchase or platform or edition %}
 | 
			
		||||
            {% if purchase or platform or edition or game or ownership_type %}
 | 
			
		||||
            <span class="block">
 | 
			
		||||
                <a class="text-red-400 inline" href="{% url 'list_sessions' %}">
 | 
			
		||||
                    <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 inline">
 | 
			
		||||
                        <path stroke-linecap="round" stroke-linejoin="round" d="M9.75 9.75l4.5 4.5m0-4.5l-4.5 4.5M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
 | 
			
		||||
                    </svg>
 | 
			
		||||
                </a>Filtering by "{% firstof purchase platform edition %}"
 | 
			
		||||
                </a>
 | 
			
		||||
                {% if purchase %}
 | 
			
		||||
                Filtering by purchase "{{ purchase }}"
 | 
			
		||||
                (<a href="{% url 'edit_purchase' purchase.id %}" class="hover:underline dark:text-white">Edit</a>)
 | 
			
		||||
                {% elif platform %}
 | 
			
		||||
                Filtering by purchase "{{ platform }}"
 | 
			
		||||
                (<a href="{% url 'edit_platform' platform.id %}" class="hover:underline dark:text-white">Edit</a>)
 | 
			
		||||
                {% elif game %}
 | 
			
		||||
                Filtering by purchase "{{ game }}"
 | 
			
		||||
                (<a href="{% url 'edit_game' game.id %}" class="hover:underline dark:text-white">Edit</a>)
 | 
			
		||||
                {% elif edition %}
 | 
			
		||||
                Filtering by purchase "{{ edition }}"
 | 
			
		||||
                (<a href="{% url 'edit_edition' edition.id %}" class="hover:underline dark:text-white">Edit</a>)
 | 
			
		||||
                {% elif ownership_type %}
 | 
			
		||||
                Filtering by ownership type "{{ ownership_type }}"
 | 
			
		||||
                {% endif%}
 | 
			
		||||
            </span>
 | 
			
		||||
            {% if purchase %}<a class="dark:text-white hover:underline block" href="{% url 'list_sessions_by_edition' purchase.edition.id %}">See all platforms</a>{% endif %}
 | 
			
		||||
            {% endif %}
 | 
			
		||||
@ -31,16 +48,38 @@
 | 
			
		||||
            </a>
 | 
			
		||||
            {% endif %}
 | 
			
		||||
        </div>
 | 
			
		||||
    <div id="session-table" class="gap-4 shadow rounded-xl max-w-screen-lg mx-auto dark:bg-slate-700 p-2 justify-center">
 | 
			
		||||
        <div class="dark:border-white dark:text-slate-300 text-lg">Name</div>
 | 
			
		||||
    <div id="session-table" class="gap-4 shadow rounded-xl max-w-screen-2xl mx-auto dark:bg-slate-700 p-2 justify-center">
 | 
			
		||||
        <div class="dark:border-white dark:text-slate-300 text-lg">Purchase</div>
 | 
			
		||||
        <div class="dark:border-white dark:text-slate-300 text-lg">Platform</div>
 | 
			
		||||
        <div class="dark:border-white dark:text-slate-300 text-lg text-center">Start</div>
 | 
			
		||||
        <div class="dark:border-white dark:text-slate-300 text-lg text-center">End</div>
 | 
			
		||||
        <div class="dark:border-white dark:text-slate-300 text-lg">Duration</div>
 | 
			
		||||
        <div class="dark:border-white dark:text-slate-300 text-lg text-right">Manage</div>
 | 
			
		||||
        {% for data in dataset %}
 | 
			
		||||
            <div class="dark:text-white overflow-hidden text-ellipsis whitespace-nowrap"><a class="hover:underline" href="{% url 'list_sessions_by_purchase' data.purchase.id %}">{{ data.purchase.edition }}</a></div>
 | 
			
		||||
            <div class="dark:text-white overflow-hidden text-ellipsis whitespace-nowrap"><a class="hover:underline" href="{% url 'list_sessions_by_platform' data.purchase.platform.id %}">{{ data.purchase.platform }}</a></div>
 | 
			
		||||
            <div class="purchase-name">
 | 
			
		||||
                <span class="dark:text-white overflow-hidden text-ellipsis whitespace-nowrap">{{ data.purchase.edition }} <span class="dark:text-slate-400">(<a class="hover:underline" href="{% url 'list_sessions_by_ownership_type' data.purchase.ownership_type %}">{{ data.purchase.get_ownership_type_display }}</a>)</span></span>
 | 
			
		||||
                
 | 
			
		||||
                <span>
 | 
			
		||||
                    <a href="{% url 'list_sessions_by_game'  data.purchase.edition.game.id %}">
 | 
			
		||||
                        <img src="{% static 'icons/game_white.png' %}" width="32" class="inline" alt="Filter by this game" title="Filter by this game" />
 | 
			
		||||
                    </a>
 | 
			
		||||
                    <a href="{% url 'list_sessions_by_edition'  data.purchase.edition.id %}">
 | 
			
		||||
                        <img src="{% static 'icons/edition_white.png' %}" width="32" class="inline" alt="Filter by this edition" title="Filter by this edition" />
 | 
			
		||||
                    </a>
 | 
			
		||||
                    <a href="{% url 'list_sessions_by_purchase' data.purchase.id %}">
 | 
			
		||||
                        <img src="{% static 'icons/purchase_white.png' %}" width="32" class="inline" alt="Filter by this purchase" title="Filter by this purchase" />
 | 
			
		||||
                    </a>
 | 
			
		||||
                </span>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="dark:text-white overflow-hidden text-ellipsis whitespace-nowrap">
 | 
			
		||||
                <a class="hover:underline" href="{% url 'list_sessions_by_platform' data.purchase.platform.id %}">
 | 
			
		||||
                    {% if data.purchase.platform != data.purchase.edition.platform %}
 | 
			
		||||
                        {{data.purchase.edition.platform}} on {{ data.purchase.platform }}
 | 
			
		||||
                    {% else %}
 | 
			
		||||
                        {{ data.purchase.platform }}
 | 
			
		||||
                    {% endif %}
 | 
			
		||||
                </a>
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="dark:text-slate-400 text-center">{{ data.timestamp_start | date:"d/m/Y H:i" }}</div>
 | 
			
		||||
            <div class="dark:text-slate-400 text-center">
 | 
			
		||||
                {% if data.unfinished %}
 | 
			
		||||
 | 
			
		||||
@ -31,6 +31,8 @@ urlpatterns = [
 | 
			
		||||
    path("add-purchase/", views.add_purchase, name="add_purchase"),
 | 
			
		||||
    path("add-edition/", views.add_edition, name="add_edition"),
 | 
			
		||||
    path("edit-edition/<int:edition_id>", views.edit_edition, name="edit_edition"),
 | 
			
		||||
    path("edit-game/<int:game_id>", views.edit_game, name="edit_game"),
 | 
			
		||||
    path("edit-platform/<int:platform_id>", views.edit_platform, name="edit_platform"),
 | 
			
		||||
    path("add-device/", views.add_device, name="add_device"),
 | 
			
		||||
    path("edit-session/<int:session_id>", views.edit_session, name="edit_session"),
 | 
			
		||||
    path("edit-purchase/<int:purchase_id>", views.edit_purchase, name="edit_purchase"),
 | 
			
		||||
@ -47,10 +49,22 @@ urlpatterns = [
 | 
			
		||||
        {"filter": "platform"},
 | 
			
		||||
        name="list_sessions_by_platform",
 | 
			
		||||
    ),
 | 
			
		||||
    path(
 | 
			
		||||
        "list-sessions/by-game/<int:game_id>",
 | 
			
		||||
        views.list_sessions,
 | 
			
		||||
        {"filter": "game"},
 | 
			
		||||
        name="list_sessions_by_game",
 | 
			
		||||
    ),
 | 
			
		||||
    path(
 | 
			
		||||
        "list-sessions/by-edition/<int:edition_id>",
 | 
			
		||||
        views.list_sessions,
 | 
			
		||||
        {"filter": "edition"},
 | 
			
		||||
        name="list_sessions_by_edition",
 | 
			
		||||
    ),
 | 
			
		||||
    path(
 | 
			
		||||
        "list-sessions/by-ownership/<str:ownership_type>",
 | 
			
		||||
        views.list_sessions,
 | 
			
		||||
        {"filter": "ownership_type"},
 | 
			
		||||
        name="list_sessions_by_ownership_type",
 | 
			
		||||
    ),
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
@ -79,6 +79,30 @@ def edit_purchase(request, purchase_id=None):
 | 
			
		||||
    return render(request, "add.html", context)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def edit_game(request, game_id=None):
 | 
			
		||||
    context = {}
 | 
			
		||||
    purchase = Game.objects.get(id=game_id)
 | 
			
		||||
    form = GameForm(request.POST or None, instance=purchase)
 | 
			
		||||
    if form.is_valid():
 | 
			
		||||
        form.save()
 | 
			
		||||
        return redirect("list_sessions")
 | 
			
		||||
    context["title"] = "Edit Game"
 | 
			
		||||
    context["form"] = form
 | 
			
		||||
    return render(request, "add.html", context)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def edit_platform(request, platform_id=None):
 | 
			
		||||
    context = {}
 | 
			
		||||
    purchase = Platform.objects.get(id=platform_id)
 | 
			
		||||
    form = PlatformForm(request.POST or None, instance=purchase)
 | 
			
		||||
    if form.is_valid():
 | 
			
		||||
        form.save()
 | 
			
		||||
        return redirect("list_sessions")
 | 
			
		||||
    context["title"] = "Edit Platform"
 | 
			
		||||
    context["form"] = form
 | 
			
		||||
    return render(request, "add.html", context)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def edit_edition(request, edition_id=None):
 | 
			
		||||
    context = {}
 | 
			
		||||
    edition = Edition.objects.get(id=edition_id)
 | 
			
		||||
@ -110,7 +134,15 @@ def delete_session(request, session_id=None):
 | 
			
		||||
    return redirect("list_sessions")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def list_sessions(request, filter="", purchase_id="", platform_id="", edition_id=""):
 | 
			
		||||
def list_sessions(
 | 
			
		||||
    request,
 | 
			
		||||
    filter="",
 | 
			
		||||
    purchase_id="",
 | 
			
		||||
    platform_id="",
 | 
			
		||||
    game_id="",
 | 
			
		||||
    edition_id="",
 | 
			
		||||
    ownership_type: str = "",
 | 
			
		||||
):
 | 
			
		||||
    context = {}
 | 
			
		||||
    context["title"] = "Sessions"
 | 
			
		||||
 | 
			
		||||
@ -123,6 +155,12 @@ def list_sessions(request, filter="", purchase_id="", platform_id="", edition_id
 | 
			
		||||
    elif filter == "edition":
 | 
			
		||||
        dataset = Session.objects.filter(purchase__edition=edition_id)
 | 
			
		||||
        context["edition"] = Edition.objects.get(id=edition_id)
 | 
			
		||||
    elif filter == "game":
 | 
			
		||||
        dataset = Session.objects.filter(purchase__edition__game=game_id)
 | 
			
		||||
        context["game"] = Game.objects.get(id=game_id)
 | 
			
		||||
    elif filter == "ownership_type":
 | 
			
		||||
        dataset = Session.objects.filter(purchase__ownership_type=ownership_type)
 | 
			
		||||
        context["ownership_type"] = dict(Purchase.OWNERSHIP_TYPES)[ownership_type]
 | 
			
		||||
    elif filter == "recent":
 | 
			
		||||
        dataset = Session.objects.filter(
 | 
			
		||||
            timestamp_start__gte=datetime.now() - timedelta(days=30)
 | 
			
		||||
@ -142,7 +180,7 @@ def list_sessions(request, filter="", purchase_id="", platform_id="", edition_id
 | 
			
		||||
    # cannot use dataset[0] here because that might be only partial QuerySet
 | 
			
		||||
    context["last"] = Session.objects.all().order_by("timestamp_start").last()
 | 
			
		||||
    # charts are always oldest->newest
 | 
			
		||||
    if Session.objects.count() >= 2:
 | 
			
		||||
    if dataset.count() >= 2:
 | 
			
		||||
        context["chart"] = playtime_over_time_chart(dataset.order_by("timestamp_start"))
 | 
			
		||||
 | 
			
		||||
    return render(request, "list_sessions.html", context)
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
[tool.poetry]
 | 
			
		||||
name = "timetracker"
 | 
			
		||||
version = "1.0.2"
 | 
			
		||||
version = "1.0.3"
 | 
			
		||||
description = "A simple time tracker."
 | 
			
		||||
authors = ["Lukáš Kucharczyk <lukas@kucharczyk.xyz>"]
 | 
			
		||||
license = "GPL"
 | 
			
		||||
 | 
			
		||||