API Timeline ajoutée !

This commit is contained in:
Olivier DOSSMANN 2017-09-17 00:04:26 +02:00
parent 95d8b30c3c
commit 92ea2f6af7
10 changed files with 200 additions and 16 deletions

View File

@ -4,7 +4,7 @@ Current version (0.2) :
- Renommage des consoles en « plateformes » - Renommage des consoles en « plateformes »
- Activation des requêtes CORS pour permettre à une autre application d'accéder à l'API - Activation des requêtes CORS pour permettre à une autre application d'accéder à l'API
- MàJ vers Django 1.11.5 - 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 - 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 - Nouveau champ 'note' pour la progression dans le jeu
- Omission de l'état "Nouveau" pour les jeux - Omission de l'état "Nouveau" pour les jeux

5
TODO
View File

@ -1,6 +1,7 @@
# À faire # À 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. * étudier la possibilité à l'utilisateur, via des variables d'environnement, de configurer un email, une autre BDD, etc.
## Dépôt / code ## Dépôt / code
@ -11,7 +12,6 @@
* Unplayed ne doit pas s'afficher si on a un état différent de Unfinished * 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.) * 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 ? * ajouter une date d'obtention du jeu vidéo (par défaut aujourd'hui) => est-ce vraiment utile si on a la Timeline ?
## Tests ## Tests
@ -30,7 +30,6 @@
## Documentation ## Documentation
* documentation API
* documentation du projet : installation par Docker, utilisation des variables d'environnement pour changer différentes choses, etc. * documentation du projet : installation par Docker, utilisation des variables d'environnement pour changer différentes choses, etc.
# Idée # Idée

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 0.1\n" "Project-Id-Version: 0.1\n"
"Report-Msgid-Bugs-To: \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" "PO-Revision-Date: 2017-09-16 17:16+0200\n"
"Last-Translator: Olivier DOSSMANN <git@dossmann.net>\n" "Last-Translator: Olivier DOSSMANN <git@dossmann.net>\n"
"Language-Team: \n" "Language-Team: \n"

View File

@ -16,7 +16,12 @@ Including another URLconf
from django.conf.urls import url, include from django.conf.urls import url, include
from django.contrib import admin from django.contrib import admin
from collection import __version__ as app_version 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 import routers
from rest_framework.documentation import include_docs_urls 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 = routers.DefaultRouter()
router.register(r'games', GameViewSet) router.register(r'games', GameViewSet)
router.register(r'platforms', PlatformViewSet) router.register(r'platforms', PlatformViewSet)
router.register(
r'game_timelines',
GameTimelineViewSet,
base_name='game_timeline')
urlpatterns = [ urlpatterns = [
url(r'^$', GameList.as_view(), name='homepage'), url(r'^$', GameList.as_view(), name='homepage'),

View File

@ -7,8 +7,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 0.1\n" "Project-Id-Version: 0.1\n"
"Report-Msgid-Bugs-To: \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:17+0200\n" "PO-Revision-Date: 2017-09-16 23:59+0200\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: \n" "Language-Team: \n"
"Language: fr\n" "Language: fr\n"
@ -55,7 +55,7 @@ msgstr "plateformes"
#: collection/games/models.py:20 #: collection/games/models.py:20
msgid "Most used platform name." 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 #: collection/games/models.py:40
msgid "Beaten" msgid "Beaten"
@ -117,14 +117,38 @@ msgstr "jeu"
msgid "games" msgid "games"
msgstr "jeux" 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" msgid "Timeline"
msgstr "Chronologie" msgstr "Chronologie"
#: collection/games/models.py:85 #: collection/games/models.py:91
msgid "Timelines" msgid "Timelines"
msgstr "Chronologies" 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 #: collection/games/templates/games/index.html:5
msgid "Games" msgid "Games"
msgstr "Jeux" msgstr "Jeux"

View File

@ -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'),
),
]

View File

@ -73,6 +73,12 @@ class Game(Item):
verbose_name_plural = _('games') 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): class Timeline(BaseTimeline):
TARGET_MODEL = 'games.Game' TARGET_MODEL = 'games.Game'
TARGET_VERBOSE_NAME = 'game' TARGET_VERBOSE_NAME = 'game'
@ -83,3 +89,10 @@ class Timeline(BaseTimeline):
ordering = ('-date',) ordering = ('-date',)
verbose_name = _('Timeline') verbose_name = _('Timeline')
verbose_name_plural = _('Timelines') 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')

View File

@ -1,4 +1,4 @@
from games.models import Game, Platform from games.models import Game, Platform, Timeline
from rest_framework import serializers 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 PlatformSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Platform model = Platform

View File

@ -1,8 +1,9 @@
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.urls import reverse 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 import status
from rest_framework.test import APITestCase, force_authenticate from rest_framework.test import APITestCase, force_authenticate
from datetime import date
import json import json
@ -112,3 +113,46 @@ class GameTest(APITestCase):
response = self.client.post(url, data, format='json') response = self.client.post(url, data, format='json')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertEqual(Game.objects.count(), 0) 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

View File

@ -1,10 +1,14 @@
from django.db.models import Q from django.db.models import Q
from django.views.generic import ListView from django.views.generic import ListView
from rest_framework import viewsets from rest_framework import viewsets
from .serializers import GameSerializer, PlatformSerializer from .serializers import (
GameSerializer,
GameTimelineSerializer,
PlatformSerializer
)
from .models import Game, Platform, Timeline from .models import Game, Platform, Timeline
class PlatformViewSet(viewsets.ModelViewSet): class PlatformViewSet(viewsets.ModelViewSet):
""" """
API endpoints that allows platforms to be edited or viewed. API endpoints that allows platforms to be edited or viewed.
@ -22,12 +26,32 @@ class PlatformViewSet(viewsets.ModelViewSet):
serializer_class = PlatformSerializer 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): class GameViewSet(viewsets.ModelViewSet):
""" """
API endpoints that allows games to be edited or viewed. API endpoints that allows games to be edited or viewed.
retrieve: retrieve:
Return the given game Return the given game.
list: list:
Return a list of all existing games ordered by name. Return a list of all existing games ordered by name.