initial commit

This commit is contained in:
artus40
2017-09-21 13:12:35 +02:00
parent a270cb55de
commit 2e0cde3552
31 changed files with 703 additions and 0 deletions

0
core/__init__.py Normal file
View File

14
core/admin.py Normal file
View File

@@ -0,0 +1,14 @@
from django.contrib import admin
from .models import (Enregistrement, Etiquette)
# Register your models here.
@admin.register(Etiquette)
class EtiquetteAdmin(admin.ModelAdmin):
pass
@admin.register(Enregistrement)
class EnregistrementAdmin(admin.ModelAdmin):
pass

5
core/apps.py Normal file
View File

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

25
core/forms.py Normal file
View File

@@ -0,0 +1,25 @@
from django import forms
from .models import Enregistrement
class EnregistrementForm(forms.ModelForm):
est_negatif = forms.BooleanField(required=False)
class Meta:
model = Enregistrement
fields = ('montant', 'description', 'date', 'etiquette')
widgets = {'etiquette': forms.Select(
attrs={'class':'form-control custom-select'}
),
'date': forms.SelectDateWidget(
attrs={'class':'form-control custom-select mx-1'}
)
}
def clean(self):
data = super().clean()
print(data)
# Force un nombre négatif si 'est_negatif' est coché
if (data['est_negatif']
and data['montant'] > 0):
self.cleaned_data['montant'] = 0 - data['montant']
return self.cleaned_data

View File

@@ -0,0 +1,45 @@
import csv
import collections
from django.core.management.base import BaseCommand, CommandError
from core.models import Enregistrement, Etiquette
class Command(BaseCommand):
help = "Importe des enregistrement à partir d'un fichier .csv"
mapping = ("date", "description", "etiquette", "montant")
def add_arguments(self, parser):
parser.add_argument("files", nargs="+", type=str)
parser.add_argument("-t", "--test", action="store_true")
def handle(self, *args, **options):
header = "== Importation au format .csv"
if options['test']:
header += " (test)"
header += " ==\n%s" % (self.mapping,)
self.stdout.write(self.style.MIGRATE_HEADING(header))
for filepath in options['files']:
with open(filepath, 'r') as f:
self.stdout.write(self.style.WARNING("[-] %s " % filepath), ending='')
reader = csv.reader(f)
results = list()
for row in reader:
result = collections.OrderedDict()
for i, field in enumerate(self.mapping):
result[field] = row[i]
results.append(result)
self.stdout.write(self.style.SUCCESS("Ok (%i results):" % len(results)))
for result in results:
output = ", ".join(map(lambda t: "%s:%s" % (t[0][0:4], t[1]), result.items()))
if not options['test']:
# Création d'un nouvel enregistrement
date = result['date'] # TODO: format date !
etiquette, created = Etiquette.objects.get_or_create(nom=result['etiquette'])
Enregistrement.objects.create(date=result['date'],
description=result['description'],
etiquette=etiquette,
montant=result['montant'])
output = "+" + output
if created: output += "(added %s)" % etiquette
self.stdout.write(self.style.SUCCESS(" " + output))

125
core/models.py Normal file
View File

