WIP: Begin ae app with subscription handling

This commit is contained in:
Skia 2015-12-15 17:50:50 +01:00
parent a6aac76d75
commit b35483d2a9
12 changed files with 247 additions and 0 deletions

0
ae/__init__.py Normal file
View File

3
ae/admin.py Normal file
View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
('core', '0002_auto_20151215_0827'),
]
operations = [
migrations.CreateModel(
name='Member',
fields=[
('user', models.OneToOneField(primary_key=True, to=settings.AUTH_USER_MODEL, serialize=False)),
],
),
migrations.CreateModel(
name='Subscription',
fields=[
('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')),
('subscription_type', models.CharField(choices=[('cursus branche', 'Cursus Branche'), ('cursus tronc commun', 'Cursus Tronc Commun'), ('deux semestres', 'Deux semestres'), ('un semestre', 'Un semestre')], verbose_name='subscription type', max_length=255)),
('subscription_start', models.DateField(verbose_name='subscription start')),
('subscription_end', models.DateField(verbose_name='subscription end')),
('payment_method', models.CharField(choices=[('cheque', 'Chèque'), ('cash', 'Espèce'), ('other', 'Autre')], verbose_name='payment method', max_length=255)),
('member', models.ForeignKey(related_name='subscriptions', to='ae.Member')),
],
options={
'ordering': ['subscription_start'],
},
),
]

View File

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ae', '0001_initial'),
]
operations = [
migrations.AlterField(
model_name='subscription',
name='subscription_type',
field=models.CharField(verbose_name='subscription type', max_length=255, choices=[('cursus-branche', 'Cursus Branche'), ('cursus-tronc-commun', 'Cursus Tronc Commun'), ('deux-semestres', 'Deux semestres'), ('un-semestre', 'Un semestre')]),
),
]

View File

107
ae/models.py Normal file
View File

@ -0,0 +1,107 @@
from datetime import date, timedelta
from django.db import models
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from django.conf import settings
from core.models import User
def validate_type(value):
if value not in settings.AE_SUBSCRIPTIONS.keys():
raise ValidationError(_('Bad subscription type'))
def validate_payment(value):
if value not in settings.AE_PAYMENT_METHOD:
raise ValidationError(_('Bad payment method'))
class Member(models.Model):
user = models.OneToOneField(User, primary_key=True)
def is_subscribed(self):
return self.subscriptions.last().is_valid_now()
class Subscription(models.Model):
member = models.ForeignKey(Member, related_name='subscriptions')
subscription_type = models.CharField(_('subscription type'),
max_length=255,
choices=((k.lower().replace(' ', '-'), k) for k in sorted(settings.AE_SUBSCRIPTIONS.keys())))
subscription_start = models.DateField(_('subscription start'))
subscription_end = models.DateField(_('subscription end'))
payment_method = models.CharField(_('payment method'), max_length=255, choices=settings.AE_PAYMENT_METHOD)
class Meta:
ordering = ['subscription_start',]
@staticmethod
def compute_start(d=date.today()):
"""
This function computes the start date of the subscription with respect to the given date (default is today),
and the start date given in settings.AE_START_DATE.
It takes the nearest past start date.
Exemples: with AE_START_DATE = (8, 15)
Today -> Start date
2015-03-17 -> 2015-02-15
2015-01-11 -> 2014-08-15
"""
today = d
year = today.year
start = date(year, settings.AE_START_DATE[0], settings.AE_START_DATE[1])
start2 = start.replace(month=(start.month+6)%12)
if start > start2:
start, start2 = start2, start
if today < start:
return start2.replace(year=year-1)
elif today < start2:
return start
else:
return start2
@staticmethod
def compute_end(duration, start=None):
"""
This function compute the end date of the subscription given a start date and a duration in number of semestre
Exemple:
Start - Duration -> End date
2015-09-18 - 1 -> 2016-03-18
2015-09-18 - 2 -> 2016-09-18
2015-09-18 - 3 -> 2017-03-18
2015-09-18 - 4 -> 2017-09-18
"""
if start is None:
start = Subscription.compute_start()
# This can certainly be simplified, but it works like this
return start.replace(month=(start.month+6*duration)%12,
year=start.year+int(duration/2)+(1 if start.month > 6 and duration%2 == 1 else 0))
def is_valid_now(self):
return self.subscription_start <= date.today() and date.today() <= self.subscription_end
def guy_test(date):
print(str(date)+" -> "+str(Subscription.compute_start(date)))
def bibou_test(duration, date=None):
print(str(date)+" - "+str(duration)+" -> "+str(Subscription.compute_end(duration, date)))
def guy():
guy_test(date(2015, 7, 11))
guy_test(date(2015, 8, 11))
guy_test(date(2015, 2, 17))
guy_test(date(2015, 3, 17))
guy_test(date(2015, 1, 11))
guy_test(date(2015, 2, 11))
guy_test(date(2015, 8, 17))
guy_test(date(2015, 9, 17))
print('='*80)
bibou_test(1, date(2015, 2, 18))
bibou_test(2, date(2015, 2, 18))
bibou_test(3, date(2015, 2, 18))
bibou_test(4, date(2015, 2, 18))
bibou_test(1, date(2015, 9, 18))
bibou_test(2, date(2015, 9, 18))
bibou_test(3, date(2015, 9, 18))
bibou_test(4, date(2015, 9, 18))
bibou_test(1)
bibou_test(2)
bibou_test(3)
bibou_test(4)
if __name__ == "__main__":
guy()

