From 9b0def75b78977f5aa81a8521d8fb2bf2a62bea3 Mon Sep 17 00:00:00 2001 From: Olivier DOSSMANN Date: Mon, 4 Sep 2017 23:42:57 +0200 Subject: [PATCH] Nouvelle API : MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Avec * API sur les consoles en utilisant Django Rest Framework * Tri par nom de console * Nouveaux tests sur l'API * Documentation pour l'API * Ajout d'une description au champ "name" pour Console et sa traduction * Nouvelles dépendances à coreapi et djangorestframework --- collection/collection/components/api.py | 8 ++++ collection/collection/components/common.py | 1 + collection/collection/settings.py | 1 + collection/collection/urls.py | 13 +++++- .../conf/locale/fr/LC_MESSAGES/django.po | 46 ++++++++++--------- collection/games/models.py | 4 ++ collection/games/serializers.py | 8 ++++ collection/games/tests/test_api.py | 45 ++++++++++++++++++ collection/games/views.py | 20 +++++++- requirements.txt | 2 + 10 files changed, 125 insertions(+), 23 deletions(-) create mode 100644 collection/collection/components/api.py create mode 100644 collection/games/serializers.py create mode 100644 collection/games/tests/test_api.py diff --git a/collection/collection/components/api.py b/collection/collection/components/api.py new file mode 100644 index 0000000..8c99e41 --- /dev/null +++ b/collection/collection/components/api.py @@ -0,0 +1,8 @@ +# API - Django Rest Framework +REST_FRAMEWORK = { + 'DEFAULT_PERMISSION_CLASSES': [ + 'rest_framework.permissions.IsAdminUser', + ], + 'TEST_REQUEST_DEFAULT_FORMAT': 'json', + 'UNICODE_JSON': True, +} diff --git a/collection/collection/components/common.py b/collection/collection/components/common.py index a0532e3..4d4eb25 100644 --- a/collection/collection/components/common.py +++ b/collection/collection/components/common.py @@ -26,6 +26,7 @@ INSTALLED_APPS = [ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'rest_framework', 'core', 'games.apps.GamesConfig', ] diff --git a/collection/collection/settings.py b/collection/collection/settings.py index 1cf75c8..1f5ade1 100644 --- a/collection/collection/settings.py +++ b/collection/collection/settings.py @@ -18,6 +18,7 @@ base_settings = [ 'components/common.py', # standard django settings 'components/database.py', # SQLite 3 'components/i18n.py', # Internationalisation and localization + 'components/api.py', # API (django rest framework) # Select the right env: 'environments/%s.py' % ENV, diff --git a/collection/collection/urls.py b/collection/collection/urls.py index d011295..ed2ba35 100644 --- a/collection/collection/urls.py +++ b/collection/collection/urls.py @@ -16,14 +16,25 @@ 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 +from games.views import GameList, ConsoleViewSet +from rest_framework import routers +from rest_framework.documentation import include_docs_urls # Admin config admin.site.site_title = 'OpenBackloggery' admin.site.site_header = '%s %s' % (admin.site.site_title, app_version) +# Django Rest Framework router +router = routers.DefaultRouter() +router.register(r'consoles', ConsoleViewSet) + urlpatterns = [ url(r'^$', GameList.as_view(), name='homepage'), url(r'^games/', include('games.urls', namespace='games'), name='games'), url(r'^admin/', admin.site.urls), + url(r'^api/v1/', include(router.urls), name='api'), + url(r'^api/v1/docs/', include_docs_urls(title=' '.join( + [admin.site.site_title, + 'API']))), + url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')) ] diff --git a/collection/conf/locale/fr/LC_MESSAGES/django.po b/collection/conf/locale/fr/LC_MESSAGES/django.po index fafdf6f..e051876 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-08-31 14:49+0000\n" -"PO-Revision-Date: 2017-08-31 16:50+0200\n" +"POT-Creation-Date: 2017-09-04 21:24+0000\n" +"PO-Revision-Date: 2017-09-04 23:27+0200\n" "Last-Translator: \n" "Language-Team: \n" "Language: fr\n" @@ -45,7 +45,7 @@ msgstr "Information du jeu" msgid "Progress" msgstr "Progression" -#: games/models.py:15 games/models.py:25 +#: games/models.py:15 games/models.py:29 msgid "console" msgstr "console" @@ -53,71 +53,75 @@ msgstr "console" msgid "consoles" msgstr "consoles" -#: games/models.py:36 +#: games/models.py:20 +msgid "Most used console name." +msgstr "Nom de console le plus utilisé." + +#: games/models.py:40 msgid "Beaten" msgstr "Terminé (quête principale)" -#: games/models.py:37 +#: games/models.py:41 msgid "Completed" msgstr "Terminé complètement" -#: games/models.py:38 +#: games/models.py:42 msgid "Excluded" msgstr "Exclu" -#: games/models.py:39 +#: games/models.py:43 msgid "Mastered" msgstr "Usé / Épuisé" -#: games/models.py:40 +#: games/models.py:44 msgid "Unfinished" msgstr "Inachevé" -#: games/models.py:49 +#: games/models.py:53 msgid "Progress note" msgstr "Note de progression" -#: games/models.py:50 +#: games/models.py:54 msgid "Short note displayed to your followers." msgstr "Courte note affichée à ceux qui vous suive" -#: games/models.py:55 +#: games/models.py:59 msgid "playing?" msgstr "en train d'y jouer ?" -#: games/models.py:56 +#: games/models.py:60 msgid "You're currently playing this game." msgstr "Vous jouez actuellement à ce jeu." -#: games/models.py:59 +#: games/models.py:63 msgid "unplayed?" msgstr "jamais joué ?" -#: games/models.py:60 +#: games/models.py:64 msgid "You never played this game." msgstr "Vous n'avez jamais joué à ce jeu." -#: games/models.py:63 +#: games/models.py:67 msgid "wish?" msgstr "envie de l'avoir ?" -#: games/models.py:64 +#: games/models.py:68 msgid "You're waiting X-mas father offers you this game." msgstr "Vous patientez que le père Noël vous offre ce jeu." -#: games/models.py:68 +#: games/models.py:72 msgid "game" msgstr "jeu" -#: games/models.py:69 +#: games/models.py:73 msgid "games" msgstr "jeux" -#: games/models.py:80 +#: games/models.py:84 msgid "Timeline" msgstr "Chronologie" -#: games/models.py:81 +#: games/models.py:85 msgid "Timelines" msgstr "Chronologies" @@ -145,6 +149,6 @@ msgstr "Aucun jeu." msgid "Memory Card" msgstr "Carte mémoire" -#: games/templates/games/index.html:38 +#: games/templates/games/index.html:43 msgid "Empty memory." msgstr "Mémoire vide." diff --git a/collection/games/models.py b/collection/games/models.py index 0acc296..739c5ab 100644 --- a/collection/games/models.py +++ b/collection/games/models.py @@ -16,6 +16,10 @@ class Console(Collection): verbose_name_plural = _('consoles') +# Redefine help_text (for documentation, API, etc.) +Console._meta.get_field('name').help_text = _('Most used console name.') + + class Game(Item): """ A video game you will use on a specific Console. diff --git a/collection/games/serializers.py b/collection/games/serializers.py new file mode 100644 index 0000000..6cc4b7c --- /dev/null +++ b/collection/games/serializers.py @@ -0,0 +1,8 @@ +from games.models import Console +from rest_framework import serializers + + +class ConsoleSerializer(serializers.ModelSerializer): + class Meta: + model = Console + fields = ('name',) diff --git a/collection/games/tests/test_api.py b/collection/games/tests/test_api.py new file mode 100644 index 0000000..bf9f531 --- /dev/null +++ b/collection/games/tests/test_api.py @@ -0,0 +1,45 @@ +from django.contrib.auth.models import User +from django.urls import reverse +from games.models import Console +from rest_framework import status +from rest_framework.test import APITestCase, force_authenticate +import json + + +class ConsoleTest(APITestCase): + + @classmethod + def setUpTestData(cls): + cls.superuser = User.objects.create_superuser( + 'admin', + 'admin@localhost', + 'admin') + + def test_create_console(self): + """ + Check we can create a console object. + """ + url = reverse('console-list') + data = {'name': 'GP2X'} + self.client.force_authenticate(user=self.superuser) + response = self.client.post(url, data, format='json') + self.assertEqual(response.status_code, status.HTTP_201_CREATED) + self.assertEqual(Console.objects.count(), 1) + self.assertEqual(Console.objects.get().name, 'GP2X') + + def test_sorted_console(self): + """ + Check that console list is sorted. + """ + Console.objects.create(name='GP2X') + Console.objects.create(name='3DS') + Console.objects.create(name='Game Boy') + Console.objects.create(name='Amiga') + url = reverse('console-list') + self.client.force_authenticate(user=self.superuser) + response = self.client.get(url, format='json') + sorted_consoles = list( + Console.objects.all().order_by('name').values_list( + 'name', flat=True)) + consoles = [x.get('name') for x in json.loads(response.content)] + self.assertEqual(consoles, sorted_consoles) diff --git a/collection/games/views.py b/collection/games/views.py index 742b985..5eea4e4 100644 --- a/collection/games/views.py +++ b/collection/games/views.py @@ -1,7 +1,25 @@ from django.db.models import Q from django.views.generic import ListView +from rest_framework import viewsets +from .serializers import ConsoleSerializer -from .models import Game, Timeline +from .models import Console, Game, Timeline + +class ConsoleViewSet(viewsets.ModelViewSet): + """ + API endpoints that allows consoles to be edited or viewed. + + retrieve: + Return the given console + + list: + Return a list of all existing consoles ordered by name. + + create: + Create a new console instance. + """ + queryset = Console.objects.all().order_by('name') + serializer_class = ConsoleSerializer class GameList(ListView): diff --git a/requirements.txt b/requirements.txt index 7b2ed4b..fa1e38f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,5 @@ Django==1.11.4 django-split-settings==0.2.5 PyYAML==3.12 uWSGI==2.0.15 +djangorestframework==3.6.4 +coreapi==2.3.1