@@ -0,0 +1,125 @@
import datetime
from collections import defaultdict
from django.db import models
# Create your models here.
class Etiquette(models.Model):
nom = models.CharField(max_length=128, unique=True)
def __str__(self):
return "%s" % self.nom
class MonthManager(models.Manager):
def get_queryset_for_month(self, month, year):
return super().get_queryset().filter(date__month=month,
date__year=year)
def get_queryset(self, month=None, year=None):
""" Add custom 'month' and 'year' keyword arguments, used as filters.
Without argument, simply proxy to parent method.
This method checks if month is newly created (first access). If
so, it spawns recursive records for this month.
"""
# Generic call
if not month and not year:
return super().get_queryset()
# Custom call
elif month and year:
recursifs = EnregistrementRecursif.objects.all()
if (not self.get_queryset_for_month(month, year).exists()
and recursifs.exists()):
print("LOG: Month is created so we populated it...")
for r in recursifs:
r.spawn(month, year)
return self.get_queryset_for_month(month, year)
# Invalid call
else:
raise TypeError(
"You must call with either none or \
both 'month' and 'year' keyword arguments")
def _populate_data(self, qs, data):
def somme_par_etiquette(qs):
sommes = defaultdict(lambda:0)
for el in qs:
sommes[el.etiquette.nom] += el.montant
return dict(sommes)
data['balance'] = sum(map(lambda t: t[0], qs.values_list('montant')))
data['par_etiquette'] = somme_par_etiquette(qs)
return data
def calculate_data(self, qs):
#TODO: cached results
return self._populate_data(qs, {})
def get_month_with_data(self, month, year):
""" Retrieve the queryset for given month and year, plus
some data calculated on the set """
qs = self.get_queryset(month=month, year=year)
data = self.calculate_data(qs)
print(qs, data)
return qs, data
class Enregistrement(models.Model):
objects = MonthManager()
date = models.DateField()
montant = models.FloatField()
etiquette = models.ForeignKey(Etiquette)
description = models.CharField(max_length=512)
def __str__(self):
return "<Depense: %s>" % self.etiquette
class EnregistrementRecursif(models.Model):
jour = models.IntegerField()
montant = models.FloatField()
etiquette = models.ForeignKey(Etiquette)
description = models.CharField(max_length=512)
created_date = models.DateField()
def is_older(self, month, year):
return (self.created_date.month <= month
and self.created_date.year <= year)
def spawn(self, month, year, commit=True):
""" Spawn a new Enregistrement for given month and year. """
# Do not spawn for dates older than this instance creation date
if not self.is_older(month, year):
return None
# Create new Enregistrement from this instance values
date = datetime.date(year, month, self.jour)
new_object, created = Enregistrement.objects.get_or_create(date=date,
montant=self.montant,
etiquette=self.etiquette,
description=self.description)
if created and commit:
new_object.save()
return new_object
def save(self, **kwargs):
""" Update 'created_date' on each save, this avoids conflict with older
spawned instances.
"""
self.created_date = tz.now().date()
return super().save(**kwargs)
def __str__(self):
return "<[%i] %s (%s) : %s {%s}>" % (self.jour,
self.description,
self.etiquette,
self.montant,
self.created_date)

1
core/static/activity.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-activity"><polyline points="22 12 18 12 15 21 9 3 6 12 2 12"></polyline></svg>

After

Width:  |  Height:  |  Size: 275 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevrons-left"><polyline points="11 17 6 12 11 7"></polyline><polyline points="18 17 13 12 18 7"></polyline></svg>

After

Width:  |  Height:  |  Size: 311 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevrons-right"><polyline points="13 17 18 12 13 7"></polyline><polyline points="6 17 11 12 6 7"></polyline></svg>

After

Width:  |  Height:  |  Size: 311 B

1
core/static/delete.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-delete"><path d="M21 4H8l-7 8 7 8h13a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2z"></path><line x1="18" y1="9" x2="12" y2="15"></line><line x1="12" y1="9" x2="18" y2="15"></line></svg>

After

Width:  |  Height:  |  Size: 374 B

1
core/static/edit.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-edit"><path d="M20 14.66V20a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h5.34"></path><polygon points="18 2 22 6 12 16 8 16 8 12 18 2"></polygon></svg>

After

Width:  |  Height:  |  Size: 356 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-minus-circle"><circle cx="12" cy="12" r="10"></circle><line x1="8" y1="12" x2="16" y2="12"></line></svg>

After

Width:  |  Height:  |  Size: 308 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-minus-square"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><line x1="8" y1="12" x2="16" y2="12"></line></svg>

After

Width:  |  Height:  |  Size: 330 B

1
core/static/plus.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-plus"><line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line></svg>

After

Width:  |  Height:  |  Size: 297 B

1
core/static/repeat.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-repeat"><polyline points="17 1 21 5 17 9"></polyline><path d="M3 11V9a4 4 0 0 1 4-4h14"></path><polyline points="7 23 3 19 7 15"></polyline><path d="M21 13v2a4 4 0 0 1-4 4H3"></path></svg>

After

Width:  |  Height:  |  Size: 385 B

