Compare commits
80 Commits
Author | SHA1 | Date |
---|---|---|
Olivier DOSSMANN | 16deac8849 | |
Olivier DOSSMANN | 56c198f19a | |
Olivier DOSSMANN | 445a769ce5 | |
Olivier DOSSMANN | c9e9ec46fa | |
Olivier DOSSMANN | a5bcbf0c7e | |
Olivier DOSSMANN | 286c075ea8 | |
Olivier DOSSMANN | 2e7123989f | |
Olivier DOSSMANN | 2db9340f3d | |
Olivier DOSSMANN | bb9869df73 | |
Olivier DOSSMANN | 16847d1856 | |
Olivier DOSSMANN | 0c3a9b3be7 | |
Olivier DOSSMANN | 801d26cb4a | |
Olivier DOSSMANN | 52d56926d4 | |
Olivier DOSSMANN | 5e1c32a2ff | |
Olivier DOSSMANN | bb52c3c19b | |
Olivier DOSSMANN | 00e4b78fe1 | |
Olivier DOSSMANN | 5a2ba5c16a | |
Olivier DOSSMANN | d43af2278e | |
Olivier DOSSMANN | 00d34f819d | |
Olivier DOSSMANN | 090376f1e3 | |
Olivier DOSSMANN | 110add5b0e | |
Olivier DOSSMANN | b83f00609a | |
Olivier DOSSMANN | f60d7d98fd | |
Olivier DOSSMANN | 64ef1b8086 | |
Olivier DOSSMANN | 4dfe7f4285 | |
Olivier DOSSMANN | 083d10fc6e | |
Olivier DOSSMANN | 63bed8818e | |
Olivier DOSSMANN | eab50caca9 | |
Olivier DOSSMANN | b0c9f7ebec | |
Olivier DOSSMANN | a0e365f596 | |
Olivier DOSSMANN | a86810b451 | |
Olivier DOSSMANN | c70673b8b1 | |
Olivier DOSSMANN | f2d7a2dfd8 | |
Olivier DOSSMANN | 91cc57f166 | |
Olivier DOSSMANN | a6dcad77d9 | |
Olivier DOSSMANN | 50f0361b91 | |
Olivier DOSSMANN | dcc11b994f | |
Olivier DOSSMANN | 03498d93e7 | |
Olivier DOSSMANN | bb51eb68cd | |
Olivier DOSSMANN | c74413e3b4 | |
Olivier DOSSMANN | 20a6d25a8b | |
Olivier DOSSMANN | 0536b16d6d | |
Olivier DOSSMANN | a5ff2326f7 | |
Olivier DOSSMANN | 619cd8d326 | |
Olivier DOSSMANN | aa01a0d8db | |
Olivier DOSSMANN | 92ea2f6af7 | |
Olivier DOSSMANN | 95d8b30c3c | |
Olivier DOSSMANN | 1845ae9eac | |
Olivier DOSSMANN | 8dec41073f | |
Olivier DOSSMANN | b0978034b5 | |
Olivier DOSSMANN | 0380bc9071 | |
Olivier DOSSMANN | 8847c351c1 | |
Olivier DOSSMANN | ea2d3d0c43 | |
Olivier DOSSMANN | 63e1425b00 | |
Olivier DOSSMANN | d93a3a024f | |
Olivier DOSSMANN | 1612cdae6e | |
Olivier DOSSMANN | 7d83a4a59f | |
Olivier DOSSMANN | 9b0def75b7 | |
Olivier DOSSMANN | ff228ab845 | |
Olivier DOSSMANN | c3491b7975 | |
Olivier DOSSMANN | 88ceb57a09 | |
Olivier DOSSMANN | be6b7bfc99 | |
Olivier DOSSMANN | ed9c81f0d9 | |
Olivier DOSSMANN | ecaf40ba41 | |
Olivier DOSSMANN | b71c1c7baa | |
Olivier DOSSMANN | 5867d70111 | |
Olivier DOSSMANN | 5ba6193d4b | |
Olivier DOSSMANN | c55fdf3e38 | |
Olivier DOSSMANN | 6319f24499 | |
Olivier DOSSMANN | 84e1baa64b | |
Olivier DOSSMANN | 99ed5d574c | |
Olivier DOSSMANN | ae1a3edd99 | |
Olivier DOSSMANN | 96de9e859c | |
Olivier DOSSMANN | fdc4e36da7 | |
Olivier DOSSMANN | bd326cd846 | |
Olivier DOSSMANN | 8afd6dec6e | |
Olivier DOSSMANN | 67afac3d45 | |
Olivier DOSSMANN | 00afb10702 | |
Olivier DOSSMANN | 5820bdc63a | |
Olivier DOSSMANN | 04708e6286 |
|
@ -0,0 +1 @@
|
|||
*.mo
|
|
@ -0,0 +1,12 @@
|
|||
image: python:3.6-stretch
|
||||
|
||||
stages:
|
||||
- test
|
||||
|
||||
test-openbackloggery:
|
||||
type: test
|
||||
script:
|
||||
- apt-get update -qy
|
||||
- apt-get install -y python3-dev python3-pip
|
||||
- pip3 install -r requirements.txt
|
||||
- cd collection && python3 manage.py test
|
|
@ -0,0 +1,40 @@
|
|||
# See http://pre-commit.com for more information
|
||||
# See http://pre-commit.com/hooks.html for more hooks
|
||||
repos:
|
||||
- repo: git://github.com/asottile/reorder_python_imports
|
||||
sha: v0.3.5
|
||||
hooks:
|
||||
- id: reorder-python-imports
|
||||
- repo: https://github.com/pre-commit/mirrors-yapf
|
||||
sha: v0.18.0
|
||||
hooks:
|
||||
- id: yapf
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
sha: v0.9.2
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
name: Supprime espaces fin de ligne
|
||||
files: .py$
|
||||
- id: end-of-file-fixer
|
||||
name: Règle problème sur les fins de fichiers
|
||||
- id: check-yaml
|
||||
name: Syntaxe des fichiers YAML
|
||||
- id: check-json
|
||||
name: Syntaxe des fichiers JSON
|
||||
- id: debug-statements
|
||||
name: Supprime les PDB restants
|
||||
- id: detect-private-key
|
||||
name: Trouve les clefs privées
|
||||
- id: flake8
|
||||
name: Vérification code Python avec flake8
|
||||
args: ["--exclude=.git, __pycache__"]
|
||||
- id: requirements-txt-fixer
|
||||
name: Réordonne le fichier requirements.txt
|
||||
- repo: https://github.com/pre-commit/mirrors-jshint
|
||||
sha: v2.9.5
|
||||
hooks:
|
||||
- id: jshint
|
||||
- repo: https://github.com/Lucas-C/pre-commit-hooks-safety
|
||||
sha: v1.1.0
|
||||
hooks:
|
||||
- id: python-safety-dependencies-check
|
|
@ -0,0 +1,27 @@
|
|||
Current version (0.2) :
|
||||
|
||||
- Nom court pour les plateformes et les collections de figurines
|
||||
- Nouvelle collection de figurines
|
||||
- Tests automatiques chez Gitlab
|
||||
- Bug corrigé : Génération de 2 timelines si on crée un jeu avec l'état "created"
|
||||
- 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.9
|
||||
- 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
|
||||
- Traduction de l'interface en Français
|
||||
- Aide contextuelle sur l'interface admin d'un jeu vidéo
|
||||
|
||||
Version 0.1 :
|
||||
|
||||
- Application de la licence EUPL v1.2
|
||||
- Utilisation possible de l'application par Docker
|
||||
- Interface web d'administration
|
||||
- Liste de jeux joués
|
||||
- Liste de ses jeux souhaités
|
||||
- Historique des états d'un jeu vidéo, y compris sa création
|
||||
- État d'un jeu vidéo parmi : Unfinished, beaten, completed, excluded, mastered, new
|
||||
- Création de jeux vidéos liés à une console
|
||||
- Création de consoles de jeux
|
|
@ -9,13 +9,16 @@ COPY requirements.txt $APPS_DIR/
|
|||
WORKDIR $APPS_DIR
|
||||
|
||||
# uWSGI requires linux-headers
|
||||
# django-split-settings needs ca-certificates as they changed on PyPi
|
||||
RUN set -ex \
|
||||
&& buildDeps=' \
|
||||
build-base \
|
||||
ca-certificates \
|
||||
linux-headers \
|
||||
python3-dev \
|
||||
' \
|
||||
&& apk --no-cache --update add \
|
||||
gettext \
|
||||
mailcap \
|
||||
python3 \
|
||||
$buildDeps \
|
||||
|
|
67
README.md
67
README.md
|
@ -13,17 +13,54 @@ Logiciel web permettant de :
|
|||
[![Python 3.6](https://img.shields.io/badge/python-3.6-green.svg)](http://python.org/)
|
||||
[![Django 1.11](https://img.shields.io/badge/django-1.11-green.svg)](http://djangoproject.com/)
|
||||
[![SQLite 3](https://img.shields.io/badge/SQLite-3-green.svg)](http://sqlite.org/)
|
||||
[![django REST framework 3](https://img.shields.io/badge/django_REST_framework-3-green.svg)](http://www.django-rest-framework.org/)
|
||||
|
||||
# Installation
|
||||
|
||||
En s'appuyant sur [Docker](https://www.docker.com/), l'installation se déroule en plusieurs étapes :
|
||||
Choisissez l'une des méthodes suivantes :
|
||||
|
||||
* la création d'une image Docker
|
||||
* la création d'un dossier pour la base de données et la génération de cette dernière
|
||||
* la création d'un utilisateur administrateur de la base de données (l'utilisateur principal)
|
||||
* le lancement d'un conteneur Docker permettant d'accéder à l'interface Web de l'application
|
||||
* en utilisant **pip**, le gestionnaire de paquets Python. À utiliser en local ou en utilisant les [fameux environnements virtuels Python](http://sametmax.com/les-environnement-virtuels-python-virtualenv-et-virtualenvwrapper/)
|
||||
* ou en s'appuyant sur [Docker](https://www.docker.com/)
|
||||
|
||||
## Création de l'image Docker
|
||||
## En utilisant pip
|
||||
|
||||
Vous devez disposer de **pip** sur votre machine, par exemple sur Debian/Ubuntu, il suffit de lancer la commande suivante :
|
||||
|
||||
```bash
|
||||
sudo apt install python3-pip
|
||||
```
|
||||
|
||||
Ensuite on utilise les dépendances Python propres au projet :
|
||||
|
||||
```bash
|
||||
cd openbackloggery
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
On génère la base de données et on la rempli :
|
||||
|
||||
```bash
|
||||
cd collection
|
||||
python3 manage.py migrate
|
||||
python3 manage.py loaddata initial
|
||||
python3 manage.py createsuperuser --user=superadmin --email=superadmin@domaine.tld
|
||||
```
|
||||
|
||||
Remplacez **superadmin** par un identifiant que vous souhaiteriez avoir. Et mettez l'adresse courriel que vous voulez (car pour l'instant l'application n'utilise pas le courriel).
|
||||
|
||||
Il ne vous reste plus qu'à lancer l'application :
|
||||
|
||||
```bash
|
||||
python3 manage.py runserver
|
||||
```
|
||||
|
||||
et d'y accéder à l'adresse suivante : http://127.0.0.1:8000/admin/.
|
||||
|
||||
Pensez à jouer quand même ! ^_^
|
||||
|
||||
## En utilisant Docker
|
||||
|
||||
Avec Docker nous allons procéder en plusieurs étapes :
|
||||
|
||||
Nous appelerons notre image Docker **openbackloggery**. Nous considéréons que l'application se trouver dans un dossier nommé **openbackloggery** dans lequel se trouve un fichier *Dockerfile*.
|
||||
|
||||
|
@ -32,8 +69,6 @@ cd openbackloggery
|
|||
docker build -t openbackloggery:0.1 .
|
||||
```
|
||||
|
||||
## Base de données
|
||||
|
||||
Actuellement nous utilisons SQLite3 comme base de données car les fonctionnalités et la quantité de données ne requièrent pas un gestionnaire de base de données plus conséquent.
|
||||
|
||||
```bash
|
||||
|
@ -42,9 +77,7 @@ docker run -it --rm -v /openbackloggery_db:/opt/apps/db openbackloggery:0.1 pyth
|
|||
docker run -it --rm -v /openbackloggery_db:/opt/apps/db openbackloggery:0.1 python3 manage.py loaddata initial
|
||||
```
|
||||
|
||||
*loaddata initial* permet de charger quelques données initiales comme une liste de consoles par exemple.
|
||||
|
||||
## Création d'un utilisateur admin
|
||||
*loaddata initial* permet de charger quelques données initiales comme une liste de plateformes par exemple.
|
||||
|
||||
Adaptez la ligne en remplaçant **admin** par le nom d'utilisateur que vous voulez et **admin@domaine.tld** par l'adresse courriel de votre choix (qu'elle existe ou non importe peu pour l'instant car elle n'est pas utilisée).
|
||||
|
||||
|
@ -52,8 +85,6 @@ Adaptez la ligne en remplaçant **admin** par le nom d'utilisateur que vous voul
|
|||
docker run -it --rm -v /openbackloggery_db:/opt/apps/db openbackloggery:0.1 python3 manage.py createsuperuser --user=admin --email=admin@domaine.tld
|
||||
```
|
||||
|
||||
## Lancement du conteneur Docker
|
||||
|
||||
Le service se lancera sur le **port 8282** et aura pour clé secrète **abcdefghijk** :
|
||||
|
||||
```bash
|
||||
|
@ -62,10 +93,16 @@ docker run -d -e SECRET_KEY='abcdefghijk' -v /openbackloggery_db:/opt/apps/db -p
|
|||
|
||||
Veillez à modifier la clé secrète par une chaîne de caractère aléatoire assez longue et parsemée de majuscules, minuscules, ponctuation, etc.
|
||||
|
||||
## Accès à l'application
|
||||
|
||||
Une fois le conteneur Docker lancé, il suffit d'ouvrir un navigateur Internet et d'y taper l'adresse suivante : http://127.0.0.1:8282/admin/.
|
||||
|
||||
# Adresses réticulaires disponibles
|
||||
|
||||
Les adresses à connaître :
|
||||
|
||||
* '/admin/' : Interface d'administration
|
||||
* '/api/v1/' : Adresse de l'API RESTful
|
||||
* '/api/v1/docs' : documentation de l'API
|
||||
|
||||
# Licence
|
||||
|
||||
Ce logiciel est concédé sous [licence EUPL, version 1.2 uniquement](https://joinup.ec.europa.eu/community/eupl/og_page/eupl-text-11-12).
|
||||
|
|
47
TODO
47
TODO
|
@ -1,13 +1,38 @@
|
|||
# À faire
|
||||
|
||||
* Gérer les figurines (trouver le nom de l'objet qui permet de les regrouper, par exemple Skylanders, Disney Infinity, etc.)
|
||||
* Tests sur Console
|
||||
* Tests sur Game
|
||||
* Données initiales des consoles connues
|
||||
* API django rest framework
|
||||
* documentation API
|
||||
* enlever force_authenticate des tests (en dépendances) puisque ça utiliser self.client
|
||||
* Trouver des états supplémentaires aux Figurines (complétée ?)
|
||||
* Ajout des Timeline sur les Figurines
|
||||
* Figurine API + tests
|
||||
* étudier la possibilité à l'utilisateur, via des variables d'environnement, de configurer un email, une autre BDD, etc.
|
||||
|
||||
## Dépôt / code
|
||||
|
||||
* README : Déplacer les informations d'installation de Docker dans une documentation autre
|
||||
|
||||
## Fonctionnalités
|
||||
|
||||
* Ajouter la liste des aptitudes (Ability) d'une figurine
|
||||
* Unplayed ne doit pas s'afficher si on a un état différent de Unfinished
|
||||
* ajouter une date d'obtention du jeu vidéo (par défaut aujourd'hui) => est-ce vraiment utile si on a la Timeline ?
|
||||
|
||||
## Tests
|
||||
|
||||
* Sur Platform
|
||||
* Sur Game
|
||||
* API : sur Game, pour les champs wish, unplayed, playing et status
|
||||
* travis.yml to launch test
|
||||
* ajouter une date d'obtention du jeu vidéo (par défaut aujourd'hui)
|
||||
* Selenium pour vérifier :
|
||||
* que Nouveau n'apparaisse pas dans la liste des états possibles
|
||||
* que Nouveau n'apparaisse pas dans les filtres
|
||||
|
||||
## Données
|
||||
|
||||
* Données initiales des consoles connues
|
||||
|
||||
## Documentation
|
||||
|
||||
* documentation du projet : installation par Docker, utilisation des variables d'environnement pour changer différentes choses, etc.
|
||||
|
||||
# Idée
|
||||
|
||||
|
@ -15,3 +40,11 @@
|
|||
* Faire une API en vue d'un site web en VueJS ?
|
||||
* Inclure une récupération sur HowLongToBeat ?
|
||||
* Gérer plusieurs utilisateurs ? Si oui, comment gérer la liste des jeux par utilisateur ? => à faire dans l'interface web peut-être ?
|
||||
* Proposer de jouer un jeu aléatoire parmi la Liste des jeux jamais joués ou détenus (mais pas dans la wish list)
|
||||
* Proposer un système de gestion de prêt à une personne ? Je prête à quelqu'un, je note la date de début de prêt. Et je note juste quand je l'ai récupéré => historique des prêts
|
||||
* changer de nom :
|
||||
* kolektor (super-héros qui collecte)
|
||||
* Lambda Collection (les collections pour les utilisateurs Lambda)
|
||||
* Kolegeektor (ou collegeektor)
|
||||
* Accumulation, monceau, monticule (en anglais : the hill = petite colline), barda (attirail très lourd sur le dos)
|
||||
* backloggery = la boutique aux backlogs, c'est à dire la boutique des accumulations. Le magasin d'accumulation. C'est notre "Caverne d'Ali Baba". Boutique à souvenirs. La pile de tâches à faire.
|
||||
|
|
|
@ -1 +1 @@
|
|||
__version__ = '0.1'
|
||||
__version__ = '0.2-trunk'
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
# API - Django Rest Framework
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_PERMISSION_CLASSES': [
|
||||
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly',
|
||||
],
|
||||
'TEST_REQUEST_DEFAULT_FORMAT': 'json',
|
||||
'UNICODE_JSON': True,
|
||||
}
|
||||
|
||||
# CORS
|
||||
CORS_ORIGIN_ALLOW_ALL = False # disallow all website for cross site requests
|
||||
|
||||
# Authorized website for cross site requests
|
||||
CORS_ORIGIN_WHITELIST = (
|
||||
'localhost:8000',
|
||||
'127.0.0.1:8000'
|
||||
)
|
|
@ -0,0 +1,98 @@
|
|||
"""
|
||||
Django settings for collection project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 1.11.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/1.11/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/1.11/ref/settings/
|
||||
"""
|
||||
import os
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
DEBUG = False
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = (
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'rest_framework',
|
||||
'corsheaders',
|
||||
'core',
|
||||
'games.apps.GamesConfig',
|
||||
'figurines.apps.FigurinesConfig', )
|
||||
|
||||
MIDDLEWARE = (
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.locale.LocaleMiddleware',
|
||||
'corsheaders.middleware.CorsMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware', )
|
||||
|
||||
ROOT_URLCONF = 'collection.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'collection.wsgi.application'
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME':
|
||||
'django.contrib.auth.password_validation.\
|
||||
UserAttributeSimilarityValidator',
|
||||
},
|
||||
{
|
||||
'NAME':
|
||||
'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
},
|
||||
{
|
||||
'NAME':
|
||||
'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME':
|
||||
'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
},
|
||||
]
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/1.11/howto/static-files/
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
STATIC_ROOT = os.path.join(os.path.abspath(os.path.curdir), 'static')
|
||||
if os.getenv('STATIC_ROOT', None):
|
||||
STATIC_ROOT = os.path.abspath(os.getenv('STATIC_ROOT'))
|
||||
|
||||
STATICFILES_FINDERS = (
|
||||
'django.contrib.staticfiles.finders.FileSystemFinder',
|
||||
'django.contrib.staticfiles.finders.AppDirectoriesFinder', )
|
|
@ -0,0 +1,15 @@
|
|||
# Database
|
||||
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
|
||||
|
||||
DB_DIR = os.getenv('DB_DIR', os.path.join(BASE_DIR, 'db'))
|
||||
|
||||
if not os.path.exists(DB_DIR):
|
||||
os.makedirs(DB_DIR)
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': os.path.join(BASE_DIR, 'db', 'db.sqlite3'),
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/1.11/topics/i18n/
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
LANGUAGE_CODE = os.getenv('LANGUAGE_CODE', 'fr')
|
||||
|
||||
LANGUAGES = (
|
||||
('fr', _('French')),
|
||||
('en', _('English')), )
|
||||
|
||||
TIME_ZONE = 'UTC'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_L10N = True
|
||||
|
||||
USE_TZ = False
|
||||
|
||||
# Translation directories
|
||||
LOCALE_PATHS = [
|
||||
os.path.join(BASE_DIR, 'conf/locale'),
|
||||
os.path.join(BASE_DIR, 'collection/locale')
|
||||
]
|
|
@ -0,0 +1,18 @@
|
|||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = 'tqma23#v!#ecse_gz_u(1oa6+x%1uyi718an9%nefqhi$0q_eg'
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = ['*']
|
||||
|
||||
# CORS: allow all site to make cross site requests
|
||||
CORS_ORIGIN_ALLOW_ALL = True
|
||||
|
||||
MIDDLEWARE += ('debug_toolbar.middleware.DebugToolbarMiddleware', )
|
||||
|
||||
# Add Django debug toolbar
|
||||
INSTALLED_APPS += ('debug_toolbar', )
|
||||
INTERNAL_IPS = ['127.0.0.1']
|
|
@ -0,0 +1,45 @@
|
|||
"""
|
||||
Django production settings
|
||||
"""
|
||||
|
||||
DEBUG = os.getenv('DEBUG', False)
|
||||
|
||||
SECRET_KEY = os.getenv('SECRET_KEY')
|
||||
|
||||
ALLOWED_HOSTS = [os.getenv('ALLOWED_HOSTS', '*')]
|
||||
|
||||
# Domain configuration:
|
||||
# - if you have a specific domain to secure, change ALLOWED_HOSTS like this:
|
||||
# ALLOWED_HOSTS = ['domain.tld', 'domain.tld.']
|
||||
# - if you use a proxy like Nginx, you need to add this line into config:
|
||||
# `proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;`
|
||||
USE_X_FORWARDED_HOST = True
|
||||
|
||||
# CORS: allow all site to make cross site requests
|
||||
CORS_ORIGIN_ALLOW_ALL = True
|
||||
|
||||
# CORS: limit to API only
|
||||
CORS_URLS_REGEX = r'^/api/.*$'
|
||||
|
||||
# WARNING: you need to install psycopg2 with pip
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
||||
'NAME': os.getenv('POSTGRES_DB', 'postgres'),
|
||||
'USER': os.getenv('POSTGRES_USER', 'postgres'),
|
||||
'PASSWORD': os.getenv('POSTGRES_PASS', 'postgres'),
|
||||
'HOST': os.getenv('POSTGRES_HOST', '127.0.0.1'),
|
||||
'PORT': os.getenv('POSTGRES_PORT', '5432'),
|
||||
}
|
||||
}
|
||||
|
||||
# Security
|
||||
SECURE_HSTS_SECONDS = 3600
|
||||
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
|
||||
SECURE_HSTS_PRELOAD = True
|
||||
SECURE_CONTENT_TYPE_NOSNIFF = True
|
||||
SECURE_BROWSER_XSS_FILTER = True
|
||||
SECURE_SSL_REDIRECT = True
|
||||
SESSION_COOKIE_SECURE = True
|
||||
CSRF_COOKIE_SECURE = True
|
||||
X_FRAME_OPTIONS = 'DENY'
|
|
@ -0,0 +1,26 @@
|
|||
# OpenBackloggery.
|
||||
# Copyright (C) 2017
|
||||
# This file is distributed under the same license as the openbackloggery package.
|
||||
# Olivier DOSSMANN <git@dossmann.net>, 2017.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 0.1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-01-15 20:10+0000\n"
|
||||
"PO-Revision-Date: 2017-09-16 17:16+0200\n"
|
||||
"Last-Translator: Olivier DOSSMANN <git@dossmann.net>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: fr\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 2.0.3\n"
|
||||
|
||||
#: collection/components/i18n.py:8
|
||||
msgid "French"
|
||||
msgstr "Français"
|
||||
|
||||
#: collection/components/i18n.py:9
|
||||
msgid "English"
|
||||
msgstr "Anglais"
|
|
@ -1,18 +0,0 @@
|
|||
"""
|
||||
Django production settings
|
||||
"""
|
||||
import os
|
||||
from collection.settings import * # NOQA
|
||||
|
||||
DEBUG = os.getenv('DEBUG', False)
|
||||
|
||||
SECRET_KEY = os.getenv('SECRET_KEY')
|
||||
|
||||
ALLOWED_HOSTS = [os.getenv('ALLOWED_HOSTS', '*')]
|
||||
|
||||
# Domain configuration:
|
||||
# - if you have a specific domain to secure, change ALLOWED_HOSTS like this:
|
||||
# ALLOWED_HOSTS = ['domain.tld', 'domain.tld.']
|
||||
# - if you use a proxy like Nginx, you need to add this line into config:
|
||||
# `proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;`
|
||||
USE_X_FORWARDED_HOST = True
|
|
@ -1,125 +1,30 @@
|
|||
"""
|
||||
Django settings for collection project.
|
||||
This is a django-split-settings main file.
|
||||
For more information read this:
|
||||
https://github.com/sobolevn/django-split-settings
|
||||
|
||||
Generated by 'django-admin startproject' using Django 1.11.
|
||||
Default environment is `developement`.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/1.11/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/1.11/ref/settings/
|
||||
To change settings file:
|
||||
`DJANGO_ENV=production python manage.py runserver`
|
||||
"""
|
||||
|
||||
import os
|
||||
from split_settings.tools import optional, include
|
||||
from os import environ
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
ENV = environ.get('DJANGO_ENV') or 'development'
|
||||
|
||||
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) + CORS
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = 'tqma23#v!#ecse_gz_u(1oa6+x%1uyi718an9%nefqhi$0q_eg'
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'core',
|
||||
'games.apps.GamesConfig',
|
||||
# Select the right env:
|
||||
'environments/%s.py' % ENV,
|
||||
# Optionally override some settings:
|
||||
optional('environments/local_settings.py'),
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'collection.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'collection.wsgi.application'
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': os.path.join(BASE_DIR, 'db', 'db.sqlite3'),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/1.11/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
|
||||
TIME_ZONE = 'UTC'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_L10N = False
|
||||
|
||||
USE_TZ = False
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/1.11/howto/static-files/
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
STATIC_ROOT = os.path.join(os.path.abspath(os.path.curdir), 'static')
|
||||
if os.getenv('STATIC_ROOT', None):
|
||||
STATIC_ROOT = os.path.abspath(os.getenv('STATIC_ROOT'))
|
||||
# Include settings:
|
||||
include(*base_settings)
|
||||
|
|
|
@ -13,15 +13,49 @@ Including another URLconf
|
|||
1. Import the include() function: from django.conf.urls import url, include
|
||||
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.conf import settings
|
||||
from django.conf.urls import include
|
||||
from django.conf.urls import url
|
||||
from django.contrib import admin
|
||||
from collection import __version__ as app_version
|
||||
from django.views.generic import TemplateView
|
||||
from figurines.views import FigurineViewSet
|
||||
from figurines.views import SetViewSet
|
||||
from games.views import GameTimelineViewSet
|
||||
from games.views import GameViewSet
|
||||
from games.views import PlatformViewSet
|
||||
from rest_framework import routers
|
||||
from rest_framework.documentation import include_docs_urls
|
||||
|
||||
from collection import __version__ as app_version
|
||||
|
||||
# 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'games', GameViewSet)
|
||||
router.register(r'figurines', FigurineViewSet)
|
||||
router.register(r'platforms', PlatformViewSet)
|
||||
router.register(r'sets', SetViewSet)
|
||||
router.register(
|
||||
r'game_timelines', GameTimelineViewSet, base_name='game_timeline')
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^$',
|
||||
TemplateView.as_view(template_name="core/homepage.html"),
|
||||
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'))
|
||||
]
|
||||
|
||||
if settings.DEBUG:
|
||||
import debug_toolbar
|
||||
urlpatterns = [
|
||||
url(r'^__debug__/', include(debug_toolbar.urls)),
|
||||
] + urlpatterns
|
||||
|
|
|
@ -0,0 +1,272 @@
|
|||
# OpenBackloggery.
|
||||
# Copyright (C) 2017
|
||||
# This file is distributed under the same license as the openbackloggery package.
|
||||
# Olivier DOSSMANN <git@dossmann.net>, 2017.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: 0.1\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2018-02-19 21:17+0000\n"
|
||||
"PO-Revision-Date: 2018-02-19 22:18+0100\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: \n"
|
||||
"Language: fr\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: Poedit 2.0.6\n"
|
||||
|
||||
#: core/models.py:9 core/models.py:29
|
||||
msgid "name"
|
||||
msgstr "nom"
|
||||
|
||||
#: core/models.py:10
|
||||
msgid "shortname"
|
||||
msgstr "nom court"
|
||||
|
||||
#: core/models.py:26
|
||||
msgid "New"
|
||||
msgstr "Nouveau"
|
||||
|
||||
#: core/models.py:30
|
||||
msgid "achievement"
|
||||
msgstr "progression"
|
||||
|
||||
#: core/models.py:31
|
||||
msgid "out of"
|
||||
msgstr "sur"
|
||||
|
||||
#: core/models.py:51 core/models.py:84
|
||||
msgid "status"
|
||||
msgstr "état"
|
||||
|
||||
#: core/models.py:70
|
||||
msgid "date"
|
||||
msgstr "date"
|
||||
|
||||
#: figurines/admin.py:9
|
||||
msgid "Information"
|
||||
msgstr "Information"
|
||||
|
||||
#: figurines/admin.py:11 games/admin.py:65
|
||||
msgid "Progress"
|
||||
msgstr "Progression"
|
||||
|
||||
#: figurines/models.py:17 figurines/models.py:32
|
||||
msgid "set"
|
||||
msgstr "collection"
|
||||
|
||||
#: figurines/models.py:18
|
||||
msgid "sets"
|
||||
msgstr "collections"
|
||||
|
||||
#: figurines/models.py:22
|
||||
msgid "Usual name of figurines set."
|
||||
msgstr "Nom habituel de la collection de figurines."
|
||||
|
||||
#: figurines/models.py:47
|
||||
msgid "Character"
|
||||
msgstr "Personnage"
|
||||
|
||||
#: figurines/models.py:48
|
||||
msgid "Vehicle"
|
||||
msgstr "Véhicule"
|
||||
|
||||
#: figurines/models.py:49
|
||||
msgid "Level pack"
|
||||
msgstr "Niveau supplémentaire"
|
||||
|
||||
#: figurines/models.py:50
|
||||
msgid "Gadget"
|
||||
msgstr "Gadget"
|
||||
|
||||
#: figurines/models.py:51
|
||||
msgid "Battle pack"
|
||||
msgstr "Arène supplémentaire"
|
||||
|
||||
#: figurines/models.py:52
|
||||
msgid "Expansion pack"
|
||||
msgstr "Extension"
|
||||
|
||||
#: figurines/models.py:53
|
||||
msgid "Trophy"
|
||||
msgstr "Trophé"
|
||||
|
||||
#: figurines/models.py:54
|
||||
msgid "Trap"
|
||||
msgstr "Piège"
|
||||
|
||||
#: figurines/models.py:59
|
||||
msgid "kind"
|
||||
msgstr "sorte"
|
||||
|
||||
#: figurines/models.py:63 games/models.py:67
|
||||
msgid "wish?"
|
||||
msgstr "envie de l'avoir ?"
|
||||
|
||||
#: figurines/models.py:64
|
||||
msgid "You need this figurine."
|
||||
msgstr "Vous avez besoin de cette figurine."
|
||||
|
||||
#: figurines/models.py:68
|
||||
msgid "coins"
|
||||
msgstr "monnaie"
|
||||
|
||||
#: figurines/models.py:75
|
||||
msgid "Figurine denomination"
|
||||
msgstr "Dénomination de la figurine"
|
||||
|
||||
#: figurines/models.py:76
|
||||
msgid "Becoming set"
|
||||
msgstr "Collection d'où cela provient"
|
||||
|
||||
#: figurines/models.py:77
|
||||
msgid "Figurine progression"
|
||||
msgstr "Progression de la figurine"
|
||||
|
||||
#: games/admin.py:17
|
||||
msgid "state"
|
||||
msgstr "état"
|
||||
|
||||
#: games/admin.py:40 games/admin.py:87 games/models.py:15 games/models.py:29
|
||||
msgid "platform"
|
||||
msgstr "plateforme"
|
||||
|
||||
#: games/admin.py:63
|
||||
msgid "Game Information"
|
||||
msgstr "Information du jeu"
|
||||
|
||||
#: games/admin.py:79 games/admin.py:117
|
||||
msgid "Platform"
|
||||
msgstr "Plateforme"
|
||||
|
||||
#: games/models.py:16
|
||||
msgid "platforms"
|
||||
msgstr "plateformes"
|
||||
|
||||
#: games/models.py:20
|
||||
msgid "Most used platform name."
|
||||
msgstr "Nom de plateforme le plus communément utilisé."
|
||||
|
||||
#: games/models.py:40
|
||||
msgid "Beaten"
|
||||
msgstr "Terminé (quête principale)"
|
||||
|
||||
#: games/models.py:41
|
||||
msgid "Completed"
|
||||
msgstr "Terminé complètement"
|
||||
|
||||
#: games/models.py:42
|
||||
msgid "Excluded"
|
||||
msgstr "Exclu"
|
||||
|
||||
#: games/models.py:43
|
||||
msgid "Mastered"
|
||||
msgstr "Usé / Épuisé"
|
||||
|
||||
#: games/models.py:44
|
||||
msgid "Unfinished"
|
||||
msgstr "Inachevé"
|
||||
|
||||
#: games/models.py:53
|
||||
msgid "Progress note"
|
||||
msgstr "Note de progression"
|
||||
|
||||
#: games/models.py:54
|
||||
msgid "Short note displayed to your followers."
|
||||
msgstr "Courte note affichée à ceux qui vous suive."
|
||||
|
||||
#: games/models.py:59
|
||||
msgid "playing?"
|
||||
msgstr "en train d'y jouer ?"
|
||||
|
||||
#: games/models.py:60
|
||||
msgid "You're currently playing this game."
|
||||
msgstr "Vous jouez actuellement à ce jeu."
|
||||
|
||||
#: games/models.py:63
|
||||
msgid "unplayed?"
|
||||
msgstr "jamais joué ?"
|
||||
|
||||
#: games/models.py:64
|
||||
msgid "You never played this game."
|
||||
msgstr "Vous n'avez jamais joué à ce jeu."
|
||||
|
||||
#: 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:72
|
||||
msgid "game"
|
||||
msgstr "jeu"
|
||||
|
||||
#: games/models.py:73
|
||||
msgid "games"
|
||||
msgstr "jeux"
|
||||
|
||||
#: games/models.py:77
|
||||
msgid "Game title"
|
||||
msgstr "Titre du jeu"
|
||||
|
||||
#: games/models.py:78
|
||||
msgid "Game running platform"
|
||||
msgstr "Plateforme sur laquelle se lance le jeu"
|
||||
|
||||
#: games/models.py:79
|
||||
msgid "Game progression"
|
||||
msgstr "Progression du jeu"
|
||||
|
||||
#: games/models.py:90
|
||||
msgid "Timeline"
|
||||
msgstr "Chronologie"
|
||||
|
||||
#: games/models.py:91
|
||||
msgid "Timelines"
|
||||
msgstr "Chronologies"
|
||||
|
||||
#: games/models.py:95
|
||||
msgid "Status change date"
|
||||
msgstr "Date de changement de statut"
|
||||
|
||||
#: games/models.py:96
|
||||
msgid "Which game?"
|
||||
msgstr "Quel jeu ?"
|
||||
|
||||
#: games/models.py:98
|
||||
msgid "New status for this game at the given date"
|
||||
msgstr "Nouveau statut pour ce jeu à la date donnée"
|
||||
|
||||
#: games/templates/games/index.html:5
|
||||
msgid "Games"
|
||||
msgstr "Jeux"
|
||||
|
||||
#: games/templates/games/index.html:13
|
||||
msgid "No game found."
|
||||
msgstr "Aucun jeu."
|
||||
|
||||
#: games/templates/games/summary.html:5
|
||||
msgid "My summary"
|
||||
msgstr "Mon résumé"
|
||||
|
||||
#: games/templates/games/summary.html:6
|
||||
msgid "Now playing"
|
||||
msgstr "En train d'y jouer"
|
||||
|
||||
#: games/templates/games/summary.html:15
|
||||
msgid "No playing game found."
|
||||
msgstr "Aucun jeu en cours."
|
||||
|
||||
#: games/templates/games/summary.html:17
|
||||
msgid "Memory Card"
|
||||
msgstr "Carte mémoire"
|
||||
|
||||
#: games/templates/games/summary.html:33
|
||||
msgid "Empty memory."
|
||||
msgstr "Mémoire vide."
|
||||
|
||||
#~ msgid "Complete list"
|
||||
#~ msgstr "Liste complète"
|
||||
|
||||
#~ msgid "created"
|
||||
#~ msgstr "créé"
|
|
@ -1,10 +1,13 @@
|
|||
from datetime import datetime
|
||||
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
|
||||
class Collection(models.Model):
|
||||
name = models.CharField(max_length=255)
|
||||
name = models.CharField(
|
||||
max_length=255, verbose_name=_('name'), unique=True)
|
||||
shortname = models.CharField(max_length=30, verbose_name=_('shortname'))
|
||||
|
||||
def __str__(self):
|
||||
return '%s' % self.name
|
||||
|
@ -20,12 +23,12 @@ class Item(models.Model):
|
|||
|
||||
# status choices
|
||||
CREATED = 'created'
|
||||
STATUS_CHOICES = (
|
||||
(CREATED, _('New')),
|
||||
)
|
||||
STATUS_CHOICES = ((CREATED, _('New')), )
|
||||
DEFAULT_CHOICE = CREATED
|
||||
|
||||
name = models.CharField(max_length=255)
|
||||
name = models.CharField(max_length=255, verbose_name=_('name'))
|
||||
achievement = models.PositiveIntegerField(blank=True, null=True, verbose_name=_('achievement'))
|
||||
achievement_max = models.PositiveIntegerField(blank=True, null=True, verbose_name=_('out of'))
|
||||
|
||||
def __str__(self):
|
||||
return '%s' % self.name
|
||||
|
@ -39,13 +42,16 @@ class Item(models.Model):
|
|||
target_field = models.ForeignKey(
|
||||
cls.TARGET_MODEL,
|
||||
related_name=cls.RELATED_TARGET_NAME,
|
||||
verbose_name=cls.TARGET_VERBOSE_NAME)
|
||||
verbose_name=_(cls.TARGET_VERBOSE_NAME))
|
||||
target_field.contribute_to_class(cls, 'collection')
|
||||
status_field = models.CharField(
|
||||
max_length=30,
|
||||
choices=Item.STATUS_CHOICES + cls.STATUS_CHOICES,
|
||||
default=cls.DEFAULT_CHOICE)
|
||||
default=cls.DEFAULT_CHOICE,
|
||||
verbose_name=_('status'))
|
||||
status_field.contribute_to_class(cls, 'status')
|
||||
# check that no other same item name on same collection exists
|
||||
cls._meta.unique_together = (('collection', 'name'), )
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
@ -58,29 +64,30 @@ class Timeline(models.Model):
|
|||
For an example, a game cretion date. Or when you completed a game.
|
||||
"""
|
||||
TARGET_MODEL = None
|
||||
TARGET_VERBOSE_NAME = None
|
||||
STATUS_CHOICES = Item.STATUS_CHOICES
|
||||
DEFAULT_CHOICE = Item.CREATED
|
||||
date = models.DateTimeField(default=datetime.now)
|
||||
date = models.DateField(default=datetime.now, verbose_name=_('date'))
|
||||
|
||||
@classmethod
|
||||
def on_class_prepared(cls):
|
||||
"""
|
||||
Add new field 'item' which is a link to TARGET_MODEL
|
||||
"""
|
||||
target_field = models.ForeignKey(cls.TARGET_MODEL)
|
||||
target_field = models.ForeignKey(
|
||||
cls.TARGET_MODEL, verbose_name=_(cls.TARGET_VERBOSE_NAME))
|
||||
target_field.contribute_to_class(cls, 'item')
|
||||
status_field = models.CharField(
|
||||
max_length=30,
|
||||
choices=Item.STATUS_CHOICES + cls.STATUS_CHOICES,
|
||||
default=cls.DEFAULT_CHOICE)
|
||||
default=cls.DEFAULT_CHOICE,
|
||||
verbose_name=_('status'))
|
||||
status_field.contribute_to_class(cls, 'status')
|
||||
|
||||
def __str__(self):
|
||||
return '%s: %s - %s' % (
|
||||
self.date.strftime('%Y-%m-%d'),
|
||||
self.status,
|
||||
self.item)
|
||||
return '%s: %s - %s' % (self.date.strftime('%Y-%m-%d'), self.status,
|
||||
self.item)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
ordering = ('-date',)
|
||||
ordering = ('-date', )
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
Eventually by HTML5 UP
|
||||
html5up.net | @ajlkn
|
||||
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
|
||||
*/
|
||||
|
||||
/* BG */
|
||||
|
||||
#bg {
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=25)";
|
||||
}
|
||||
|
||||
/* Type */
|
||||
|
||||
body, input, select, textarea {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Form */
|
||||
|
||||
input[type="text"],
|
||||
input[type="password"],
|
||||
input[type="email"],
|
||||
select,
|
||||
textarea {
|
||||
border: solid 2px #fff;
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
Eventually by HTML5 UP
|
||||
html5up.net | @ajlkn
|
||||
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
|
||||
*/
|
||||
|
||||
/* Signup Form */
|
||||
|
||||
#signup-form > * {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 382 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 870 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.5 MiB |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE html>
|
||||
{% load i18n %}{% get_current_language as LANGUAGE_CODE %}<html lang="{{ LANGUAGE_CODE }}">
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
|
||||
<title>OpenBackloggery</title>
|
||||
</head>
|
||||
<body>
|
||||
{% block content %}
|
||||
{% endblock %}
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,43 @@
|
|||
{% load static %}
|
||||
|
||||
<!DOCTYPE HTML>
|
||||
<!--
|
||||
Eventually by HTML5 UP
|
||||
html5up.net | @ajlkn
|
||||
Free for personal and commercial use under the CCA 3.0 license (html5up.net/license)
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<title>OpenBackloggery</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
|
||||
<!--[if lte IE 8]><script src="{% static 'js/ie/html5shiv.js' %}"></script><![endif]-->
|
||||
<link rel="stylesheet" href="{% static 'css/main.css' %}" />
|
||||
<!--[if lte IE 8]><link rel="stylesheet" href="{% static 'css/ie8.css' %}" /><![endif]-->
|
||||
<!--[if lte IE 9]><link rel="stylesheet" href="{% static 'css/ie9.css' %}" /><![endif]-->
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Header -->
|
||||
<header id="header">
|
||||
<h1>OpenBackloggery</h1>
|
||||
<p>Just track games and figurine collection<br />
|
||||
through a website.</p>
|
||||
</header>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer id="footer">
|
||||
<ul class="icons">
|
||||
<li><a href="https://forge.o9.re/olivier/openbackloggery" class="icon fa-git-square"><span class="label">GitHub</span></a></li>
|
||||
</ul>
|
||||
<ul class="copyright">
|
||||
<li>© OpenBackloggery.</li><li>Credits: <a href="http://unsplash.com/">Unsplash</a> + <a href="http://html5up.net">HTML5 UP</a></li>
|
||||
</ul>
|
||||
</footer>
|
||||
|
||||
<!-- Scripts -->
|
||||
<!--[if lte IE 8]><script src="{% static 'js/ie/respond.min.js' %}"></script><![endif]-->
|
||||
<script src="{% static 'js/main.js' %}"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,25 @@
|
|||
from django.contrib import admin
|
||||
from django.utils.translation import ugettext as _
|
||||
from figurines.models import Figurine
|
||||
from figurines.models import Set
|
||||
|
||||
|
||||
class FigurineAdmin(admin.ModelAdmin):
|
||||
list_display = ('name', 'kind', 'achievement', 'coins', 'collection')
|
||||
fieldsets = [(_('Information'), {
|
||||
'fields': [('name', 'kind', 'collection')]
|
||||
}), (_('Progress'), {
|
||||
'fields': [('achievement', 'achievement_max', 'coins')]
|
||||
}), ('', {
|
||||
'fields': [('wish')]
|
||||
})]
|
||||
list_filter = ('collection', 'kind')
|
||||
search_fields = ('name', )
|
||||
|
||||
|
||||
class SetAdmin(admin.ModelAdmin):
|
||||
list_display = ('shortname', 'name')
|
||||
|
||||
|
||||
admin.site.register(Set, SetAdmin)
|
||||
admin.site.register(Figurine, FigurineAdmin)
|
|
@ -0,0 +1,5 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class FigurinesConfig(AppConfig):
|
||||
name = 'figurines'
|
|
@ -0,0 +1,20 @@
|
|||
- model: figurines.set
|
||||
pk: 1
|
||||
fields:
|
||||
name: Amiibo
|
||||
shortname: Amiibo
|
||||
- model: figurines.set
|
||||
pk: 2
|
||||
fields:
|
||||
name: Disney Infinity
|
||||
shortname: Infinity
|
||||
- model: figurines.set
|
||||
pk: 3
|
||||
fields:
|
||||
name: Lego Dimensions
|
||||
shortname: LD
|
||||
- model: figurines.set
|
||||
pk: 4
|
||||
fields:
|
||||
name: "Skylanders Serie 1: Spyro's adventure"
|
||||
shortname: Skylanders
|
|
@ -0,0 +1,43 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.5 on 2017-09-18 19:11
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Figurine',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(help_text='Figurine denomination', max_length=255, verbose_name='nom')),
|
||||
('wish', models.BooleanField(default=False, help_text='You need this figurine.', verbose_name='wish?')),
|
||||
('status', models.CharField(choices=[('created', 'New'), ('created', 'New')], default='created', help_text='Figurine progression', max_length=30, verbose_name='status')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Set',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(help_text='Usual name of figurines set.', max_length=255, verbose_name='nom')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'set',
|
||||
'verbose_name_plural': 'sets',
|
||||
'ordering': ('name',),
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='figurine',
|
||||
name='collection',
|
||||
field=models.ForeignKey(help_text='Becoming set', on_delete=django.db.models.deletion.CASCADE, related_name='figurines', to='figurines.Set', verbose_name='set'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,23 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.9 on 2018-01-15 17:30
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('figurines', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='set',
|
||||
name='shortname',
|
||||
field=models.CharField(
|
||||
default='TODO: Add shortname',
|
||||
max_length=30,
|
||||
verbose_name='shortname'), ),
|
||||
]
|
|
@ -0,0 +1,31 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.9 on 2018-01-16 17:56
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('figurines', '0002_auto_20180115_1730'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='figurine',
|
||||
options={'ordering': ('name', )}, ),
|
||||
migrations.AlterField(
|
||||
model_name='set',
|
||||
name='name',
|
||||
field=models.CharField(
|
||||
help_text='Usual name of figurines set.',
|
||||
max_length=255,
|
||||
unique=True,
|
||||
verbose_name='name'), ),
|
||||
migrations.AlterField(
|
||||
model_name='set',
|
||||
name='shortname',
|
||||
field=models.CharField(max_length=30, verbose_name='shortname'), ),
|
||||
]
|
|
@ -0,0 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.9 on 2018-01-20 20:54
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('figurines', '0003_auto_20180116_1756'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='figurine',
|
||||
name='kind',
|
||||
field=models.CharField(choices=[('character', 'Character'), ('vehicle', 'Vehicle'), ('world', 'World'), ('gadget', 'Gadget')], default='character', max_length=30, verbose_name='kind'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,28 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.9 on 2018-01-21 11:06
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('figurines', '0004_add_figurines_kind'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='figurine',
|
||||
name='achievement_max',
|
||||
field=models.PositiveIntegerField(blank=True, null=True,
|
||||
verbose_name='out of'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='figurine',
|
||||
name='achievement',
|
||||
field=models.PositiveIntegerField(blank=True, null=True,
|
||||
verbose_name='achievement'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,22 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.9 on 2018-01-21 14:40
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('figurines', '0005_add_achievements'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='figurine',
|
||||
name='coins',
|
||||
field=models.PositiveIntegerField(default=0, null=True, blank=True,
|
||||
verbose_name='coins'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,35 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.9 on 2018-02-04 19:34
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('figurines', '0006_add_coins'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='figurine',
|
||||
name='kind',
|
||||
field=models.CharField(
|
||||
choices=[('character', 'Character'), ('vehicle', 'Vehicle'),
|
||||
('world', 'Level pack'), ('gadget', 'Gadget'),
|
||||
('battle', 'Battle pack'), ('expansion',
|
||||
'Expansion pack'),
|
||||
('trophy', 'Trophy'), ('trap', 'Trap')],
|
||||
default='character',
|
||||
max_length=30,
|
||||
verbose_name='kind'), ),
|
||||
migrations.AlterField(
|
||||
model_name='figurine',
|
||||
name='name',
|
||||
field=models.CharField(
|
||||
help_text='Figurine denomination',
|
||||
max_length=255,
|
||||
verbose_name='name'), ),
|
||||
]
|
|
@ -0,0 +1,77 @@
|
|||
from core.models import Collection
|
||||
from core.models import Item
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
|
||||
class Set(Collection):
|
||||
"""
|
||||
Common name used to describe a set of characters, objects or vehicles
|
||||
"""
|
||||
|
||||
def __str__(self):
|
||||
return '%s' % self.name
|
||||
|
||||
class Meta:
|
||||
ordering = ('name', )
|
||||
verbose_name = _('set')
|
||||
verbose_name_plural = _('sets')
|
||||
|
||||
|
||||
# Redefine help_text (for documentation, API, etc.)
|
||||
Set._meta.get_field('name').help_text = _('Usual name of figurines set.')
|
||||
|
||||
|
||||
class Figurine(Item):
|
||||
"""
|
||||
A character, a gadget, a vehicle or a world used on a Game to use specific
|
||||
levels, abilities, etc.
|
||||
"""
|
||||
# class config
|
||||
TARGET_MODEL = 'figurines.Set'
|
||||
TARGET_VERBOSE_NAME = _('set')
|
||||
RELATED_TARGET_NAME = 'figurines'
|
||||
|
||||
# No more status choices than "CREATED"
|
||||
|
||||
# Figurines can be Character, vehicle, world or gadget (weapon)
|
||||
CHARACTER = 'character'
|
||||
VEHICLE = 'vehicle'
|
||||
WORLD = 'world'
|
||||
GADGET = 'gadget'
|
||||
BATTLE = 'battle'
|
||||
EXPANSION = 'expansion'
|
||||
TROPHY = 'trophy'
|
||||
TRAP = 'trap'
|
||||
KIND_CHOICES = (
|
||||
(CHARACTER, _('Character')),
|
||||
(VEHICLE, _('Vehicle')),
|
||||
(WORLD, _('Level pack')),
|
||||
(GADGET, _('Gadget')),
|
||||
(BATTLE, _('Battle pack')),
|
||||
(EXPANSION, _('Expansion pack')),
|
||||
(TROPHY, _('Trophy')),
|
||||
(TRAP, _('Trap')), )
|
||||
kind = models.CharField(
|
||||
max_length=30,
|
||||
choices=KIND_CHOICES,
|
||||
default=CHARACTER,
|
||||
verbose_name=_('kind'))
|
||||
|
||||
wish = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=_('wish?'),
|
||||
help_text=_('You need this figurine.'))
|
||||
|
||||
# How money does your figurine have?
|
||||
coins = models.PositiveIntegerField(
|
||||
default=0, null=True, blank=True, verbose_name=_('coins'))
|
||||
|
||||
class Meta:
|
||||
ordering = ('name', )
|
||||
|
||||
|
||||
# Redefine help_text (for documentation, API, etc.)
|
||||
Figurine._meta.get_field('name').help_text = _('Figurine denomination')
|
||||
Figurine._meta.get_field('collection').help_text = _('Becoming set')
|
||||
Figurine._meta.get_field('status').help_text = _('Figurine progression')
|
|
@ -0,0 +1,22 @@
|
|||
from figurines.models import Figurine
|
||||
from figurines.models import Set
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
class SetSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Set
|
||||
fields = ('name', 'shortname')
|
||||
|
||||
|
||||
class FigurineSerializer(serializers.HyperlinkedModelSerializer):
|
||||
class Meta:
|
||||
model = Figurine
|
||||
fields = (
|
||||
'collection',
|
||||
'name',
|
||||
'kind',
|
||||
'achievement',
|
||||
'achievement_max',
|
||||
'coins',
|
||||
'wish', )
|
|
@ -0,0 +1,86 @@
|
|||
import json
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.urls import reverse
|
||||
from figurines.models import Figurine
|
||||
from figurines.models import Set
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
|
||||
class SetTest(APITestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.superuser = User.objects.create_superuser(
|
||||
'admin', 'admin@localhost', 'admin')
|
||||
|
||||
def test_create_set(self):
|
||||
"""
|
||||
Check we can create a set object.
|
||||
"""
|
||||
url = reverse('set-list')
|
||||
data = {'name': 'Skypanzers', 'shortname': 'SP'}
|
||||
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(Set.objects.count(), 1)
|
||||
self.assertEqual(Set.objects.get().name, 'Skypanzers')
|
||||
|
||||
def test_sorted_set(self):
|
||||
"""
|
||||
Check that set list is sorted.
|
||||
"""
|
||||
Set.objects.create(name='Skypanzers')
|
||||
Set.objects.create(name='Liibo')
|
||||
Set.objects.create(name='Skypanzers2')
|
||||
Set.objects.create(name='Amimoche')
|
||||
url = reverse('set-list')
|
||||
self.client.force_authenticate(user=self.superuser)
|
||||
response = self.client.get(url, format='json')
|
||||
sorted_sets = list(Set.objects.all().order_by('name')
|
||||
.values_list('name', flat=True))
|
||||
sets = [x.get('name') for x in json.loads(response.content)]
|
||||
self.assertEqual(sets, sorted_sets)
|
||||
|
||||
|
||||
class FigurineTest(APITestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.superuser = User.objects.create_superuser(
|
||||
'admin', 'admin@localhost', 'admin')
|
||||
|
||||
def test_create_figurine(self):
|
||||
"""
|
||||
Check we can create a figurine object.
|
||||
"""
|
||||
# Figurine comes from a specific Set
|
||||
collection = Set.objects.create(name='Skypanzers')
|
||||
collection_url = reverse('set-detail', kwargs={'pk': collection.id})
|
||||
|
||||
url = reverse('figurine-list')
|
||||
data = {
|
||||
'name': 'Posséïdon',
|
||||
'collection': collection_url,
|
||||
}
|
||||
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(Figurine.objects.count(), 1)
|
||||
self.assertEqual(Figurine.objects.get().name, 'Posséïdon')
|
||||
self.assertEqual(Figurine.objects.get().collection_id, collection.id)
|
||||
|
||||
def test_sorted_figurines(self):
|
||||
"""
|
||||
Check that figurine list is sorted.
|
||||
"""
|
||||
collection = Set.objects.create(name='Amimoche')
|
||||
Figurine.objects.create(name='Posséïdon', collection=collection)
|
||||
Figurine.objects.create(name='Veronica', collection=collection)
|
||||
Figurine.objects.create(name='Quintrépide', collection=collection)
|
||||
url = reverse('figurine-list')
|
||||
self.client.force_authenticate(user=self.superuser)
|
||||
response = self.client.get(url, format='json')
|
||||
sorted_figurines = list(Figurine.objects.all().order_by('name')
|
||||
.values_list('name', flat=True))
|
||||
figurines = [x.get('name') for x in json.loads(response.content)]
|
||||
self.assertEqual(figurines, sorted_figurines)
|
|
@ -0,0 +1,17 @@
|
|||
from django.test import TestCase
|
||||
from figurines.models import Set
|
||||
|
||||
|
||||
class SetTest(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
Set.objects.create(name='DimDim')
|
||||
Set.objects.create(name='Rainbow')
|
||||
Set.objects.create(name='Mystery')
|
||||
|
||||
|
||||
def test_sorted_sets(self):
|
||||
sets = list(Set.objects.all().values_list('name', flat=True))
|
||||
sorted_sets = list(
|
||||
Set.objects.all().order_by('name').values_list('name', flat=True))
|
||||
self.assertEqual(sets, sorted_sets)
|
|
@ -0,0 +1,40 @@
|
|||
from figurines.models import Figurine
|
||||
from figurines.models import Set
|
||||
from rest_framework import viewsets
|
||||
|
||||
from .serializers import FigurineSerializer
|
||||
from .serializers import SetSerializer
|
||||
|
||||
|
||||
class FigurineViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
API endpoints that allows figurines to be edited or viewed.
|
||||
|
||||
retrieve:
|
||||
Return the given figurine
|
||||
|
||||
list:
|
||||
Return a list of all existing figurines ordered by name.
|
||||
|
||||
create:
|
||||
Create a new figurine instance.
|
||||
"""
|
||||
queryset = Figurine.objects.all().order_by('name')
|
||||
serializer_class = FigurineSerializer
|
||||
|
||||
|
||||
class SetViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
API endpoints that allows to edit or view sets.
|
||||
|
||||
retrieve:
|
||||
Return the given set
|
||||
|
||||
list:
|
||||
Return a list of all existing sets ordered by name.
|
||||
|
||||
create:
|
||||
Create a new set instance.
|
||||
"""
|
||||
queryset = Set.objects.all().order_by('name')
|
||||
serializer_class = SetSerializer
|
|
@ -1,22 +1,123 @@
|
|||
from django.contrib import admin
|
||||
from games.models import Console, Game, Timeline
|
||||
from django.utils.translation import ugettext as _
|
||||
from games.forms import GameForm
|
||||
from games.models import Game
|
||||
from games.models import Platform
|
||||
from games.models import Timeline
|
||||
|
||||
|
||||
class PlatformAdmin(admin.ModelAdmin):
|
||||
list_display = ('shortname', 'name')
|
||||
|
||||
|
||||
class StatusFilter(admin.SimpleListFilter):
|
||||
"""
|
||||
Remove CREATED status.
|
||||
"""
|
||||
title = _('state')
|
||||
parameter_name = 'status'
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
"""
|
||||
Return only games choices (not CREATED from Item abstract class).
|
||||
"""
|
||||
return Game.STATUS_CHOICES
|
||||
|
||||
def queryset(self, request, queryset):
|
||||
"""
|
||||
Filter on 'status' field.
|
||||
"""
|
||||
if self.value():
|
||||
return queryset.filter(status=self.value())
|
||||
else:
|
||||
return queryset
|
||||
|
||||
|
||||
class PlatformFilter(admin.SimpleListFilter):
|
||||
"""
|
||||
Display Platform shortname.
|
||||
"""
|
||||
title = _('platform')
|
||||
parameter_name = 'collection'
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
"""
|
||||
Get all platforms
|
||||
"""
|
||||
return [(x.id, x.shortname) for x in Platform.objects.all()]
|
||||
|
||||
def queryset(self, request, queryset):
|
||||
"""
|
||||
Filter on 'shortname' field.
|
||||
"""
|
||||
if self.value():
|
||||
return queryset.filter(collection__id=self.value())
|
||||
else:
|
||||
return queryset
|
||||
|
||||
|
||||
class GameAdmin(admin.ModelAdmin):
|
||||
list_display = (
|
||||
'name', 'playing', 'status', 'wish')
|
||||
list_filter = [
|
||||
'status',
|
||||
'playing',
|
||||
'wish']
|
||||
search_fields = ('name',)
|
||||
list_display = ('name', 'get_platform', 'playing', 'status', 'wish')
|
||||
list_filter = [StatusFilter, PlatformFilter, 'playing', 'wish']
|
||||
search_fields = ('name', )
|
||||
fieldsets = [(_('Game Information'), {
|
||||
'fields': [('name', 'collection')]
|
||||
}), (_('Progress'), {
|
||||
'fields': [('status'), ('achievement', 'achievement_max'), ('note')]
|
||||
}), ('', {
|
||||
'fields': [('playing'), ('unplayed'), ('wish')]
|
||||
})]
|
||||
|
||||
form = GameForm
|
||||
|
||||
def get_platform(self, obj):
|
||||
"""
|
||||
Display platform shortname
|
||||
"""
|
||||
return obj.collection.shortname
|
||||
|
||||
get_platform.short_description = _('Platform')
|
||||
get_platform.admin_order_field = 'collection__shortname'
|
||||
|
||||
|
||||
class TimelinePlatformFilter(admin.SimpleListFilter):
|
||||
"""
|
||||
Display Platform shortname.
|
||||
"""
|
||||
title = _('platform')
|
||||
parameter_name = 'collection'
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
"""
|
||||
Get all platforms
|
||||
"""
|
||||
return [(x.id, x.shortname) for x in Platform.objects.all()]
|
||||
|
||||
def queryset(self, request, queryset):
|
||||
"""
|
||||
Filter on 'shortname' field.
|
||||
"""
|
||||
if self.value():
|
||||
return queryset.filter(item__collection__id=self.value())
|
||||
else:
|
||||
return queryset
|
||||
|
||||
|
||||
class TimelineAdmin(admin.ModelAdmin):
|
||||
list_display = (
|
||||
'date', 'status', 'item')
|
||||
list_display = ('date', 'status', 'item', 'get_platform')
|
||||
search_fields = ('item__name', )
|
||||
list_filter = (TimelinePlatformFilter, )
|
||||
|
||||
def get_platform(self, obj):
|
||||
"""
|
||||
Display platform shortname
|
||||
"""
|
||||
return obj.item.collection.shortname
|
||||
|
||||
get_platform.short_description = _('Platform')
|
||||
get_platform.admin_order_field = ('item__collection__shortname')
|
||||
|
||||
|
||||
admin.site.register(Console)
|
||||
admin.site.register(Platform, PlatformAdmin)
|
||||
admin.site.register(Game, GameAdmin)
|
||||
admin.site.register(Timeline, TimelineAdmin)
|
||||
|
|
|
@ -1,44 +1,55 @@
|
|||
- model: games.console
|
||||
- model: games.platform
|
||||
pk: 1
|
||||
fields:
|
||||
name: Steam
|
||||
- model: games.console
|
||||
shortname: Steam
|
||||
- model: games.platform
|
||||
pk: 2
|
||||
fields:
|
||||
name: Game Boy
|
||||
- model: games.console
|
||||
shortname: Game Boy
|
||||
- model: games.platform
|
||||
pk: 3
|
||||
fields:
|
||||
name: Nintendo 3DS
|
||||
- model: games.console
|
||||
shortname: 3DS
|
||||
- model: games.platform
|
||||
pk: 4
|
||||
fields:
|
||||
name: Nintendo Switch
|
||||
- model: games.console
|
||||
shortname: Switch
|
||||
- model: games.platform
|
||||
pk: 5
|
||||
fields:
|
||||
name: PC
|
||||
- model: games.console
|
||||
shortname: PC
|
||||
- model: games.platform
|
||||
pk: 6
|
||||
fields:
|
||||
name: Genesis / Mega Drive
|
||||
- model: games.console
|
||||
shortname: GEN
|
||||
- model: games.platform
|
||||
pk: 7
|
||||
fields:
|
||||
name: Nintendo Entertainment System
|
||||
- model: games.console
|
||||
shortname: NES
|
||||
- model: games.platform
|
||||
pk: 8
|
||||
fields:
|
||||
name: PlayStation
|
||||
- model: games.console
|
||||
shortname: PS
|
||||
- model: games.platform
|
||||
pk: 9
|
||||
fields:
|
||||
name: PlayStation 3
|
||||
- model: games.console
|
||||
shortname: PS3
|
||||
- model: games.platform
|
||||
pk: 10
|
||||
fields:
|
||||
name: PlayStation 4
|
||||
- model: games.console
|
||||
shortname: PS4
|
||||
- model: games.platform
|
||||
pk: 11
|
||||
fields:
|
||||
name: Wii
|
||||
shortname: Wii
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
- model: games.game
|
||||
pk: 1
|
||||
fields: {name: Crash bandicoot 3, note: null, playing: false, unplayed: false, wish: false,
|
||||
collection: 8, status: unfinished}
|
||||
- model: games.game
|
||||
pk: 2
|
||||
fields: {name: Crash Bandicoot 2, note: null, playing: false, unplayed: false, wish: false,
|
||||
collection: 8, status: excluded}
|
||||
- model: games.game
|
||||
pk: 3
|
||||
fields: {name: 'The Legend of Zelda: Breath of the Wild', note: null, playing: true,
|
||||
unplayed: false, wish: false, collection: 4, status: unfinished}
|
||||
- model: games.game
|
||||
pk: 4
|
||||
fields: {name: Gravity Rush - Remasterised, note: null, playing: true, unplayed: false,
|
||||
wish: false, collection: 10, status: unfinished}
|
||||
- model: games.game
|
||||
pk: 5
|
||||
fields: {name: Deponia, note: null, playing: false, unplayed: true, wish: false,
|
||||
collection: 10, status: excluded}
|
||||
- model: games.game
|
||||
pk: 6
|
||||
fields: {name: Les chevaliers de Baphomet 5, note: "Tous les troph\xE9s obtenus",
|
||||
playing: false, unplayed: false, wish: false, collection: 10, status: completed}
|
||||
- model: games.game
|
||||
pk: 7
|
||||
fields: {name: Sherlock Holmes, note: null, playing: false, unplayed: false, wish: true,
|
||||
collection: 10, status: unfinished}
|
|
@ -0,0 +1,12 @@
|
|||
from django import forms
|
||||
from games.models import Game
|
||||
|
||||
|
||||
class GameForm(forms.ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""
|
||||
Do not display CREATED status.
|
||||
"""
|
||||
super(GameForm, self).__init__(*args, **kwargs)
|
||||
|
||||
self.fields['status'].choices = Game.STATUS_CHOICES
|
|
@ -0,0 +1,30 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2017-08-24 18:43
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('games', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='game',
|
||||
name='playing',
|
||||
field=models.BooleanField(default=False, help_text="You're currently playing this game."),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='game',
|
||||
name='unplayed',
|
||||
field=models.BooleanField(default=False, help_text='You never played this game.'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='game',
|
||||
name='wish',
|
||||
field=models.BooleanField(default=False, help_text="You're waiting X-mas father offers you this game."),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,74 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11 on 2017-08-24 20:22
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import datetime
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('games', '0002_auto_20170824_1843'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='console',
|
||||
options={'verbose_name': 'console', 'verbose_name_plural': 'consoles'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='game',
|
||||
options={'ordering': ('-playing', 'name'), 'verbose_name': 'game', 'verbose_name_plural': 'games'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='timeline',
|
||||
options={'verbose_name': 'Timeline', 'verbose_name_plural': 'Timelines'},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='console',
|
||||
name='name',
|
||||
field=models.CharField(max_length=255, verbose_name='nom'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='game',
|
||||
name='name',
|
||||
field=models.CharField(max_length=255, verbose_name='nom'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='game',
|
||||
name='playing',
|
||||
field=models.BooleanField(default=False, help_text="You're currently playing this game.", verbose_name='playing?'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='game',
|
||||
name='status',
|
||||
field=models.CharField(choices=[('created', 'New'), ('beaten', 'Beaten'), ('completed', 'Completed'), ('excluded', 'Excluded'), ('mastered', 'Mastered'), ('unfinished', 'Unfinished')], default='unfinished', max_length=30, verbose_name='status'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='game',
|
||||
name='unplayed',
|
||||
field=models.BooleanField(default=False, help_text='You never played this game.', verbose_name='unplayed?'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='game',
|
||||
name='wish',
|
||||
field=models.BooleanField(default=False, help_text="You're waiting X-mas father offers you this game.", verbose_name='wish?'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='timeline',
|
||||
name='date',
|
||||
field=models.DateTimeField(default=datetime.datetime.now, verbose_name='date'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='timeline',
|
||||
name='item',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='games.Game', verbose_name='game'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='timeline',
|
||||
name='status',
|
||||
field=models.CharField(choices=[('created', 'New'), ('beaten', 'Beaten'), ('completed', 'Completed'), ('excluded', 'Excluded'), ('mastered', 'Mastered'), ('unfinished', 'Unfinished')], default='created', max_length=30, verbose_name='status'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,33 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.4 on 2017-08-25 22:11
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('games', '0003_add_verbose_translatable_names'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='console',
|
||||
options={'ordering': ('name',), 'verbose_name': 'console', 'verbose_name_plural': 'consoles'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='game',
|
||||
options={'ordering': ('-playing', 'name'), 'verbose_name': 'jeu', 'verbose_name_plural': 'jeux'},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='timeline',
|
||||
options={'ordering': ('-date',), 'verbose_name': 'Chronologie', 'verbose_name_plural': 'Chronologies'},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='game',
|
||||
name='note',
|
||||
field=models.CharField(help_text='Short note displayed to your followers.', max_length=150, null=True, blank=True, verbose_name='Progress note'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,51 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.4 on 2017-08-31 12:11
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('games', '0004_add_game_note_field'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
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='playing',
|
||||
field=models.BooleanField(default=False, help_text='Vous jouez actuellement à ce jeu.', verbose_name="en train d'y jouer ?"),
|
||||
),
|
||||
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', max_length=30, verbose_name='état'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='game',
|
||||
name='unplayed',
|
||||
field=models.BooleanField(default=False, help_text="Vous n'avez jamais joué à ce jeu.", verbose_name='jamais joué ?'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='game',
|
||||
name='wish',
|
||||
field=models.BooleanField(default=False, help_text='Vous patientez que le père Noël vous offre ce jeu.', verbose_name="envie de l'avoir ?"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='timeline',
|
||||
name='item',
|
||||
field=models.ForeignKey(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', max_length=30, verbose_name='état'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.4 on 2017-09-01 20:41
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import datetime
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('games', '0005_auto_20170831_1211'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='timeline',
|
||||
name='date',
|
||||
field=models.DateField(default=datetime.datetime.now, verbose_name='date'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.4 on 2017-09-04 21:46
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('games', '0006_auto_20170901_2041'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='console',
|
||||
name='name',
|
||||
field=models.CharField(help_text='Nom de console le plus utilisé.', max_length=255, verbose_name='nom'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,38 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.5 on 2017-09-16 15:05
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('games', '0007_help_text_on_console_name'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameModel(
|
||||
old_name='Console',
|
||||
new_name='Platform',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='game',
|
||||
name='collection',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='games', to='games.Platform', verbose_name='platform'),
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='Platform',
|
||||
options={
|
||||
'verbose_name': 'platform',
|
||||
'verbose_name_plural': 'platforms',
|
||||
'ordering': ('name',),
|
||||
},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='platform',
|
||||
name='name',
|
||||
field=models.CharField(help_text='Most used platform name.', max_length=255, verbose_name='nom'),
|
||||
),
|
||||
]
|
|
@ -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'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,23 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.9 on 2018-01-15 17:30
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('games', '0009_add_help_text_for_documentation'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='platform',
|
||||
name='shortname',
|
||||
field=models.CharField(
|
||||
default='TODO: Add shortname',
|
||||
max_length=30,
|
||||
verbose_name='shortname'), ),
|
||||
]
|
|
@ -0,0 +1,24 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.9 on 2018-01-16 17:56
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('games', '0010_auto_20180115_1730'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='platform',
|
||||
name='name',
|
||||
field=models.CharField(
|
||||
help_text='Most used platform name.',
|
||||
max_length=255,
|
||||
unique=True,
|
||||
verbose_name='name'), ),
|
||||
]
|
|
@ -0,0 +1,29 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.9 on 2018-01-21 11:06
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import datetime
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('games', '0011_auto_20180116_1756'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='game',
|
||||
name='achievement_max',
|
||||
field=models.PositiveIntegerField(blank=True, null=True,
|
||||
verbose_name='out of'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='game',
|
||||
name='achievement',
|
||||
field=models.PositiveIntegerField(blank=True, null=True,
|
||||
verbose_name='achievement'),
|
||||
),
|
||||
]
|
|
@ -0,0 +1,119 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.9 on 2018-02-04 19:34
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations
|
||||
from django.db import models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('games', '0012_add_achievements'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='game',
|
||||
options={
|
||||
'ordering': ('-playing', 'name'),
|
||||
'verbose_name': 'game',
|
||||
'verbose_name_plural': 'games'
|
||||
}, ),
|
||||
migrations.AlterModelOptions(
|
||||
name='platform',
|
||||
options={
|
||||
'ordering': ('name', ),
|
||||
'verbose_name': 'platform',
|
||||
'verbose_name_plural': 'platforms'
|
||||
}, ),
|
||||
migrations.AlterModelOptions(
|
||||
name='timeline',
|
||||
options={
|
||||
'ordering': ('-date', ),
|
||||
'verbose_name': 'Timeline',
|
||||
'verbose_name_plural': 'Timelines'
|
||||
}, ),
|
||||
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='platform'), ),
|
||||
migrations.AlterField(
|
||||
model_name='game',
|
||||
name='name',
|
||||
field=models.CharField(
|
||||
help_text='Game title', max_length=255, verbose_name='name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='game',
|
||||
name='note',
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
help_text='Short note displayed to your followers.',
|
||||
max_length=150,
|
||||
null=True,
|
||||
verbose_name='Progress note'), ),
|
||||
migrations.AlterField(
|
||||
model_name='game',
|
||||
name='playing',
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
help_text="You're currently playing this game.",
|
||||
verbose_name='playing?'), ),
|
||||
migrations.AlterField(
|
||||
model_name='game',
|
||||
name='status',
|
||||
field=models.CharField(
|
||||
choices=[('created', 'New'), ('beaten', 'Beaten'),
|
||||
('completed', 'Completed'), ('excluded', 'Excluded'),
|
||||
('mastered', 'Mastered'), ('unfinished',
|
||||
'Unfinished')],
|
||||
default='unfinished',
|
||||
help_text='Game progression',
|
||||
max_length=30,
|
||||
verbose_name='status'), ),
|
||||
migrations.AlterField(
|
||||
model_name='game',
|
||||
name='unplayed',
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
help_text='You never played this game.',
|
||||
verbose_name='unplayed?'), ),
|
||||
migrations.AlterField(
|
||||
model_name='game',
|
||||
name='wish',
|
||||
field=models.BooleanField(
|
||||
default=False,
|
||||
help_text="You're waiting X-mas father offers you this game.",
|
||||
verbose_name='wish?'), ),
|
||||
migrations.AlterField(
|
||||
model_name='platform',
|
||||
name='shortname',
|
||||
field=models.CharField(max_length=30, verbose_name='shortname'), ),
|
||||
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='game'), ),
|
||||
migrations.AlterField(
|
||||
model_name='timeline',
|
||||
name='status',
|
||||
field=models.CharField(
|
||||
choices=[('created', 'New'), ('beaten', 'Beaten'),
|
||||
('completed', 'Completed'), ('excluded', 'Excluded'),
|
||||
('mastered', 'Mastered'), ('unfinished',
|
||||
'Unfinished')],
|
||||
default='created',
|
||||
help_text='New status for this game at the given date',
|
||||
max_length=30,
|
||||
verbose_name='status'), ),
|
||||
]
|
|
@ -3,21 +3,30 @@ from django.db import models
|
|||
from django.utils.translation import ugettext as _
|
||||
|
||||
|
||||
class Console(Collection):
|
||||
class Platform(Collection):
|
||||
"""
|
||||
All console, system or box that can be used to play video games.
|
||||
All platform, system or box that can be used to play video games.
|
||||
"""
|
||||
def __str__(self):
|
||||
return '%s' % self.name
|
||||
|
||||
class Meta:
|
||||
ordering = ('name',)
|
||||
verbose_name = _('platform')
|
||||
verbose_name_plural = _('platforms')
|
||||
|
||||
|
||||
# Redefine help_text (for documentation, API, etc.)
|
||||
Platform._meta.get_field('name').help_text = _('Most used platform name.')
|
||||
|
||||
|
||||
class Game(Item):
|
||||
"""
|
||||
A video game you will use on a specific Console.
|
||||
A video game you will use on a specific Platform.
|
||||
"""
|
||||
# class config
|
||||
TARGET_MODEL = 'games.Console'
|
||||
TARGET_VERBOSE_NAME = _('console')
|
||||
TARGET_MODEL = 'games.Platform'
|
||||
TARGET_VERBOSE_NAME = _('platform')
|
||||
RELATED_TARGET_NAME = 'games'
|
||||
|
||||
# Status choices
|
||||
|
@ -36,16 +45,54 @@ class Game(Item):
|
|||
)
|
||||
DEFAULT_CHOICE = UNFINISHED
|
||||
|
||||
# progression
|
||||
note = models.CharField(
|
||||
max_length=150,
|
||||
null=True,
|
||||
blank=True,
|
||||
verbose_name=_('Progress note'),
|
||||
help_text=_('Short note displayed to your followers.'))
|
||||
|
||||
# others
|
||||
playing = models.BooleanField(default=False)
|
||||
unplayed = models.BooleanField(default=False)
|
||||
wish = models.BooleanField(default=False)
|
||||
playing = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=_('playing?'),
|
||||
help_text=_('You\'re currently playing this game.'))
|
||||
unplayed = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=_('unplayed?'),
|
||||
help_text=_('You never played this game.'))
|
||||
wish = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=_('wish?'),
|
||||
help_text=_('You\'re waiting X-mas father offers you this game.'))
|
||||
|
||||
class Meta:
|
||||
ordering = ('-playing', 'name')
|
||||
verbose_name = _('game')
|
||||
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'
|
||||
STATUS_CHOICES = Game.STATUS_CHOICES
|
||||
DEFAULT_CHOICE = Item.DEFAULT_CHOICE
|
||||
|
||||
class Meta:
|
||||
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')
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
from games.models import Game
|
||||
from games.models import Platform
|
||||
from games.models import Timeline
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
class GameSerializer(serializers.HyperlinkedModelSerializer):
|
||||
class Meta:
|
||||
model = Game
|
||||
fields = (
|
||||
'collection',
|
||||
'name',
|
||||
'note',
|
||||
'playing',
|
||||
'status',
|
||||
'unplayed',
|
||||
'wish', )
|
||||
|
||||
|
||||
class GameTimelineSerializer(serializers.HyperlinkedModelSerializer):
|
||||
class Meta:
|
||||
model = Timeline
|
||||
fields = (
|
||||
'date',
|
||||
'item',
|
||||
'status', )
|
||||
|
||||
|
||||
class PlatformSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Platform
|
||||
fields = ('name', 'shortname')
|
|
@ -18,5 +18,6 @@ def game_saved(sender, instance, created, raw, using, update_fields,
|
|||
new_entry = dict(entry)
|
||||
new_entry.update({'status': instance.CREATED})
|
||||
Timeline.objects.create(**new_entry)
|
||||
# Add new timeline entry
|
||||
Timeline.objects.create(**entry)
|
||||
# Add new timeline entry ONLY if status is difference from CREATED one
|
||||
if instance.status != instance.CREATED:
|
||||
Timeline.objects.create(**entry)
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{% trans "Games" %}</h1>
|
||||
{% if non_excluded_games %}
|
||||
<ul>
|
||||
{% for game in non_excluded_games %}
|
||||
<li><strong>{{ game.collection.shortname }}</strong> - {{ game.name }}</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<p>{% trans "No game found." %}</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,35 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{% trans "My summary" %}</h1>
|
||||
<h2>{% trans "Now playing" %}</h2>
|
||||
{% if playing_games %}
|
||||
<ul>
|
||||
{% for playing in playing_games %}
|
||||
<li><strong>{{ playing.collection.shortname }}</strong> - {{ playing.name }}</li>
|
||||
{% endfor %}
|
||||
|
||||
</ul>
|
||||
{% else %}
|
||||
<p>{% trans "No playing game found." %}</p>
|
||||
{% endif %}
|
||||
<h2>{% trans "Memory Card" %}</h2>
|
||||
{% if last_timelines %}
|
||||
{% regroup last_timelines by date as dates %}
|
||||
{% for date in dates %}
|
||||
<p><u>{{ date.grouper }} : </u>
|
||||
<ul>
|
||||
{% for timeline in date.list|dictsortreversed:"id" %}
|
||||
<li>
|
||||
<strong>{{ timeline.get_status_display }} :</strong> {{ timeline.item.name }}
|
||||
(<strong>{{ timeline.item.collection.shortname }}</strong>)
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endfor %}
|
||||
</p>
|
||||
{% else %}
|
||||
<p>{% trans "Empty memory." %}</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -0,0 +1,165 @@
|
|||
import json
|
||||
from datetime import date
|
||||
from datetime import datetime
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.urls import reverse
|
||||
from games.models import Game
|
||||
from games.models import Platform
|
||||
from games.models import Timeline
|
||||
from rest_framework import status
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
|
||||
class PlatformTest(APITestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.superuser = User.objects.create_superuser(
|
||||
'admin', 'admin@localhost', 'admin')
|
||||
|
||||
def test_create_platform(self):
|
||||
"""
|
||||
Check we can create a platform object.
|
||||
"""
|
||||
url = reverse('platform-list')
|
||||
data = {'name': 'GP2X', 'shortname': '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(Platform.objects.count(), 1)
|
||||
self.assertEqual(Platform.objects.get().name, 'GP2X')
|
||||
|
||||
def test_sorted_platform(self):
|
||||
"""
|
||||
Check that platform list is sorted.
|
||||
"""
|
||||
Platform.objects.create(name='GP2X')
|
||||
Platform.objects.create(name='3DS')
|
||||
Platform.objects.create(name='Game Boy')
|
||||
Platform.objects.create(name='Amiga')
|
||||
url = reverse('platform-list')
|
||||
self.client.force_authenticate(user=self.superuser)
|
||||
response = self.client.get(url, format='json')
|
||||
sorted_platforms = list(Platform.objects.all().order_by('name')
|
||||
.values_list('name', flat=True))
|
||||
platforms = [x.get('name') for x in json.loads(response.content)]
|
||||
self.assertEqual(platforms, sorted_platforms)
|
||||
|
||||
|
||||
class GameTest(APITestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.superuser = User.objects.create_superuser(
|
||||
'admin', 'admin@localhost', 'admin')
|
||||
|
||||
def test_create_game(self):
|
||||
"""
|
||||
Check we can create a game object.
|
||||
"""
|
||||
# Game works on specific platform
|
||||
console = Platform.objects.create(name='Game Boy')
|
||||
console_url = reverse('platform-detail', kwargs={'pk': console.id})
|
||||
|
||||
url = reverse('game-list')
|
||||
data = {
|
||||
'name': 'Tetris',
|
||||
'collection': console_url,
|
||||
}
|
||||
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(Game.objects.count(), 1)
|
||||
self.assertEqual(Game.objects.get().name, 'Tetris')
|
||||
self.assertEqual(Game.objects.get().collection_id, console.id)
|
||||
|
||||
def test_sorted_games(self):
|
||||
"""
|
||||
Check that game list is sorted.
|
||||
"""
|
||||
console = Platform.objects.create(name='BestPlatform4Ever')
|
||||
Game.objects.create(name='Pomperman', collection=console)
|
||||
Game.objects.create(name='Vektoria', collection=console)
|
||||
Game.objects.create(name='Qrackovitchya', collection=console)
|
||||
url = reverse('game-list')
|
||||
self.client.force_authenticate(user=self.superuser)
|
||||
response = self.client.get(url, format='json')
|
||||
sorted_games = list(
|
||||
Game.objects.all().order_by('name').values_list('name', flat=True))
|
||||
games = [x.get('name') for x in json.loads(response.content)]
|
||||
self.assertEqual(games, sorted_games)
|
||||
|
||||
def test_not_allowed_status(self):
|
||||
"""
|
||||
Check that we cannot insert a not allowed status in a Game
|
||||
"""
|
||||
Platform.objects.create(name='BestPlatform4Ever')
|
||||
url = reverse('game-list')
|
||||
data = {
|
||||
'name': 'Vilebrequin',
|
||||
'status': 'wrecked',
|
||||
}
|
||||
self.client.force_authenticate(user=self.superuser)
|
||||
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):
|
||||
"""
|
||||
Check that timelines are sorted by date in descending order.
|
||||
"""
|
||||
# Prepare timeline with some different dates
|
||||
console = Platform.objects.create(name='BestPlatform4Ever')
|
||||
game = Game.objects.create(
|
||||
name='Symptomatic', collection=console, status='created')
|
||||
game.status = 'beaten'
|
||||
game.save()
|
||||
game.status = 'completed'
|
||||
game.save()
|
||||
# Change date from Timelines
|
||||
Timeline.objects.filter(status='created').update(date='2017-03-01')
|
||||
Timeline.objects.filter(status='completed').update(date='2017-03-05')
|
||||
Timeline.objects.filter(status='beaten').update(date='2017-03-07')
|
||||
# Check dates
|
||||
url = reverse('game_timeline-list')
|
||||
self.client.force_authenticate(user=self.superuser)
|
||||
response = self.client.get(url, format='json')
|
||||
# sorted_timelines are datetime.date
|
||||
sorted_timelines = list(Timeline.objects.all().order_by('-date')
|
||||
.values_list('date', flat=True))
|
||||
# dates are string date, format %Y-%m-%d
|
||||
dates = [x.get('date') for x in json.loads(response.content)]
|
||||
da = [datetime.date(datetime.strptime(x, '%Y-%m-%d')) for x in dates]
|
||||
self.assertEqual(da, sorted_timelines)
|
|
@ -1,20 +1,20 @@
|
|||
from django.test import TestCase
|
||||
from games.models import Console
|
||||
from games.models import Platform
|
||||
|
||||
|
||||
class ConsoleTest(TestCase):
|
||||
class PlatformTest(TestCase):
|
||||
"""
|
||||
Console Model
|
||||
Platform Model
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
Console.objects.create(name='GP2X')
|
||||
Console.objects.create(name='3DS')
|
||||
Platform.objects.create(name='GP2X')
|
||||
Platform.objects.create(name='3DS')
|
||||
|
||||
|
||||
def test_console_are_sorted_by_name(self):
|
||||
consoles = list(Console.objects.all().values_list('name', flat=True))
|
||||
sorted_consoles = list(
|
||||
Console.objects.all().order_by('name').values_list(
|
||||
def test_platform_are_sorted_by_name(self):
|
||||
platforms = list(Platform.objects.all().values_list('name', flat=True))
|
||||
sorted_platforms = list(
|
||||
Platform.objects.all().order_by('name').values_list(
|
||||
'name', flat=True))
|
||||
self.assertEqual(consoles, sorted_consoles)
|
||||
self.assertEqual(platforms, sorted_platforms)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from django.test import TestCase
|
||||
from games.models import Console, Game
|
||||
from games.models import Game, Platform
|
||||
|
||||
|
||||
class GameTest(TestCase):
|
||||
|
@ -8,13 +8,22 @@ class GameTest(TestCase):
|
|||
"""
|
||||
|
||||
def setUp(self):
|
||||
self.console = Console.objects.create(name='BestConsole4Ever')
|
||||
self.platform = Platform.objects.create(name='BestPlatform4Ever')
|
||||
Game.objects.create(
|
||||
name='Deponia', playing=False, collection=self.console)
|
||||
name='Deponia',
|
||||
playing=True,
|
||||
collection=self.platform,
|
||||
status=Game.EXCLUDED)
|
||||
Game.objects.create(
|
||||
name='Aladdin', playing=True, collection=self.console)
|
||||
name='Aladdin', playing=True, collection=self.platform)
|
||||
Game.objects.create(
|
||||
name='Persona 5', playing=True, collection=self.console)
|
||||
name='Persona 5', playing=True, collection=self.platform)
|
||||
Game.objects.create(
|
||||
name='The Witcher III',
|
||||
playing=False,
|
||||
collection=self.platform,
|
||||
wish=True)
|
||||
self.index_url = '/games/'
|
||||
|
||||
|
||||
def test_game_are_sorted_by_playing_and_name(self):
|
||||
|
@ -26,3 +35,48 @@ class GameTest(TestCase):
|
|||
sorted_games = list(Game.objects.all().order_by(
|
||||
'-playing', 'name').values_list('name', flat=True))
|
||||
self.assertEqual(games, sorted_games)
|
||||
|
||||
def test_index(self):
|
||||
"""
|
||||
Context gives 'playing_games'.
|
||||
Context gives 'last_timelines'.
|
||||
"""
|
||||
res = self.client.get(self.index_url)
|
||||
self.assertEqual(res.status_code, 200)
|
||||
self.assertTrue('playing_games' in res.context)
|
||||
self.assertTrue('last_timelines' in res.context)
|
||||
self.assertTrue('object_list' in res.context)
|
||||
|
||||
def test_index_queryset(self):
|
||||
"""
|
||||
Queryset excludes wishlist games.
|
||||
Queryset excludes games that have status EXCLUDED.
|
||||
Queryset is sorted by name.
|
||||
"""
|
||||
res = self.client.get(self.index_url)
|
||||
games = res.context.get('object_list')
|
||||
for game in games:
|
||||
self.assertFalse(game.wish)
|
||||
self.assertTrue(game.status != Game.EXCLUDED)
|
||||
sorted_games = list(games.order_by('name'))
|
||||
self.assertEqual([x.name for x in games], [s.name for s in sorted_games])
|
||||
|
||||
def test_index_playing_games(self):
|
||||
"""
|
||||
'playing_games' contains games that have playing=True.
|
||||
"""
|
||||
res = self.client.get(self.index_url)
|
||||
playing_games = res.context.get('playing_games')
|
||||
for game in playing_games:
|
||||
self.assertTrue(game.playing)
|
||||
|
||||
def test_index_last_timelines(self):
|
||||
"""
|
||||
'last_timelines' only have 5 items.
|
||||
'last_timelines' have NO games with status EXCLUDED.
|
||||
"""
|
||||
res = self.client.get(self.index_url)
|
||||
games = res.context.get('last_timelines')
|
||||
self.assertEqual(len(games), 5)
|
||||
for game in games:
|
||||
self.assertTrue(game.status != Game.EXCLUDED)
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
from django.test import TestCase
|
||||
from games.models import Game, Platform, Timeline
|
||||
|
||||
|
||||
class TimelineTest(TestCase):
|
||||
"""
|
||||
Timeline Model
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.console = Platform.objects.create(name='GP2X')
|
||||
|
||||
|
||||
def test_game_with_status_created_gives_one_timeline(self):
|
||||
"""
|
||||
Game with status "created" should only generate ONE status. Not more.
|
||||
"""
|
||||
# Games creation should generate timelines.
|
||||
game1 = Game.objects.create(
|
||||
name='Vektronizor',
|
||||
collection=self.console,
|
||||
status='beaten')
|
||||
game2 = Game.objects.create(
|
||||
name='Pomperman',
|
||||
collection=self.console,
|
||||
status='created')
|
||||
|
||||
game1_timeline = Timeline.objects.filter(
|
||||
item=game1)
|
||||
game2_timeline = Timeline.objects.filter(
|
||||
item=game2)
|
||||
self.assertEqual(game1_timeline.count(), 2)
|
||||
self.assertEqual(game2_timeline.count(), 1)
|
|
@ -0,0 +1,8 @@
|
|||
from django.conf.urls import url
|
||||
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
url(r'summary/$', views.SummaryList.as_view()),
|
||||
url(r'$', views.GameList.as_view()),
|
||||
]
|
|
@ -1,3 +1,91 @@
|
|||
from django.shortcuts import render
|
||||
from django.db.models import Q
|
||||
from django.views.generic import ListView
|
||||
from rest_framework import viewsets
|
||||
|
||||
# Create your views here.
|
||||
from .models import Game
|
||||
from .models import Platform
|
||||
from .models import Timeline
|
||||
from .serializers import GameSerializer
|
||||
from .serializers import GameTimelineSerializer
|
||||
from .serializers import PlatformSerializer
|
||||
|
||||
|
||||
class PlatformViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
API endpoints that allows platforms to be edited or viewed.
|
||||
|
||||
retrieve:
|
||||
Return the given platform
|
||||
|
||||
list:
|
||||
Return a list of all existing platforms ordered by name.
|
||||
|
||||
create:
|
||||
Create a new platform instance.
|
||||
"""
|
||||
queryset = Platform.objects.all().order_by('name')
|
||||
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.
|
||||
|
||||
list:
|
||||
Return a list of all existing games ordered by name.
|
||||
|
||||
create:
|
||||
Create a new game instance.
|
||||
"""
|
||||
queryset = Game.objects.all().order_by('name')
|
||||
serializer_class = GameSerializer
|
||||
|
||||
|
||||
class GameList(ListView):
|
||||
model = Game
|
||||
context_object_name = 'non_excluded_games'
|
||||
template_name = 'games/index.html'
|
||||
queryset = Game.objects.filter(~Q(status=Game.EXCLUDED) & Q(
|
||||
wish=False)).order_by('name').prefetch_related('collection')
|
||||
|
||||
|
||||
class SummaryList(ListView):
|
||||
model = Game
|
||||
context_object_name = 'playing_games'
|
||||
template_name = 'games/summary.html'
|
||||
queryset = Game.objects.filter(
|
||||
playing=True).order_by('name').prefetch_related('collection')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""
|
||||
Add playing games list.
|
||||
Add 5 last current activities from Timeline
|
||||
"""
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['last_timelines'] = Timeline.objects.filter(
|
||||
~Q(item__status=Game.EXCLUDED)).order_by(
|
||||
'-date')[:5].prefetch_related('item__collection')
|
||||
return context
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
-r requirements.txt
|
||||
django-debug-toolbar
|
||||
flake8
|
||||
pre-commit
|
||||
pylint
|
||||
pylint_django
|
|
@ -5,12 +5,14 @@ set -e
|
|||
chown -R guest "$DB_DIR"
|
||||
|
||||
if [ "$1" = 'dev' ]; then
|
||||
export DJANGO_SETTINGS_MODULE=collection.settings
|
||||
export DJANGO_ENV='development'
|
||||
exec python3 manage.py runserver 0.0.0.0:8000
|
||||
elif [ "$1" = 'prod' ]; then
|
||||
export DJANGO_SETTINGS_MODULE=collection.production
|
||||
export DJANGO_ENV='production'
|
||||
# Collect static files
|
||||
python3 manage.py collectstatic --noinput --clear -v 0
|
||||
# translations generation
|
||||
python3 manage.py compilemessages
|
||||
exec uwsgi --ini uwsgi.ini --pythonpath "$APPS_DIR" --static-map=/static/="$STATIC_ROOT"
|
||||
fi
|
||||
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
Django==1.11
|
||||
PyYAML==3.12
|
||||
coreapi==2.3.1
|
||||
Django==1.11.15
|
||||
django-cors-headers==2.1.0
|
||||
django-split-settings==0.2.5
|
||||
djangorestframework==3.6.4
|
||||
psycopg2==2.7.3.2
|
||||
PyYAML==3.12 ; python_version < 3.7
|
||||
PyYAML==3.13 ; python_version >= 3.7
|
||||
uWSGI==2.0.15
|
||||
|
|
Loading…
Reference in New Issue