Compare commits

...

80 Commits
v0.1 ... master

Author SHA1 Message Date
Olivier DOSSMANN 16deac8849 MàJ de Django 2018-10-13 22:23:56 +02:00
Olivier DOSSMANN 56c198f19a Nouvelle page d'accueil suivant HTML5UP.net 2018-02-19 23:16:14 +01:00
Olivier DOSSMANN 445a769ce5 MàJ vers Django 1.11.10 2018-02-19 22:22:23 +01:00
Olivier DOSSMANN c9e9ec46fa Séparation de la liste des jeux avec les jeux en cours + Timelines 2018-02-19 22:21:06 +01:00
Olivier DOSSMANN a5bcbf0c7e fix #21 - API : ajout des figurines et leurs colllections 2018-02-04 22:10:56 +01:00
Olivier DOSSMANN 286c075ea8 Amélioration du nom affiché pour le filtre sur les Chronologies 2018-02-04 21:34:05 +01:00
Olivier DOSSMANN 2e7123989f fix #19 - Chronologies : ajout d'une colonne "Plateforme" et filtrage dessus 2018-02-04 21:27:43 +01:00
Olivier DOSSMANN 2db9340f3d fix #14 - Figurines : ajout de nouveaux types, parmi :
* Battle pack
  * Level pack (anciennement world)
  * Expansion pack
  * Trophy (pour les trophés Skylanders)
  * Trap (pour les pièges de cristal de Skylanders)