1
core/static/rewind.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-rewind"><polygon points="11 19 2 12 11 5 11 19"></polygon><polygon points="22 19 13 12 22 5 22 19"></polygon></svg>

After

Width:  |  Height:  |  Size: 319 B

1
core/static/tag.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-tag"><path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"></path><line x1="7" y1="7" x2="7" y2="7"></line></svg>

After

Width:  |  Height:  |  Size: 345 B

View File

@@ -0,0 +1,43 @@
{% load bootstrap4 staticfiles %}
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
{% bootstrap_css %}
{% bootstrap_javascript jquery=True %}
</head>
<body>
<!-- Navbar //-->
<nav class="navbar sticky-top navbar-expand-lg navbar-dark bg-dark justify-content-between">
<a class="navbar-brand mb-0 col-2" href="{% url "index" %}">
<img src="{% static "activity.svg" %}" height="30" width="30"/>
Cresus
</a>
<span class="navbar-text col-8 text-center">{% block page_title %}Titre{% endblock %}</span>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarMenu" aria-controls="navbarMenu" aria-expanded="false" aria-label="Toggle Navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse col-2" id="navbarMenu">
<div class="btn-group btn-group-sm mx-2" role="group" aria-label="Etiquettes">
<a class="btn btn-dark" href="{% url "etiquette-add" %}">
<img src="{% static "plus.svg" %}" height="18" width="18" />
</a>
<a href="{% url "etiquette-list" %}" class="btn btn-dark px-2">
<img src="{% static "tag.svg" %}" />
</a>
</div>
<div class="btn-group btn-group-sm mx-2" role="group" aria-label="Enregistrements récurrents">
<a class="btn btn-dark" href="{% url "recursif-add" %}"><img src="{% static "plus.svg" %}" height="18" width="18" /></a>
<a class="btn btn-dark" href="{% url "recursif-list" %}"><img src="{% static "repeat.svg" %}" /></a>
</div> </div>
</nav>
<div class="container">
<!-- Content //-->
{% block body_content %}
Page vide.
{% endblock %}
</div>
</body>
</html>

View File

@@ -0,0 +1,12 @@
{% extends "core/base.html" %}
{% block page_title %}
Suppression de {{ object}}
{% endblock %}
{% block body_content %}
<form action="" method="post" class="mt-3">{% csrf_token %}
<p>Êtes-vous sûr de vouloir supprimer définitivement {{ object }} ?</p>
<input type="submit" value="Confirm" class="btn btn-block btn-dark" />
</form>
{% endblock %}

View File

@@ -0,0 +1,37 @@
{% extends "core/base.html" %}
{% load bootstrap4 staticfiles %}
{% block page_title %}
Ajouter une écriture
{% endblock %}
{% block body_content %}
<form action="" method="POST" class="mt-3">
{% csrf_token %}
<div class="form-row">
<div class="form-group col-4">
<label class="control-label" for="id_montant">Montant</label>
<div class="input-group">
<span class="input-group-addon">
<img src="{% static "minus-square.svg"%}" />{{ form.est_negatif}}
</span>
<input class="form-control" type="number" id="id_montant" name="montant" required />
</div>
</div>
{% bootstrap_field form.description form_group_class="form-group col-8" %}
</div>
<div class="form-row">
<div class="form-group col-4">
<label class="control-label" for="id_date">Date</label>
<div class="form-inline align-items-center">{{ form.date }}</div>
</div>
<div class="form-group col-8">
<label class="control-label" for="id_etiquette">Étiquette</label>
{{ form.etiquette }}
</div>
</div>
<div class="form-row mt-3">
<button type="submit" class="btn btn-block btn-dark"><img src="{% static "plus.svg" %}" /> Ajouter</button>
</div>
</form>
{% endblock %}

View File

@@ -0,0 +1,14 @@
{% extends "core/base.html" %}
{% load bootstrap4 %}
{% block page_title %}
Mise à jour de {{ object }}
{% endblock %}
{% block body_content %}
<form action="" method="post" class="mt-2">
{% csrf_token %}
{% bootstrap_form form %}
<button type="submit" class="btn btn-block btn-dark">Enregistrer</button>
</form>
{% endblock %}

View File

