Refonte de la base:
* utilisation de classes abstraites Django : Item et Collection * héritage de ces classes pour Game et Console * création d'un objet Timeline contenant le changement d'état des jeux * affichage de la Timeline sur l'interface Admin
This commit is contained in:
parent
deac1710ca
commit
242b0edff0
5
TODO
5
TODO
@ -8,10 +8,7 @@
|
|||||||
* API django rest framework
|
* API django rest framework
|
||||||
* documentation API
|
* documentation API
|
||||||
* travis.yml to launch test
|
* travis.yml to launch test
|
||||||
* ajouter une date d'obtention du jeu vidéo (par défaut aujourd'hui)
|
* ajouter une date d'obtention du jeu vidéo (par défaut aujourd'hui)
|
||||||
* gérer l'historique de changement d'un jeu : faire une table qui contient jeu, date et état. Triée par date descendant (le plus récent au dessus). L'appeler memory ?
|
|
||||||
* que faire pour fusionner les dates d'obtention des jeux vidéos et l'historique des changements sur ces derniers afin d'obtenir une sorte de table contenant l'ensemble des activités sur l'application ? Ceci permettrait d'avoir un encart de "news" des derniers changements effectués sur le site
|
|
||||||
|
|
||||||
|
|
||||||
# Idée
|
# Idée
|
||||||
|
|
||||||
|
@ -37,7 +37,8 @@ INSTALLED_APPS = [
|
|||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'games',
|
'core',
|
||||||
|
'games.apps.GamesConfig',
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
|
0
collection/core/__init__.py
Normal file
0
collection/core/__init__.py
Normal file
6
collection/core/apps.py
Normal file
6
collection/core/apps.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class CoreConfig(AppConfig):
|
||||||
|
name = 'core'
|
||||||
|
|
86
collection/core/models.py
Normal file
86
collection/core/models.py
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
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)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '%s' % self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
ordering = ['name']
|
||||||
|
|
||||||
|
|
||||||
|
class Item(models.Model):
|
||||||
|
TARGET_MODEL = None
|
||||||
|
TARGET_VERBOSE_NAME = None
|
||||||
|
|
||||||
|
# status choices
|
||||||
|
CREATED = 'created'
|
||||||
|
STATUS_CHOICES = (
|
||||||
|
(CREATED, _('New')),
|
||||||
|
)
|
||||||
|
DEFAULT_CHOICE = CREATED
|
||||||
|
|
||||||
|
name = models.CharField(max_length=255)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '%s' % self.name
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def on_class_prepared(cls):
|
||||||
|
"""
|
||||||
|
Add new field 'collection' which is a link to TARGET_MODEL.
|
||||||
|
Add new field 'status' which is a list of choices. With CREATED.
|
||||||
|
"""
|
||||||
|
target_field = models.ForeignKey(
|
||||||
|
cls.TARGET_MODEL,
|
||||||
|
related_name=cls.RELATED_TARGET_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)
|
||||||
|
status_field.contribute_to_class(cls, 'status')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
ordering = ['name']
|
||||||
|
|
||||||
|
|
||||||
|
class Timeline(models.Model):
|
||||||
|
"""
|
||||||
|
Keep changes on collection.
|
||||||
|
For an example, a game cretion date. Or when you completed a game.
|
||||||
|
"""
|
||||||
|
TARGET_MODEL = None
|
||||||
|
STATUS_CHOICES = Item.STATUS_CHOICES
|
||||||
|
DEFAULT_CHOICE = Item.CREATED
|
||||||
|
date = models.DateTimeField(default=datetime.now)
|
||||||
|
|
||||||
|
@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.contribute_to_class(cls, 'item')
|
||||||
|
status_field = models.CharField(
|
||||||
|
max_length=30,
|
||||||
|
choices=Item.STATUS_CHOICES + cls.STATUS_CHOICES,
|
||||||
|
default=cls.DEFAULT_CHOICE)
|
||||||
|
status_field.contribute_to_class(cls, 'status')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return '%s: %s - %s' % (
|
||||||
|
self.date.strftime('%Y-%m-%d'),
|
||||||
|
self.status,
|
||||||
|
self.item)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
||||||
|
ordering = ('-date',)
|
@ -1,5 +1,5 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from games.models import Console, Game
|
from games.models import Console, Game, Timeline
|
||||||
|
|
||||||
|
|
||||||
class GameAdmin(admin.ModelAdmin):
|
class GameAdmin(admin.ModelAdmin):
|
||||||
@ -11,5 +11,12 @@ class GameAdmin(admin.ModelAdmin):
|
|||||||
'wish']
|
'wish']
|
||||||
search_fields = ('name',)
|
search_fields = ('name',)
|
||||||
|
|
||||||
|
|
||||||
|
class TimelineAdmin(admin.ModelAdmin):
|
||||||
|
list_display = (
|
||||||
|
'date', 'status', 'item')
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(Console)
|
admin.site.register(Console)
|
||||||
admin.site.register(Game, GameAdmin)
|
admin.site.register(Game, GameAdmin)
|
||||||
|
admin.site.register(Timeline, TimelineAdmin)
|
||||||
|
@ -1,5 +1,28 @@
|
|||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
|
from django.db.models import signals
|
||||||
|
|
||||||
|
|
||||||
|
def call_on_class_prepared(sender, **kwargs):
|
||||||
|
"""
|
||||||
|
Calls the function only if it is defined in the class being prepared
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
sender.on_class_prepared()
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class GamesConfig(AppConfig):
|
class GamesConfig(AppConfig):
|
||||||
name = 'games'
|
name = 'games'
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
"""
|
||||||
|
Add signals to the application
|
||||||
|
"""
|
||||||
|
from .models import Game
|
||||||
|
from .signals import game_saved
|
||||||
|
signals.post_save.connect(game_saved, sender='games.Game')
|
||||||
|
|
||||||
|
def __init__(self, app_name, app_module):
|
||||||
|
super(GamesConfig, self).__init__(app_name, app_module)
|
||||||
|
signals.class_prepared.connect(call_on_class_prepared)
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
# Generated by Django 1.11 on 2017-08-16 19:55
|
# Generated by Django 1.11 on 2017-08-22 18:56
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import datetime
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
|
||||||
@ -18,21 +19,39 @@ class Migration(migrations.Migration):
|
|||||||
name='Console',
|
name='Console',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('name', models.CharField(max_length=254)),
|
('name', models.CharField(max_length=255)),
|
||||||
],
|
],
|
||||||
options={'ordering': ('name',)},
|
options={
|
||||||
|
'ordering': ['name'],
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Game',
|
name='Game',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('name', models.CharField(max_length=254)),
|
('name', models.CharField(max_length=255)),
|
||||||
('console', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='games.Console')),
|
|
||||||
('playing', models.BooleanField(default=False)),
|
('playing', models.BooleanField(default=False)),
|
||||||
('status', models.IntegerField(choices=[(4, 'Unfinished'), (0, 'Beaten'), (1, 'Completed'), (2, 'Excluded'), (3, 'Mastered')], default=4)),
|
|
||||||
('unplayed', models.BooleanField(default=False)),
|
('unplayed', models.BooleanField(default=False)),
|
||||||
('wish', models.BooleanField(default=False)),
|
('wish', models.BooleanField(default=False)),
|
||||||
|
('status', models.CharField(choices=[('created', 'New'), ('beaten', 'Beaten'), ('completed', 'Completed'), ('excluded', 'Excluded'), ('mastered', 'Mastered'), ('unfinished', 'Unfinished')], default='unfinished', max_length=30)),
|
||||||
|
('collection', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='games', to='games.Console', verbose_name='console')),
|
||||||
],
|
],
|
||||||
options={'ordering': ('-playing', 'name')},
|
options={
|
||||||
|
'ordering': ('-playing', 'name'),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Timeline',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('date', models.DateTimeField(default=datetime.datetime.now)),
|
||||||
|
('status', models.CharField(choices=[('created', 'New'), ('beaten', 'Beaten'), ('completed', 'Completed'), ('excluded', 'Excluded'), ('mastered', 'Mastered'), ('unfinished', 'Unfinished')], default='created', max_length=30)),
|
||||||
|
('item', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='games.Game')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ('-date',),
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -1,53 +1,51 @@
|
|||||||
|
from core.models import Collection, Item, Timeline as BaseTimeline
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
|
|
||||||
|
|
||||||
class Console(models.Model):
|
class Console(Collection):
|
||||||
"""
|
"""
|
||||||
All console, system or box that can be used to play video games.
|
All console, system or box that can be used to play video games.
|
||||||
"""
|
"""
|
||||||
name = models.CharField(max_length=254)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return '%s' % self.name
|
return '%s' % self.name
|
||||||
|
|
||||||
class Meta:
|
|
||||||
ordering = ('name',)
|
|
||||||
|
|
||||||
|
class Game(Item):
|
||||||
class Game(models.Model):
|
|
||||||
"""
|
"""
|
||||||
A video game you will use on a specific Console.
|
A video game you will use on a specific Console.
|
||||||
"""
|
"""
|
||||||
|
# class config
|
||||||
|
TARGET_MODEL = 'games.Console'
|
||||||
|
TARGET_VERBOSE_NAME = _('console')
|
||||||
|
RELATED_TARGET_NAME = 'games'
|
||||||
|
|
||||||
# Status choices
|
# Status choices
|
||||||
BEATEN = 0
|
BEATEN = 'beaten'
|
||||||
COMPLETED = 1
|
COMPLETED = 'completed'
|
||||||
EXCLUDED = 2
|
EXCLUDED = 'excluded'
|
||||||
MASTERED = 3
|
MASTERED = 'mastered'
|
||||||
UNFINISHED = 4
|
UNFINISHED = 'unfinished'
|
||||||
|
|
||||||
STATUS_CHOICES = (
|
STATUS_CHOICES = (
|
||||||
(UNFINISHED, _('Unfinished')),
|
|
||||||
(BEATEN, _('Beaten')),
|
(BEATEN, _('Beaten')),
|
||||||
(COMPLETED, _('Completed')),
|
(COMPLETED, _('Completed')),
|
||||||
(EXCLUDED, _('Excluded')),
|
(EXCLUDED, _('Excluded')),
|
||||||
(MASTERED, _('Mastered')),
|
(MASTERED, _('Mastered')),
|
||||||
|
(UNFINISHED, _('Unfinished')),
|
||||||
)
|
)
|
||||||
|
DEFAULT_CHOICE = UNFINISHED
|
||||||
# required
|
|
||||||
name = models.CharField(max_length=254)
|
|
||||||
console = models.ForeignKey('games.Console')
|
|
||||||
status = models.IntegerField(
|
|
||||||
choices=STATUS_CHOICES,
|
|
||||||
default=UNFINISHED)
|
|
||||||
|
|
||||||
# others
|
# others
|
||||||
playing = models.BooleanField(default=False)
|
playing = models.BooleanField(default=False)
|
||||||
unplayed = models.BooleanField(default=False)
|
unplayed = models.BooleanField(default=False)
|
||||||
wish = models.BooleanField(default=False)
|
wish = models.BooleanField(default=False)
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return '%s' % self.name
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
ordering = ('-playing', 'name')
|
ordering = ('-playing', 'name')
|
||||||
|
|
||||||
|
|
||||||
|
class Timeline(BaseTimeline):
|
||||||
|
TARGET_MODEL = 'games.Game'
|
||||||
|
STATUS_CHOICES = Game.STATUS_CHOICES
|
||||||
|
DEFAULT_CHOICE = Item.DEFAULT_CHOICE
|
||||||
|
22
collection/games/signals.py
Normal file
22
collection/games/signals.py
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
from games.models import Timeline
|
||||||
|
|
||||||
|
|
||||||
|
def game_saved(sender, instance, created, raw, using, update_fields,
|
||||||
|
**kwargs):
|
||||||
|
"""
|
||||||
|
Add timeline entry.
|
||||||
|
If game is created, add 2 timlines: 1 with CREATED status. The other with
|
||||||
|
current object status.
|
||||||
|
"""
|
||||||
|
# FIXME: don't write a timeline if previous have same title and object_id
|
||||||
|
entry = {
|
||||||
|
'item': instance,
|
||||||
|
'status': instance.status,
|
||||||
|
}
|
||||||
|
# Add CREATED status if Game was created
|
||||||
|
if created is True:
|
||||||
|
new_entry = dict(entry)
|
||||||
|
new_entry.update({'status': instance.CREATED})
|
||||||
|
Timeline.objects.create(**new_entry)
|
||||||
|
# Add new timeline entry
|
||||||
|
Timeline.objects.create(**entry)
|
@ -10,11 +10,11 @@ class GameTest(TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.console = Console.objects.create(name='BestConsole4Ever')
|
self.console = Console.objects.create(name='BestConsole4Ever')
|
||||||
Game.objects.create(
|
Game.objects.create(
|
||||||
name='Deponia', playing=False, console=self.console)
|
name='Deponia', playing=False, collection=self.console)
|
||||||
Game.objects.create(
|
Game.objects.create(
|
||||||
name='Aladdin', playing=True, console=self.console)
|
name='Aladdin', playing=True, collection=self.console)
|
||||||
Game.objects.create(
|
Game.objects.create(
|
||||||
name='Persona 5', playing=True, console=self.console)
|
name='Persona 5', playing=True, collection=self.console)
|
||||||
|
|
||||||
|
|
||||||
def test_game_are_sorted_by_playing_and_name(self):
|
def test_game_are_sorted_by_playing_and_name(self):
|
||||||
|
Loading…
Reference in New Issue
Block a user