2018-02-04 21:20:06 +01:00
Olivier DOSSMANN bb9869df73 Application en anglais pour l'environnement de développement: notamment pour
les fichiers de migration
2018-02-04 21:17:40 +01:00
Olivier DOSSMANN 16847d1856 fix #18 - Ajout d'un champ de recherche sur les Chronologies 2018-02-04 14:43:10 +01:00
Olivier DOSSMANN 0c3a9b3be7 Figurines : déplacement vers une gestion à part + filtrage/recherche 2018-02-04 14:37:16 +01:00
Olivier DOSSMANN 801d26cb4a Amélioration de l'affichage de la page d'accueil 2018-02-03 20:11:14 +01:00
Olivier DOSSMANN 52d56926d4 Installation de la Django Debug Toolbar pour l'environnement de DEV. 2018-02-03 20:01:34 +01:00
Olivier DOSSMANN 5e1c32a2ff MàJ de l'environnement de production pour la sécurité des cookies, HSTS, etc. 2018-02-03 19:43:36 +01:00
Olivier DOSSMANN bb52c3c19b Correction : dépendance à psycopg2 manquante (pour le mode production) 2018-02-02 19:36:48 +01:00
Olivier DOSSMANN 00e4b78fe1 MàJ TODO list 2018-02-02 19:11:04 +01:00
Olivier DOSSMANN 5a2ba5c16a MàJ TODO list pour nom du projet 2018-01-30 19:50:51 +01:00
Olivier DOSSMANN d43af2278e Mode production utilise désormais postgreSQL comme base de données 2018-01-26 08:24:39 +01:00
Olivier DOSSMANN 00d34f819d MàJ TODO 2018-01-26 08:22:59 +01:00
Olivier DOSSMANN 090376f1e3 Ayant besoin de connaître la fortune de chaque figurine, j'ai ajouté 'coins' 2018-01-21 15:47:33 +01:00
Olivier DOSSMANN 110add5b0e Ajout de la progression détaillée des objets 'Item' (jeux, figurines, etc.) 2018-01-21 12:38:18 +01:00
Olivier DOSSMANN b83f00609a Ajout d'une catégorisation des figurines : champ 'sorte' (kind) 2018-01-20 22:21:22 +01:00
Olivier DOSSMANN f60d7d98fd Changement de l'ordre des filtres des jeux (Game) 2018-01-18 20:59:53 +01:00
Olivier DOSSMANN 64ef1b8086 Ajout d'un filter par plateforme sur les jeux (Game) 2018-01-18 20:57:06 +01:00
Olivier DOSSMANN 4dfe7f4285 Correction: empêcher d'ajouter 2 fois le même objet à la même collection 2018-01-18 20:31:09 +01:00
Olivier DOSSMANN 083d10fc6e Correction : empêcher d'ajouter 2 fois la même collection (figurine/consoles) 2018-01-16 19:07:39 +01:00
Olivier DOSSMANN 63bed8818e Affichage des noms courts de console (plateforme) à côté de chaque jeu 2018-01-16 18:52:13 +01:00
Olivier DOSSMANN eab50caca9 Affichage correct de la version en cours d'utilisation 2018-01-16 18:01:15 +01:00
Olivier DOSSMANN b0c9f7ebec MàJ du CHANGELOG 2018-01-16 18:00:24 +01:00
Olivier DOSSMANN a0e365f596 Merge branch 'master' of forge.o9.re:olivier/openbackloggery 2018-01-16 08:33:32 +01:00
Olivier DOSSMANN a86810b451 MàJ TODO 2018-01-16 08:33:16 +01:00
Olivier DOSSMANN c70673b8b1 Liste des jeux : ajout d'une colonne pour le nom de la plateforme 2018-01-15 21:13:49 +01:00
Olivier DOSSMANN f2d7a2dfd8 Ajout du champ 'shortname' à Games et Figurines 2018-01-15 19:07:19 +01:00
Olivier DOSSMANN 91cc57f166 API: champ 'shortname' manquant 2018-01-15 19:06:26 +01:00
Olivier DOSSMANN a6dcad77d9 Ajout du champ "shortname" sur les collections 2018-01-15 18:55:03 +01:00
Olivier DOSSMANN 50f0361b91 Amélioration de l'environnement de développement 2018-01-15 18:22:25 +01:00
Olivier DOSSMANN dcc11b994f MàJ vers Django 1.11.9 2018-01-15 18:15:42 +01:00
Olivier DOSSMANN 03498d93e7 MàJ TODO 2018-01-15 18:15:20 +01:00
Olivier DOSSMANN bb51eb68cd Mode production: autorisation de toutes les requêtes CORS 2017-10-02 11:57:17 +02:00
Olivier DOSSMANN c74413e3b4 Correction de typo suite à problème de lancement 2017-10-02 11:34:30 +02:00
Olivier DOSSMANN 20a6d25a8b Nouvelle collection : les figurines 2017-09-18 21:51:10 +02:00
Olivier DOSSMANN 0536b16d6d Ajout d'un test sur l'ordre des game_timeline sur l'API 2017-09-17 17:34:09 +02:00
Olivier DOSSMANN a5ff2326f7 Vérification des champs « required » dans l'API : valeur par défaut suffit. 2017-09-17 16:48:58 +02:00
Olivier DOSSMANN 619cd8d326 Correction des tests automatiques sous Gitlab 2017-09-17 11:24:34 +02:00
Olivier DOSSMANN aa01a0d8db Tests automatiques sur Gitlab 2017-09-17 10:47:36 +02:00
Olivier DOSSMANN 92ea2f6af7 API Timeline ajoutée ! 2017-09-17 00:04:26 +02:00
Olivier DOSSMANN 95d8b30c3c Correction de bug : les jeux avec l'état "created" généraient 2 timelines 2017-09-16 23:36:06 +02:00
Olivier DOSSMANN 1845ae9eac API Game : ajout des champs status, unplayed, playing et wish 2017-09-16 22:28:41 +02:00
Olivier DOSSMANN 8dec41073f API : ajout des jeux (nom et plateforme) 2017-09-16 21:37:36 +02:00
Olivier DOSSMANN b0978034b5 Renommage des consoles en plateformes 2017-09-16 17:42:04 +02:00
Olivier DOSSMANN 0380bc9071 Permissions de l'API :
* API disponible en lecture seule par tout le monde
  * Écriture suivant les permissions établies dans l'interface admin de Django
2017-09-16 15:06:23 +02:00
Olivier DOSSMANN 8847c351c1 Activation de CORS pour l'accès à l'API :
* accès total en mode développement
  * accès restreint à l'URL /api en mode production