@@ -0,0 +1,14 @@
{% extends "core/base.html" %}
{% load bootstrap4 %}
{% block page_title %}
Ajouter un enregistrement récursif
{% endblock %}
{% block body_content %}
<form action="" method="post" class="mt-2">
{% csrf_token %}
{% bootstrap_form form %}
<button type="submit" class="btn btn-block btn-dark">Enregistrer</button>
</form>
{% endblock %}

View File

@@ -0,0 +1,14 @@
{% extends "core/base.html" %}
{% load bootstrap4 %}
{% block page_title %}
Mise à jour de {{ object }}
{% endblock %}
{% block body_content %}
<form action="" method="post" class="mt-2">
{% csrf_token %}
{% bootstrap_form form %}
<button type="submit" class="btn btn-block btn-dark">Enregistrer</button>
</form>
{% endblock %}

View File

@@ -0,0 +1,14 @@
{% extends "core/base.html" %}
{% load bootstrap4 %}
{% block page_title %}
Ajouter une étiquette
{% endblock %}
{% block body_content %}
<form action="" method="post" class="mt-2">
{% csrf_token %}
{% bootstrap_form form %}
<button type="submit" class="btn btn-block btn-dark">Enregistrer</button>
</form>
{% endblock %}

View File

@@ -0,0 +1,23 @@
{% extends "core/base.html" %}
{% load staticfiles %}
{% block page_title%}
Liste des étiquettes
{% endblock %}
{% block body_content %}
<table class="table table-striped">
<thead class="thead-inverse">
<tr><th>Nom</th><th>Actions</th></tr>
<thead>
{% for etiquette in object_list %}
<tr>
<td>{{ etiquette }}</td>
<td>
<a href="{% url "etiquette-update" pk=etiquette.pk %}"><img src="{% static "edit.svg" %}" /><span class="sr-only">Éditer</span></a>
<a href="{% url "etiquette-delete" pk=etiquette.pk %}"><img src="{% static "delete.svg" %}" /><span class="sr-only">Supprimer</span></a>
</td>
</tr>
{% endfor %}
{% endblock %}

View File

@@ -0,0 +1,14 @@
{% extends "core/base.html" %}
{% load bootstrap4 %}
{% block page_title %}
Mise à jour de "{{ object }}"
{% endblock %}
{% block body_content %}
<form action="" method="post" class="mt-2">
{% csrf_token %}
{% bootstrap_form form %}
<button type="submit" class="btn btn-block btn-dark">Enregistrer</button>
</form>
{% endblock %}

View File

