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:
2017-08-22 21:02:43 +02:00
parent deac1710ca
commit 242b0edff0
11 changed files with 198 additions and 39 deletions

View File

@ -1,5 +1,5 @@
from django.contrib import admin
from games.models import Console, Game
from games.models import Console, Game, Timeline
class GameAdmin(admin.ModelAdmin):
@ -11,5 +11,12 @@ class GameAdmin(admin.ModelAdmin):
'wish']
search_fields = ('name',)
class TimelineAdmin(admin.ModelAdmin):
list_display = (
'date', 'status', 'item')
admin.site.register(Console)
admin.site.register(Game, GameAdmin)
admin.site.register(Timeline, TimelineAdmin)

View File

@ -1,5 +1,28 @@
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):
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)

View File

@ -1,7 +1,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
import datetime
from django.db import migrations, models
import django.db.models.deletion
@ -18,21 +19,39 @@ class Migration(migrations.Migration):
name='Console',
fields=[
('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(
name='Game',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=254)),
('console', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='games.Console')),
('name', models.CharField(max_length=255)),
('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)),
('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,
},
),
]

View File

@ -1,53 +1,51 @@
from core.models import Collection, Item, Timeline as BaseTimeline
from django.db import models
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.
"""
name = models.CharField(max_length=254)
def __str__(self):
return '%s' % self.name
class Meta:
ordering = ('name',)
class Game(models.Model):
class Game(Item):
"""
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
BEATEN = 0
COMPLETED = 1
EXCLUDED = 2
MASTERED = 3
UNFINISHED = 4
BEATEN = 'beaten'
COMPLETED = 'completed'
EXCLUDED = 'excluded'
MASTERED = 'mastered'
UNFINISHED = 'unfinished'
STATUS_CHOICES = (
(UNFINISHED, _('Unfinished')),
(BEATEN, _('Beaten')),
(COMPLETED, _('Completed')),
(EXCLUDED, _('Excluded')),
(MASTERED, _('Mastered')),
(UNFINISHED, _('Unfinished')),
)
# required
name = models.CharField(max_length=254)
console = models.ForeignKey('games.Console')
status = models.IntegerField(
choices=STATUS_CHOICES,
default=UNFINISHED)
DEFAULT_CHOICE = UNFINISHED
# others
playing = models.BooleanField(default=False)
unplayed = models.BooleanField(default=False)
wish = models.BooleanField(default=False)
def __str__(self):
return '%s' % self.name
class Meta:
ordering = ('-playing', 'name')
class Timeline(BaseTimeline):
TARGET_MODEL = 'games.Game'
STATUS_CHOICES = Game.STATUS_CHOICES
DEFAULT_CHOICE = Item.DEFAULT_CHOICE

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

View File

@ -10,11 +10,11 @@ class GameTest(TestCase):
def setUp(self):
self.console = Console.objects.create(name='BestConsole4Ever')
Game.objects.create(
name='Deponia', playing=False, console=self.console)
name='Deponia', playing=False, collection=self.console)
Game.objects.create(
name='Aladdin', playing=True, console=self.console)
name='Aladdin', playing=True, collection=self.console)
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):