2017-09-07 18:27:10 +02:00
Olivier DOSSMANN ea2d3d0c43 MàJ vers Django 1.11.5 2017-09-06 22:50:07 +02:00
Olivier DOSSMANN 63e1425b00 MàJ liste de tâches (TODO) 2017-09-05 14:04:32 +02:00
Olivier DOSSMANN d93a3a024f Ajout de la dernière fonctionnalité (API) au CHANGELOG et au README 2017-09-05 13:56:59 +02:00
Olivier DOSSMANN 1612cdae6e MàJ liste de tâches (TODO) 2017-09-04 23:48:05 +02:00
Olivier DOSSMANN 7d83a4a59f Correction : fichier de migration manquant. 2017-09-04 23:47:33 +02:00
Olivier DOSSMANN 9b0def75b7 Nouvelle API :
Avec

  * API sur les consoles en utilisant Django Rest Framework
  * Tri par nom de console
  * Nouveaux tests sur l'API
  * Documentation pour l'API
  * Ajout d'une description au champ "name" pour Console et sa traduction
  * Nouvelles dépendances à coreapi et djangorestframework
2017-09-04 23:42:57 +02:00
Olivier DOSSMANN ff228ab845 Delete useless PyCharm ignore regex from .gitignore file 2017-09-04 17:19:33 +02:00
Olivier DOSSMANN c3491b7975 Page d'accueil des jeux vidéos : regroupement par date pour les timelines
Timelines :

  * regroupement par date sur la page d'accueil des jeux vidéos
  * ajout de 4 tests concernant cette page
  * changement du champ 'date' des Timeline par un 'DateField' (au lieu
  d'un DateTimeField)
2017-09-03 10:53:17 +02:00
Olivier DOSSMANN 88ceb57a09 Correction de la page d'accueil : Affichage d'un message si aucun jeu
Notamment dans la liste complète des jeux.
2017-08-31 16:52:31 +02:00
Olivier DOSSMANN be6b7bfc99 Page d'accueil : ajout des activités récentes sur les jeux vidéos 2017-08-31 16:35:30 +02:00
Olivier DOSSMANN ed9c81f0d9 MàJ de la liste de tâches avec 2 idées de fonctionnalités 2017-08-31 15:31:39 +02:00
Olivier DOSSMANN ecaf40ba41 Omission du répertoire .idea de PyCharm 2017-08-31 15:30:50 +02:00
Olivier DOSSMANN b71c1c7baa Nouveau jeu de données pour les tests sur l'objet Game 2017-08-31 15:28:57 +02:00
Olivier DOSSMANN 5867d70111 Ajout d'une page d'accueil listant la collection 2017-08-31 15:20:08 +02:00
Olivier DOSSMANN 5ba6193d4b Champ note de Game : ajout de blank=True + corrections mineures des migrations 2017-08-31 14:15:16 +02:00
Olivier DOSSMANN c55fdf3e38 Ajout de la note de progression pour un jeu donné 2017-08-26 00:23:56 +02:00
Olivier DOSSMANN 6319f24499 Omission de l'état 'Nouveau' (CREATED) dans les formulaires et le filtre 2017-08-25 23:51:37 +02:00
Olivier DOSSMANN 84e1baa64b Docker - compilation des messages traduits pour l'application 2017-08-25 14:43:15 +02:00
Olivier DOSSMANN 99ed5d574c CHANGELOG complété. Concerne la possibilité d'utiliser Docker. 2017-08-25 14:33:38 +02:00
Olivier DOSSMANN ae1a3edd99 Séparation du fichier de configuration à l'aide de django-split-settings 2017-08-25 14:33:07 +02:00
Olivier DOSSMANN 96de9e859c MàJ Django vers 1.11.4 2017-08-25 13:21:06 +02:00
Olivier DOSSMANN fdc4e36da7 Correction du tri des consoles par nom 2017-08-25 13:20:11 +02:00
Olivier DOSSMANN bd326cd846 MàJ du CHANGELOG 2017-08-25 11:09:09 +02:00
Olivier DOSSMANN 8afd6dec6e Traduction de l'interface en Français 2017-08-24 22:36:55 +02:00
Olivier DOSSMANN 67afac3d45 CHANGELOG ajouté 2017-08-24 21:04:38 +02:00
Olivier DOSSMANN 00afb10702 README - modification de la section "Installation" pour ajouter PIP 2017-08-24 20:56:11 +02:00
Olivier DOSSMANN 5820bdc63a Amélioration de l'interface d'ajout de jeux vidéos :
Modification de  :

  * ajout de champs d'aide sur les champs booléens
  * création des fichiers de migration pour ces champs d'aide
  * choix de l'ordre des champs dans l'interface admin de Games
2017-08-24 20:43:58 +02:00
Olivier DOSSMANN 04708e6286 Mise à jour de la liste TODO 2017-08-24 14:15:36 +02:00
87 changed files with 4639 additions and 225 deletions

1
.dockerignore 100644
View File

@ -0,0 +1 @@
*.mo

12
.gitlab-ci.yml 100644
View File

@ -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

View File

@ -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

27
CHANGELOG 100644
View File

@ -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

View File

@ -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 \

View File

@ -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
View File

@ -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.

View File

@ -1 +1 @@
__version__ = '0.1'
__version__ = '0.2-trunk'

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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'

View File

@ -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"

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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éé"

View File

@ -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

View File

@ -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;
}