@@ -0,0 +1,62 @@
{% extends "core/base.html" %}
{% load bootstrap4 staticfiles %}
{% block page_title %}
<a href="{{ prev_month_url }}" class="btn btn-dark btn-sm"><img src="{% static "chevrons-left.svg" %}" /></a>
Comptes de {{mois}} {{annee}}
<a href="{% url "add"%}?year={{year}}&month={{month}}" class="btn btn-dark btn-sm m-0"><img src="{% static "plus.svg" %}" /></a>
{% if next_month_url %}
<a href="{{ next_month_url}}" class="btn btn-dark btn-sm"><img src="{% static "chevrons-right.svg" %}" /></a>
{% endif %}
{% endblock %}
{% block body_content %}
<div class="row">
{% bootstrap_messages %}
<div class="col-md-9">
{% if not object_list %}
<p class="alert alert-warning m-3">Aucune donnée</p>
{% else %}
<table class="table table-striped table-hover">
<thead class="thead-inverse">
<tr>
<th width="25%">Date</th>
<th width="40%">Description</th>
<th width="20%">Etiquette</th>
<th width="10%">Montant</th>
<th width="5%">Actions</th></tr>
</thead>
{% for d in object_list %}
<tr>
<td>{{ d.date }}</td>
<td>{{ d.description }}</td>
<td>{{ d.etiquette }}</td>
<td class="text-{% if d.montant > 0 %}success{% else %}danger{%endif%}">
<strong>{{ d.montant }}</strong>
</td>
<td>
<a href="{% url "update" pk=d.pk %}"><img src="{% static "edit.svg" %}" /></a>
<a href="{% url "delete" pk=d.pk %}"><img src="{% static "delete.svg" %}" /></a>
</td>
</tr>
{% endfor %}
</table>
{% endif %}
</div>
<div class="col-md-3 mt-3">
<h2 class="page-title">Balance
<span class="badge float-right badge-{% if data.balance > 0 %}success{% else %}danger{% endif%}">{{ data.balance }} €</span>
</h2>
<div class="card mt-3" >
<div class="card-header">
Dépenses
</div>
<ul class="list-group list-group-flush">
{% for label, montant in data.par_etiquette.items %}
<li class="list-group-item">{{ label }}
<span class="badge badge-dark p-2 align-middle float-right">{{ montant }} €</span>
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,28 @@
{% extends "core/base.html" %}
{% load staticfiles %}
{% block page_title %}
Enregistrement récursif
{% endblock %}
{% block body_content %}
<table class="table">
<thead class="thead-inverse">
<tr><th>Jour</th>
<th>Description</th>
<th>Étiquette</th>
<th>Montant</th>
<th>Actions</th>
</tr>
</thead>
{% for enr_rec in object_list %}
<tr><td>{{ enr_rec.jour }}</td>
<td>{{ enr_rec.description }}</td>
<td>{{ enr_rec.etiquette }}</td>
<td>{{ enr_rec.montant }}</td>
<td><a href="{% url "recursif-update" pk=enr_rec.pk %}"><img src="{% static "edit.svg" %}" /><span class="sr-only">Éditer</span></a>
<a href="{% url "recursif-delete" pk=enr_rec.pk %}"><img src="{% static "delete.svg" %}" /><span class="sr-only">Supprimer</span></a></td>
</tr>
{% endfor %}
</table>
{% endblock %}

3
core/tests.py Normal file
View File

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

18
core/urls.py Normal file
View File

@@ -0,0 +1,18 @@
from django.conf.urls import url
from .views import *
urlpatterns = [
url(r'^$', redirect_to_current_month, name="index"),
url(r'^(?P<year>[0-9]{4})/(?P<month>[0-9]{2})/$', MonthView.as_view(), name="month-view"),
url(r'^add$', EnregistrementAddView.as_view(), name="add"),
url(r'^update/(?P<pk>[0-9]+)$', EnregistrementUpdateView.as_view(), name="update"),
url(r'^delete/(?P<pk>[0-9]+)$', EnregistrementDeleteView.as_view(), name="delete"),
url(r'^etiquettes$', EtiquetteListView.as_view(), name="etiquette-list"),
url(r'^etiquettes/add$', EtiquetteAddView.as_view(), name="etiquette-add"),
url(r'^etiquettes/update/(?P<pk>\w+)$', EtiquetteUpdateView.as_view(), name="etiquette-update"),
url(r'^etiquettes/delete/(?P<pk>\w+)$', EtiquetteDeleteView.as_view(), name="etiquette-delete"),
url(r'^recursif$', RecursifListView.as_view(), name="recursif-list"),
url(r'^recursif/add$', RecursifAddView.as_view(), name="recursif-add"),
url(r'^recursif/update/(?P<pk>[0-9]+)$', RecursifUpdateView.as_view(), name="recursif-update"),
url(r'^recursif/delete/(?P<pk>[0-9]+)$', RecursifDeleteView.as_view(), name="recursif-delete"),
]

182
core/views.py Normal file
View File