View File

@ -0,0 +1,18 @@
{% extends "core/base.html" %}
{% block title %}
{% if profile %}
Edit {{ member.user.get_display_name }}'s subscription
{% endif %}
{% endblock %}
{% block content %}
<h3>Edit subscription</h3>
{# <p><a href="{% url 'core:user_profile' object.member.user.id %}">Go to profile</a></p> #}
{# <p>You're editing the subscription of <strong>{{ member.user.get_display_name }}</strong></p>#}
<form action="" method="post">
{% csrf_token %}
{{ form.as_p }}
<p><input type="submit" value="Save!" /></p>
</form>
{% endblock %}

3
ae/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

11
ae/urls.py Normal file
View File

@ -0,0 +1,11 @@
from django.conf.urls import url, include
from ae.views import *
urlpatterns = [
# Subscription views
url(r'^subscription/(?P<user_id>[0-9]+)/$', NewSubscription.as_view(), name='subscription'),
]

21
ae/views.py Normal file
View File

@ -0,0 +1,21 @@
from django.shortcuts import render
from django.views.generic.edit import UpdateView, CreateView
from django import forms
from django.forms import Select
from django.conf import settings
from ae.models import Member, Subscription
from core.views import CanEditMixin, CanEditPropMixin, CanViewMixin
class SubscriptionForm(forms.ModelForm):
class Meta:
model = Subscription
fields = ['subscription_type', 'payment_method']
#widgets = {
# 'subscription_type': Select(choices={(k.lower(), k+" - "+str(v['price'])+"€"+str(Subscription.compute_end(2))) for k,v in settings.AE_SUBSCRIPTIONS.items()}),
#}
class NewSubscription(CanEditMixin, CreateView):
template_name = 'ae/subscription.html'
form_class = SubscriptionForm

View File

@ -38,6 +38,7 @@ INSTALLED_APPS = (
'django.contrib.messages',
'django.contrib.staticfiles',
'core',
'ae',
)
MIDDLEWARE_CLASSES = (
@ -117,6 +118,9 @@ EMAIL_HOST="localhost"
EMAIL_PORT=25
# AE configuration
# Define the date in the year serving as reference for the subscriptions calendar
# (month, day)
AE_START_DATE = (8, 15) # 15th August
AE_GROUPS = {
'root': {
'id': 1,
@ -136,3 +140,28 @@ AE_GROUPS = {
},
}
AE_PAYMENT_METHOD = [('cheque', 'Chèque'),
('cash', 'Espèce'),
('other', 'Autre'),
]
# Subscription durations are in semestres (should be settingized)
AE_SUBSCRIPTIONS = {
'Un semestre': {
'price': 15,
'duration': 1,
},
'Deux semestres': {
'price': 28,
'duration': 2,
},
'Cursus Tronc Commun': {
'price': 45,
'duration': 4,
},
'Cursus Branche': {
'price': 45,
'duration': 6,
},
# To be completed....
}

View File

@ -21,5 +21,6 @@ handler404 = "core.views.not_found"
urlpatterns = [
url(r'^', include('core.urls', namespace="core", app_name="core")),
url(r'^ae/', include('ae.urls', namespace="ae", app_name="ae")),
url(r'^admin/', include(admin.site.urls)),
]