View File

@ -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.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 382 KiB

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

View File

@ -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>

View File

@ -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>&copy; 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>

View File

View File

@ -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)

View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class FigurinesConfig(AppConfig):
name = 'figurines'

View File

@ -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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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}

View File

@ -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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,61 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.5 on 2017-09-16 21:55
from __future__ import unicode_literals
import datetime
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('games', '0008_rename_console_to_platform'),
]
operations = [
migrations.AlterModelOptions(
name='platform',
options={'ordering': ('name',), 'verbose_name': 'plateforme', 'verbose_name_plural': 'plateformes'},
),
migrations.AlterField(
model_name='game',
name='collection',
field=models.ForeignKey(help_text='Game running platform', on_delete=django.db.models.deletion.CASCADE, related_name='games', to='games.Platform', verbose_name='plateforme'),
),
migrations.AlterField(
model_name='game',
name='name',
field=models.CharField(help_text='Game title', max_length=255, verbose_name='nom'),
),
migrations.AlterField(
model_name='game',
name='note',
field=models.CharField(blank=True, help_text='Courte note affichée à ceux qui vous suive.', max_length=150, null=True, verbose_name='Note de progression'),
),
migrations.AlterField(
model_name='game',
name='status',
field=models.CharField(choices=[('created', 'Nouveau'), ('beaten', 'Terminé (quête principale)'), ('completed', 'Terminé complètement'), ('excluded', 'Exclu'), ('mastered', 'Usé / Épuisé'), ('unfinished', 'Inachevé')], default='unfinished', help_text='Game progression', max_length=30, verbose_name='état'),
),
migrations.AlterField(
model_name='platform',
name='name',
field=models.CharField(help_text='Nom de plateforme le plus utilisé.', max_length=255, verbose_name='nom'),
),
migrations.AlterField(
model_name='timeline',
name='date',
field=models.DateField(default=datetime.datetime.now, help_text='Status change date', verbose_name='date'),
),
migrations.AlterField(
model_name='timeline',
name='item',
field=models.ForeignKey(help_text='Which game?', on_delete=django.db.models.deletion.CASCADE, to='games.Game', verbose_name='jeu'),
),
migrations.AlterField(
model_name='timeline',
name='status',
field=models.CharField(choices=[('created', 'Nouveau'), ('beaten', 'Terminé (quête principale)'), ('completed', 'Terminé complètement'), ('excluded', 'Exclu'), ('mastered', 'Usé / Épuisé'), ('unfinished', 'Inachevé')], default='created', help_text='New status for this game at the given date', max_length=30, verbose_name='état'),
),
]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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)

View File

@ -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 %}

View File

@ -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 %}

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

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

View File

@ -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

View File

@ -0,0 +1,6 @@
-r requirements.txt
django-debug-toolbar
flake8
pre-commit
pylint
pylint_django

View File

@ -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

View File

@ -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