@@ -0,0 +1,182 @@
from collections import defaultdict
from django.shortcuts import render, redirect
from django.urls import reverse, reverse_lazy
from django.http import HttpResponseRedirect
from django.utils import timezone as tz
from django.utils.dates import MONTHS
from django import views
from django.contrib import messages
from .models import (Enregistrement, Etiquette, EnregistrementRecursif,)
from .forms import EnregistrementForm
# Utils
def get_current_date():
return tz.localtime(tz.now()).date()
def redirect_to_current_month(request):
current_date = get_current_date()
return HttpResponseRedirect(reverse("month-view",
kwargs={
'year': current_date.year,
'month': "%02d" % current_date.month,
}))
# Views
class MonthView(views.generic.ListView):
template_name = "core/index.html"
def get(self, request, year=None, month=None, *args, **kwargs):
self.current_date = get_current_date()
self.year = int(year)
self.month = int(month)
if self.is_in_future:
messages.warning(request, "Trying to go into the future ? Well, you've been teleported back to now...")
return redirect_to_current_month(request)
return super().get(request, *args, **kwargs)
def get_queryset(self):
""" Use custom manager method to get associated data and store it """
qs, data = Enregistrement.objects.get_month_with_data(self.month,
self.year)
self.data = data
return qs
def get_prev_month(self):
""" Get url for previous month (no limit) """
if self.month > 1:
return reverse("month-view",
kwargs={'year': self.year,
'month': "%02d" % (self.month - 1)})
else:
return reverse("month-view",
kwargs={'year': self.year - 1,
'month': 12})
@property
def is_in_future(self):
return (self.year > self.current_date.year
or (self.year == self.current_date.year
and self.month > self.current_date.month))
def get_next_month(self):
""" Get url for next month, but no going into the future ! """
if self.is_in_future or (self.year == self.current_date.year
and self.month == self.current_date.month):
return None
if self.month < 12:
return reverse("month-view",
kwargs={'year': self.year,
'month': "%02d" % (self.month + 1)})
else:
return reverse("month-view",
kwargs={'year': self.year + 1,
'month': "01"})
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['mois'], context['month'] = MONTHS[self.month], self.month
context['annee'], context['year'] = self.year, self.year
context['data'] = self.data
context['prev_month_url'] = self.get_prev_month()
context['next_month_url'] = self.get_next_month()
return context
class EnregistrementAddView(views.generic.edit.CreateView):
model = Enregistrement
template_name_suffix = "_add"
form_class = EnregistrementForm
def get_initial(self):
# Build the initial date
date = get_current_date()
try: date = date.replace(int(self.request.GET['year']))
except KeyError: pass
try: date = date.replace(date.year, int(self.request.GET['month']))
except KeyError: pass
# Initials
return {'date': date,
'est_negatif': True,
}
def get_success_url(self):
date = self.object.date
return reverse("month-view",
kwargs={'year': date.year,
'month': "%02d" % date.month})
class EnregistrementUpdateView(views.generic.edit.UpdateView):
model = Enregistrement
template_name_suffix = "_update_form"
fields = "__all__"
def get_success_url(self):
return reverse("month-view",
kwargs={'year': self.object.date.year,
'month': "%02d" % self.object.date.month})
class EnregistrementDeleteView(views.generic.edit.DeleteView):
model = Enregistrement
template_name = "core/confirm_delete.html"
def get_success_url(self):
return reverse("month-view",
kwargs={'year': self.object.date.year,
'month': "%02d" % self.object.date.month})
class EtiquetteListView(views.generic.ListView):
model = Etiquette
template_name = "core/etiquettes.html"
class EtiquetteAddView(views.generic.edit.CreateView):
model = Etiquette
fields = "__all__"
template_name_suffix = "_add_form"
success_url = reverse_lazy('etiquette-list')
class EtiquetteUpdateView(views.generic.edit.UpdateView):
model = Etiquette
fields = "__all__"
template_name_suffix = "_update_form"
success_url = reverse_lazy('etiquette-list')
class EtiquetteDeleteView(views.generic.edit.DeleteView):
model = Etiquette
template_name = "core/confirm_delete.html"
success_url = reverse_lazy("etiquette-list")
class RecursifListView(views.generic.ListView):
model = EnregistrementRecursif
template_name = "core/recursif_list.html"
class RecursifAddView(views.generic.edit.CreateView):
model = EnregistrementRecursif
fields = ('jour', 'description', 'etiquette', 'montant')
template_name_suffix = "_add_form"
success_url = reverse_lazy('recursif-list')
class RecursifUpdateView(views.generic.edit.UpdateView):
model = EnregistrementRecursif
fields = ('jour', 'description', 'etiquette', 'montant')
template_name_suffix = "_update_form"
success_url = reverse_lazy('recursif-list')
class RecursifDeleteView(views.generic.edit.DeleteView):
model = EnregistrementRecursif
template_name = "core/confirm_delete.html"
success_url = reverse_lazy("recursif-list")