diff --git a/bikeshop_project/bike/__init__.py b/bikeshop_project/bike/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bikeshop_project/bike/admin.py b/bikeshop_project/bike/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/bikeshop_project/bike/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/bikeshop_project/bike/apps.py b/bikeshop_project/bike/apps.py new file mode 100644 index 0000000..c6b5601 --- /dev/null +++ b/bikeshop_project/bike/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class BikeConfig(AppConfig): + name = 'bike' diff --git a/bikeshop_project/bike/migrations/0001_bike_with_fsm.py b/bikeshop_project/bike/migrations/0001_bike_with_fsm.py new file mode 100644 index 0000000..018ac32 --- /dev/null +++ b/bikeshop_project/bike/migrations/0001_bike_with_fsm.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2017-01-05 02:27 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import django_fsm + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('registration', '0002_auto_20161130_0157'), + ] + + operations = [ + migrations.CreateModel( + name='Bike', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('colour', models.TextField()), + ('make', models.TextField()), + ('size', models.TextField(choices=[('C', 'child'), ('M', 'medium'), ('L', 'large'), ('XL', 'extra large')], max_length=2)), + ('serial_number', models.TextField()), + ('source', models.TextField(choices=[('COS_BIKE_DIVERSION_PILOT', 'City of Saskatoon Bike Diversion Pilot'), ('UOFS', 'University of Saskatchewan'), ('DROP_OFF', 'Drop Off')])), + ('stripped', models.NullBooleanField()), + ('price', models.DecimalField(blank=True, decimal_places=2, max_digits=8, null=True)), + ('state', django_fsm.FSMField(choices=[('RECEIVED', 'Received'), ('ASSESSED', 'Assessed'), ('AVAILABLE', 'CPIC Searched'), ('CLAIMED', 'Claimed'), ('SCRAPPED', 'Scrapped'), ('PURCHASED', 'Purchased'), ('TRANSFERRED_TO_POLICE', 'Transferred to police')], default='RECEIVED', max_length=50, protected=True)), + ('stolen', models.NullBooleanField()), + ('donated_by', models.TextField()), + ('donated_at', models.DateField()), + ('created_at', models.DateTimeField(default=django.utils.timezone.now)), + ('modified_at', models.DateTimeField(auto_now=True)), + ('cpic_searched_at', models.DateTimeField(blank=True, null=True)), + ('claimed_at', models.DateTimeField(blank=True, null=True)), + ('last_worked_on', models.DateTimeField(blank=True, null=True)), + ('purchased_at', models.DateTimeField(blank=True, null=True)), + ('claimed_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='registration.Member')), + ], + ), + ] diff --git a/bikeshop_project/bike/migrations/0002_auto_20170105_0238.py b/bikeshop_project/bike/migrations/0002_auto_20170105_0238.py new file mode 100644 index 0000000..392bb86 --- /dev/null +++ b/bikeshop_project/bike/migrations/0002_auto_20170105_0238.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.10.4 on 2017-01-05 02:38 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('registration', '0002_auto_20161130_0157'), + ('bike', '0001_bike_with_fsm'), + ] + + operations = [ + migrations.AddField( + model_name='bike', + name='purchased_by', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='purchased_bike', to='registration.Member'), + ), + migrations.AlterField( + model_name='bike', + name='claimed_by', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='claimed_bike', to='registration.Member'), + ), + ] diff --git a/bikeshop_project/bike/migrations/__init__.py b/bikeshop_project/bike/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bikeshop_project/bike/models.py b/bikeshop_project/bike/models.py new file mode 100644 index 0000000..afdb9e5 --- /dev/null +++ b/bikeshop_project/bike/models.py @@ -0,0 +1,123 @@ +from datetime import timedelta +from django.db import models +from django.utils import timezone + +from django_fsm import FSMField, transition + +from registration.models import Member + + +class BikeState(object): + RECEIVED = 'RECEIVED' + ASSESSED = 'ASSESSED' + AVAILABLE = 'AVAILABLE' + CLAIMED = 'CLAIMED' + PURCHASED = 'PURCHASED' + SCRAPPED = 'SCRAPPED' + TRANSFERRED_TO_POLICE = 'TRANSFERRED_TO_POLICE' + CHOICES = ( + (RECEIVED, 'Received'), + (ASSESSED, 'Assessed'), + (AVAILABLE, 'CPIC Searched'), + (CLAIMED, 'Claimed'), + (SCRAPPED, 'Scrapped'), + (PURCHASED, 'Purchased'), + (TRANSFERRED_TO_POLICE, 'Transferred to police') + ) + + +class Bike(models.Model): + CHILD = 'C' + SMALL = 'S' + MEDIUM = 'M' + LARGE = 'L' + EXTRA_LARGE = 'XL' + + size_choices = ( + (CHILD, 'child'), + (MEDIUM, 'medium'), + (LARGE, 'large'), + (EXTRA_LARGE, 'extra large'), + ) + + COS_BIKE_DIVERSION_PILOT = 'COS_BIKE_DIVERSION_PILOT' + UOFS = 'UOFS' + DROP_OFF = 'DROP_OFF' + + source_choices = ( + (COS_BIKE_DIVERSION_PILOT, 'City of Saskatoon Bike Diversion Pilot'), + (UOFS, 'University of Saskatchewan'), + (DROP_OFF, 'Drop Off'), + ) + + colour = models.TextField(blank=False, null=False) + make = models.TextField(blank=False, null=False) + size = models.TextField(choices=size_choices, max_length=2) + serial_number = models.TextField(blank=False, null=False) + source = models.TextField(blank=False, null=False, choices=source_choices) + stripped = models.NullBooleanField() + price = models.DecimalField(max_digits=8, decimal_places=2, blank=True, null=True) + state = FSMField(default=BikeState.RECEIVED, choices=BikeState.CHOICES, protected=True) + claimed_by = models.ForeignKey(Member, on_delete=models.SET_NULL, null=True, related_name='claimed_bike') + stolen = models.NullBooleanField() + purchased_by = models.ForeignKey(Member, on_delete=models.SET_NULL, null=True, related_name='purchased_bike') + donated_by = models.TextField() + donated_at = models.DateField(blank=False, null=False) + created_at = models.DateTimeField(default=timezone.now) + modified_at = models.DateTimeField(auto_now=True) + cpic_searched_at = models.DateTimeField(blank=True, null=True) + claimed_at = models.DateTimeField(blank=True, null=True) + last_worked_on = models.DateTimeField(blank=True, null=True) + purchased_at = models.DateTimeField(blank=True, null=True) + + def can_assessed(self): + return self.colour is not None and self.make is not None and self.size is not None and self.source is not None \ + and self.price is not None + + def can_available(self): + return self.stolen is not None and self.cpic_searched_at is not None and self.serial_number is not None + + def can_claim(self): + return self.claimed_by is None or self.last_worked_on > timezone.now() + timedelta(weeks=4) + + def can_purchase(self): + if self.claimed_by: + return self.can_claim() + + return self.purchased_by is None + + def can_scrap(self): + return self.stripped is not None + + def can_transfer_to_police(self): + return self.stolen + + @transition(field=state, source=[BikeState.RECEIVED], target=BikeState.ASSESSED, conditions=[can_assessed]) + def assessed(self): + pass + + @transition(field=state, source=[BikeState.ASSESSED, BikeState.RECEIVED], target=BikeState.AVAILABLE, + conditions=[can_available]) + def available(self): + pass + + @transition(field=state, source=[BikeState.AVAILABLE], target=BikeState.CLAIMED, conditions=[can_claim]) + def claim(self, member): + self.claimed_by = member + self.claimed_at = timezone.now() + self.last_worked_on = timezone.now() + + @transition(field=state, source=[BikeState.AVAILABLE, BikeState.CLAIMED], target=BikeState.PURCHASED, + conditions=[can_purchase]) + def purchase(self, member): + self.purchased_at = timezone.now() + self.purchased_by = member + + @transition(field=state, source=[BikeState.ASSESSED, BikeState.AVAILABLE, BikeState.CLAIMED], + target=BikeState.SCRAPPED, conditions=[can_scrap]) + def scrap(self): + pass + + @transition(field=state, source=[BikeState.ASSESSED, BikeState.RECEIVED], conditions=[can_transfer_to_police]) + def transfer_to_police(self): + pass diff --git a/bikeshop_project/bike/tests.py b/bikeshop_project/bike/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/bikeshop_project/bike/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/bikeshop_project/bike/views.py b/bikeshop_project/bike/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/bikeshop_project/bike/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/bikeshop_project/bikeshop/settings/base.py b/bikeshop_project/bikeshop/settings/base.py index 3f35fd8..3f2d2b1 100644 --- a/bikeshop_project/bikeshop/settings/base.py +++ b/bikeshop_project/bikeshop/settings/base.py @@ -36,6 +36,7 @@ INSTALLED_APPS = [ 'registration', 'core', + 'bike', ] MIDDLEWARE_CLASSES = [