diff --git a/CHANGELOG b/CHANGELOG index fc98dbd..1d6281f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,7 +4,7 @@ Current version (0.2) : - Renommage des consoles en « plateformes » - Activation des requêtes CORS pour permettre à une autre application d'accéder à l'API - MàJ vers Django 1.11.5 - - Activation d'une API (pour les consoles et les jeux) accessible par l'administrateur (avec documentation) + - Activation d'une API accessible par l'administrateur (avec documentation) - Ajout d'une page d'accueil listant les jeux vidéos en cours, la liste complète et les 5 dernières activités sur ces derniers triées par date - Nouveau champ 'note' pour la progression dans le jeu - Omission de l'état "Nouveau" pour les jeux diff --git a/TODO b/TODO index b15dcb6..4632229 100644 --- a/TODO +++ b/TODO @@ -1,6 +1,7 @@ # À faire - * Ajouter des help_text à tous les champs + classes enfants. Les traduire. + * Test API sur l'ordre de tri des date pour les Timeline + * Bug? : dans la documentation de l'API, les champs "required" sont peu nombreux. Regarder s'il faut faire quelque chose de particulier pour la Timeline (sur date et status) et Game (sur status). * étudier la possibilité à l'utilisateur, via des variables d'environnement, de configurer un email, une autre BDD, etc. ## Dépôt / code @@ -11,7 +12,6 @@ * Unplayed ne doit pas s'afficher si on a un état différent de Unfinished * Gérer les figurines (trouver le nom de l'objet qui permet de les regrouper, par exemple Skylanders, Disney Infinity, etc.) - * API django rest framework * ajouter une date d'obtention du jeu vidéo (par défaut aujourd'hui) => est-ce vraiment utile si on a la Timeline ? ## Tests @@ -30,7 +30,6 @@ ## Documentation - * documentation API * documentation du projet : installation par Docker, utilisation des variables d'environnement pour changer différentes choses, etc. # Idée diff --git a/collection/collection/locale/fr/LC_MESSAGES/django.po b/collection/collection/locale/fr/LC_MESSAGES/django.po index 2c687b8..73bc668 100644 --- a/collection/collection/locale/fr/LC_MESSAGES/django.po +++ b/collection/collection/locale/fr/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: 0.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-09-16 15:15+0000\n" +"POT-Creation-Date: 2017-09-16 21:58+0000\n" "PO-Revision-Date: 2017-09-16 17:16+0200\n" "Last-Translator: Olivier DOSSMANN \n" "Language-Team: \n" diff --git a/collection/collection/urls.py b/collection/collection/urls.py index 06b8baa..65dae87 100644 --- a/collection/collection/urls.py +++ b/collection/collection/urls.py @@ -16,7 +16,12 @@ Including another URLconf from django.conf.urls import url, include from django.contrib import admin from collection import __version__ as app_version -from games.views import GameList, GameViewSet, PlatformViewSet +from games.views import ( + GameList, + GameViewSet, + GameTimelineViewSet, + PlatformViewSet, +) from rest_framework import routers from rest_framework.documentation import include_docs_urls @@ -28,6 +33,10 @@ admin.site.site_header = '%s %s' % (admin.site.site_title, app_version) router = routers.DefaultRouter() router.register(r'games', GameViewSet) router.register(r'platforms', PlatformViewSet) +router.register( + r'game_timelines', + GameTimelineViewSet, + base_name='game_timeline') urlpatterns = [ url(r'^$', GameList.as_view(), name='homepage'), diff --git a/collection/conf/locale/fr/LC_MESSAGES/django.po b/collection/conf/locale/fr/LC_MESSAGES/django.po index 7e78e02..fee213e 100644 --- a/collection/conf/locale/fr/LC_MESSAGES/django.po +++ b/collection/conf/locale/fr/LC_MESSAGES/django.po @@ -7,8 +7,8 @@ msgid "" msgstr "" "Project-Id-Version: 0.1\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-09-16 15:15+0000\n" -"PO-Revision-Date: 2017-09-16 17:17+0200\n" +"POT-Creation-Date: 2017-09-16 21:58+0000\n" +"PO-Revision-Date: 2017-09-16 23:59+0200\n" "Last-Translator: \n" "Language-Team: \n" "Language: fr\n" @@ -55,7 +55,7 @@ msgstr "plateformes" #: collection/games/models.py:20 msgid "Most used platform name." -msgstr "Nom de plateforme le plus utilisé." +msgstr "Nom de plateforme le plus communément utilisé." #: collection/games/models.py:40 msgid "Beaten" @@ -117,14 +117,38 @@ msgstr "jeu" msgid "games" msgstr "jeux" -#: collection/games/models.py:84 +#: collection/games/models.py:77 +msgid "Game title" +msgstr "Titre du jeu" + +#: collection/games/models.py:78 +msgid "Game running platform" +msgstr "Plateforme sur laquelle se lance le jeu" + +#: collection/games/models.py:79 +msgid "Game progression" +msgstr "Progression du jeu" + +#: collection/games/models.py:90 msgid "Timeline" msgstr "Chronologie" -#: collection/games/models.py:85 +#: collection/games/models.py:91 msgid "Timelines" msgstr "Chronologies" +#: collection/games/models.py:95 +msgid "Status change date" +msgstr "Date de changement de statut" + +#: collection/games/models.py:96 +msgid "Which game?" +msgstr "Quel jeu ?" + +#: collection/games/models.py:98 +msgid "New status for this game at the given date" +msgstr "Nouveau statut pour ce jeu à la date donnée" + #: collection/games/templates/games/index.html:5 msgid "Games" msgstr "Jeux" diff --git a/collection/games/migrations/0009_add_help_text_for_documentation.py b/collection/games/migrations/0009_add_help_text_for_documentation.py new file mode 100644 index 0000000..a047a8f --- /dev/null +++ b/collection/games/migrations/0009_add_help_text_for_documentation.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.5 on 2017-09-16 21:55 +from __future__ import unicode_literals + +import datetime +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('games', '0008_rename_console_to_platform'), + ] + + operations = [ + migrations.AlterModelOptions( + name='platform', + options={'ordering': ('name',), 'verbose_name': 'plateforme', 'verbose_name_plural': 'plateformes'}, + ), + migrations.AlterField( + model_name='game', + name='collection', + field=models.ForeignKey(help_text='Game running platform', on_delete=django.db.models.deletion.CASCADE, related_name='games', to='games.Platform', verbose_name='plateforme'), + ), + migrations.AlterField( + model_name='game', + name='name', + field=models.CharField(help_text='Game title', max_length=255, verbose_name='nom'), + ), + migrations.AlterField( + model_name='game', + name='note', + field=models.CharField(blank=True, help_text='Courte note affichée à ceux qui vous suive.', max_length=150, null=True, verbose_name='Note de progression'), + ), + migrations.AlterField( + model_name='game', + name='status', + field=models.CharField(choices=[('created', 'Nouveau'), ('beaten', 'Terminé (quête principale)'), ('completed', 'Terminé complètement'), ('excluded', 'Exclu'), ('mastered', 'Usé / Épuisé'), ('unfinished', 'Inachevé')], default='unfinished', help_text='Game progression', max_length=30, verbose_name='état'), + ), + migrations.AlterField( + model_name='platform', + name='name', + field=models.CharField(help_text='Nom de plateforme le plus utilisé.', max_length=255, verbose_name='nom'), + ), + migrations.AlterField( + model_name='timeline', + name='date', + field=models.DateField(default=datetime.datetime.now, help_text='Status change date', verbose_name='date'), + ), + migrations.AlterField( + model_name='timeline', + name='item', + field=models.ForeignKey(help_text='Which game?', on_delete=django.db.models.deletion.CASCADE, to='games.Game', verbose_name='jeu'), + ), + migrations.AlterField( + model_name='timeline', + name='status', + field=models.CharField(choices=[('created', 'Nouveau'), ('beaten', 'Terminé (quête principale)'), ('completed', 'Terminé complètement'), ('excluded', 'Exclu'), ('mastered', 'Usé / Épuisé'), ('unfinished', 'Inachevé')], default='created', help_text='New status for this game at the given date', max_length=30, verbose_name='état'), + ), + ] diff --git a/collection/games/models.py b/collection/games/models.py index 8633e25..53fa8f3 100644 --- a/collection/games/models.py +++ b/collection/games/models.py @@ -73,6 +73,12 @@ class Game(Item): verbose_name_plural = _('games') +# Redefine help_text (for documentation, API, etc.) +Game._meta.get_field('name').help_text = _('Game title') +Game._meta.get_field('collection').help_text = _('Game running platform') +Game._meta.get_field('status').help_text = _('Game progression') + + class Timeline(BaseTimeline): TARGET_MODEL = 'games.Game' TARGET_VERBOSE_NAME = 'game' @@ -83,3 +89,10 @@ class Timeline(BaseTimeline): ordering = ('-date',) verbose_name = _('Timeline') verbose_name_plural = _('Timelines') + + +# Redefine help_text (for documentation, API, etc.) +Timeline._meta.get_field('date').help_text = _('Status change date') +Timeline._meta.get_field('item').help_text = _('Which game?') +Timeline._meta.get_field('status').help_text = _( + 'New status for this game at the given date') diff --git a/collection/games/serializers.py b/collection/games/serializers.py index 62989a0..55d4543 100644 --- a/collection/games/serializers.py +++ b/collection/games/serializers.py @@ -1,4 +1,4 @@ -from games.models import Game, Platform +from games.models import Game, Platform, Timeline from rest_framework import serializers @@ -16,6 +16,16 @@ class GameSerializer(serializers.HyperlinkedModelSerializer): ) +class GameTimelineSerializer(serializers.HyperlinkedModelSerializer): + class Meta: + model = Timeline + fields = ( + 'date', + 'item', + 'status', + ) + + class PlatformSerializer(serializers.ModelSerializer): class Meta: model = Platform diff --git a/collection/games/tests/test_api.py b/collection/games/tests/test_api.py index d6052fc..bfb3122 100644 --- a/collection/games/tests/test_api.py +++ b/collection/games/tests/test_api.py @@ -1,8 +1,9 @@ from django.contrib.auth.models import User from django.urls import reverse -from games.models import Game, Platform +from games.models import Game, Platform, Timeline from rest_framework import status from rest_framework.test import APITestCase, force_authenticate +from datetime import date import json @@ -112,3 +113,46 @@ class GameTest(APITestCase): response = self.client.post(url, data, format='json') self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(Game.objects.count(), 0) + + +class TimelineTest(APITestCase): + @classmethod + def setUpTestData(cls): + cls.superuser = User.objects.create_superuser( + 'admin', + 'admin@localhost', + 'admin') + + def test_create_timeline(self): + """ + Check we can create a timeline object. + """ + console = Platform.objects.create(name='BestPlatform4Ever') + game = Game.objects.create( + name='Cherubin', + collection=console, + status='created') + + # By default a timeline is generated with current datetime. + # We need to change this date. + timeline = Timeline.objects.first() + timeline.date = date(2017, 9, 15) + timeline.save() + + url = reverse('game_timeline-list') + data = { + 'item': reverse('game-detail', kwargs={'pk': game.id}), + 'date': date(2017, 9, 16), + 'status': 'beaten', + } + self.client.force_authenticate(user=self.superuser) + response = self.client.post(url, data, format='json') + self.assertEqual( + response.status_code, + status.HTTP_201_CREATED, + response.content) + self.assertEqual(Timeline.objects.count(), 2) + + def test_sorted_game_timeline(self): + # TODO: Check descending date order. + pass diff --git a/collection/games/views.py b/collection/games/views.py index b0fad94..5d5a063 100644 --- a/collection/games/views.py +++ b/collection/games/views.py @@ -1,10 +1,14 @@ from django.db.models import Q from django.views.generic import ListView from rest_framework import viewsets -from .serializers import GameSerializer, PlatformSerializer - +from .serializers import ( + GameSerializer, + GameTimelineSerializer, + PlatformSerializer +) from .models import Game, Platform, Timeline + class PlatformViewSet(viewsets.ModelViewSet): """ API endpoints that allows platforms to be edited or viewed. @@ -22,12 +26,32 @@ class PlatformViewSet(viewsets.ModelViewSet): serializer_class = PlatformSerializer +class GameTimelineViewSet(viewsets.ModelViewSet): + """ + API endpoints that allows to keep games life. + BE CAREFUL. You shouldn't change these entries because they're generated + by Game status change. It could be only useful to change the acquisition + date. Not more. + + retrieve: + Return the given timeline entry. + + list: + Return a list of timeline entries sorted by date in descending order. + + create: + Create a new timeline instance. + """ + queryset = Timeline.objects.all().order_by('-date') + serializer_class = GameTimelineSerializer + + class GameViewSet(viewsets.ModelViewSet): """ API endpoints that allows games to be edited or viewed. retrieve: - Return the given game + Return the given game. list: Return a list of all existing games ordered by name.