Adrien Carteron 3 yıl önce
işleme
74bcc5db05
65 değiştirilmiş dosya ile 869 ekleme ve 0 silme
  1. 2 0
      .gitignore
  2. 0 0
      README.md
  3. 0 0
      accounting/__init__.py
  4. BIN
      accounting/__pycache__/__init__.cpython-38.pyc
  5. BIN
      accounting/__pycache__/settings.cpython-38.pyc
  6. BIN
      accounting/__pycache__/urls.cpython-38.pyc
  7. BIN
      accounting/__pycache__/wsgi.cpython-38.pyc
  8. 16 0
      accounting/asgi.py
  9. 131 0
      accounting/settings.py
  10. 27 0
      accounting/urls.py
  11. 16 0
      accounting/wsgi.py
  12. BIN
      db.sqlite3
  13. 0 0
      expenses/__init__.py
  14. BIN
      expenses/__pycache__/__init__.cpython-38.pyc
  15. BIN
      expenses/__pycache__/admin.cpython-38.pyc
  16. BIN
      expenses/__pycache__/apps.cpython-38.pyc
  17. BIN
      expenses/__pycache__/forms.cpython-38.pyc
  18. BIN
      expenses/__pycache__/models.cpython-38.pyc
  19. BIN
      expenses/__pycache__/urls.cpython-38.pyc
  20. BIN
      expenses/__pycache__/views.cpython-38.pyc
  21. 67 0
      expenses/admin.py
  22. 6 0
      expenses/apps.py
  23. 14 0
      expenses/forms.py
  24. 214 0
      expenses/migrations/0001_initial.py
  25. 0 0
      expenses/migrations/__init__.py
  26. BIN
      expenses/migrations/__pycache__/0001_initial.cpython-38.pyc
  27. BIN
      expenses/migrations/__pycache__/__init__.cpython-38.pyc
  28. 87 0
      expenses/models.py
  29. 64 0
      expenses/templates/base.html
  30. 16 0
      expenses/templates/categories_list.html
  31. 3 0
      expenses/tests.py
  32. 9 0
      expenses/urls.py
  33. 19 0
      expenses/views.py
  34. 0 0
      incomes/__init__.py
  35. BIN
      incomes/__pycache__/__init__.cpython-38.pyc
  36. BIN
      incomes/__pycache__/admin.cpython-38.pyc
  37. BIN
      incomes/__pycache__/apps.cpython-38.pyc
  38. BIN
      incomes/__pycache__/models.cpython-38.pyc
  39. 14 0
      incomes/admin.py
  40. 6 0
      incomes/apps.py
  41. 30 0
      incomes/migrations/0001_initial.py
  42. 0 0
      incomes/migrations/__init__.py
  43. BIN
      incomes/migrations/__pycache__/0001_initial.cpython-38.pyc
  44. BIN
      incomes/migrations/__pycache__/__init__.cpython-38.pyc
  45. 10 0
      incomes/models.py
  46. 3 0
      incomes/tests.py
  47. 3 0
      incomes/views.py
  48. 22 0
      manage.py
  49. 33 0
      requirements.txt
  50. 0 0
      spreadsheet/__init__.py
  51. BIN
      spreadsheet/__pycache__/__init__.cpython-38.pyc
  52. BIN
      spreadsheet/__pycache__/admin.cpython-38.pyc
  53. BIN
      spreadsheet/__pycache__/apps.cpython-38.pyc
  54. BIN
      spreadsheet/__pycache__/models.cpython-38.pyc
  55. BIN
      spreadsheet/__pycache__/urls.cpython-38.pyc
  56. BIN
      spreadsheet/__pycache__/views.cpython-38.pyc
  57. 3 0
      spreadsheet/admin.py
  58. 6 0
      spreadsheet/apps.py
  59. 0 0
      spreadsheet/forms.py
  60. 0 0
      spreadsheet/migrations/__init__.py
  61. BIN
      spreadsheet/migrations/__pycache__/__init__.cpython-38.pyc
  62. 3 0
      spreadsheet/models.py
  63. 3 0
      spreadsheet/tests.py
  64. 8 0
      spreadsheet/urls.py
  65. 34 0
      spreadsheet/views.py

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+*~
+venv

+ 0 - 0
README.md


+ 0 - 0
accounting/__init__.py


BIN
accounting/__pycache__/__init__.cpython-38.pyc


BIN
accounting/__pycache__/settings.cpython-38.pyc


BIN
accounting/__pycache__/urls.cpython-38.pyc


BIN
accounting/__pycache__/wsgi.cpython-38.pyc


+ 16 - 0
accounting/asgi.py

@@ -0,0 +1,16 @@
+"""
+ASGI config for accounting project.
+
+It exposes the ASGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/3.2/howto/deployment/asgi/
+"""
+
+import os
+
+from django.core.asgi import get_asgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'accounting.settings')
+
+application = get_asgi_application()

+ 131 - 0
accounting/settings.py

@@ -0,0 +1,131 @@
+"""
+Django settings for accounting project.
+
+Generated by 'django-admin startproject' using Django 3.2.16.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/3.2/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/3.2/ref/settings/
+"""
+
+from pathlib import Path
+
+# Build paths inside the project like this: BASE_DIR / 'subdir'.
+BASE_DIR = Path(__file__).resolve().parent.parent
+
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = "django-insecure--oc3!+qly1tf3d5xt8^by=q64&kts*(4#08g8$!v8-st#b#hy%"
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+ALLOWED_HOSTS = []
+
+
+# Application definition
+
+INSTALLED_APPS = [
+    "django.contrib.admin",
+    "django.contrib.auth",
+    "django.contrib.contenttypes",
+    "django.contrib.sessions",
+    "django.contrib.messages",
+    "django.contrib.staticfiles",
+    "hijack",
+    "django_extensions",
+    "django_bootstrap5",
+    "expenses",
+    "incomes",
+    "spreadsheet",
+]
+
+MIDDLEWARE = [
+    "django.middleware.security.SecurityMiddleware",
+    "django.contrib.sessions.middleware.SessionMiddleware",
+    "django.middleware.common.CommonMiddleware",
+    "django.middleware.csrf.CsrfViewMiddleware",
+    "django.contrib.auth.middleware.AuthenticationMiddleware",
+    "django.contrib.messages.middleware.MessageMiddleware",
+    "django.middleware.clickjacking.XFrameOptionsMiddleware",
+]
+
+ROOT_URLCONF = "accounting.urls"
+
+TEMPLATES = [
+    {
+        "BACKEND": "django.template.backends.django.DjangoTemplates",
+        "DIRS": [],
+        "APP_DIRS": True,
+        "OPTIONS": {
+            "context_processors": [
+                "django.template.context_processors.debug",
+                "django.template.context_processors.request",
+                "django.contrib.auth.context_processors.auth",
+                "django.contrib.messages.context_processors.messages",
+            ],
+        },
+    },
+]
+
+WSGI_APPLICATION = "accounting.wsgi.application"
+
+
+# Database
+# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
+
+DATABASES = {
+    "default": {
+        "ENGINE": "django.db.backends.sqlite3",
+        "NAME": BASE_DIR / "db.sqlite3",
+    }
+}
+
+
+# Password validation
+# https://docs.djangoproject.com/en/3.2/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+    {
+        "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
+    },
+    {
+        "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
+    },
+    {
+        "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
+    },
+    {
+        "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
+    },
+]
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/3.2/topics/i18n/
+
+LANGUAGE_CODE = "en-us"
+
+TIME_ZONE = "UTC"
+
+USE_I18N = True
+
+USE_L10N = True
+
+USE_TZ = True
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/3.2/howto/static-files/
+
+STATIC_URL = "/static/"
+
+# Default primary key field type
+# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
+
+DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

+ 27 - 0
accounting/urls.py

@@ -0,0 +1,27 @@
+"""accounting URL Configuration
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+    https://docs.djangoproject.com/en/3.2/topics/http/urls/
+Examples:
+Function views
+    1. Add an import:  from my_app import views
+    2. Add a URL to urlpatterns:  path('', views.home, name='home')
+Class-based views
+    1. Add an import:  from other_app.views import Home
+    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
+Including another URLconf
+    1. Import the include() function: from django.urls import include, path
+    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
+"""
+from django.contrib import admin
+from django.urls import path, include
+from expenses.views import homepage
+
+urlpatterns = [
+    path("admin/", admin.site.urls),
+    path("accounts/", include("django.contrib.auth.urls")),
+    path("expenses/", include("expenses.urls")),
+    path("spread/", include("spreadsheet.urls")),
+    path("hijack/", include("hijack.urls")),
+    path("", homepage, name="home"),
+]

+ 16 - 0
accounting/wsgi.py

@@ -0,0 +1,16 @@
+"""
+WSGI config for accounting project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/3.2/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'accounting.settings')
+
+application = get_wsgi_application()

BIN
db.sqlite3


+ 0 - 0
expenses/__init__.py


BIN
expenses/__pycache__/__init__.cpython-38.pyc


BIN
expenses/__pycache__/admin.cpython-38.pyc


BIN
expenses/__pycache__/apps.cpython-38.pyc


BIN
expenses/__pycache__/forms.cpython-38.pyc


BIN
expenses/__pycache__/models.cpython-38.pyc


BIN
expenses/__pycache__/urls.cpython-38.pyc


BIN
expenses/__pycache__/views.cpython-38.pyc


+ 67 - 0
expenses/admin.py

@@ -0,0 +1,67 @@
+from django.contrib import admin
+from expenses.models import (
+    OnlineSource,
+    PhysicalSource,
+    Category,
+    Expense,
+    MultiplePaymentExepense,
+)
+
+
+class OnlineSourceAdmin(admin.ModelAdmin):
+    list_display = ["name", "url", "created_at", "modified_at"]
+    readonly_fields = ["created_at", "modified_at"]
+    list_filter = ["name"]
+    search_fields = ["name", "url"]
+    date_hierarchy = "created_at"
+
+
+class PhysicalSourceAdmin(admin.ModelAdmin):
+    list_display = ["name", "latitude", "longitude", "created_at", "modified_at"]
+    readonly_fields = ["created_at", "modified_at"]
+    list_filter = ["name"]
+    search_fields = ["name"]
+    date_hierarchy = "created_at"
+
+
+class CategoryAdmin(admin.ModelAdmin):
+    list_display = ["name", "type", "created_at", "modified_at"]
+    readonly_fields = ["created_at", "modified_at"]
+    date_hierarchy = "created_at"
+
+
+class ExpenseAdmin(admin.ModelAdmin):
+    list_display = [
+        "name",
+        "date",
+        "amount",
+        "category",
+        "source",
+        "created_at",
+        "modified_at",
+    ]
+    readonly_fields = ["created_at", "modified_at"]
+    date_hierarchy = "date"
+
+
+class MultiplePaymentExepenseAdmin(admin.ModelAdmin):
+    list_display = [
+        "name",
+        "first_payment_date",
+        "amount",
+        "number_of_payment",
+        "payments",
+        "category",
+        "source",
+        "created_at",
+        "modified_at",
+    ]
+    readonly_fields = ["created_at", "modified_at"]
+    date_hierarchy = "first_payment_date"
+
+
+admin.site.register(OnlineSource, OnlineSourceAdmin)
+admin.site.register(PhysicalSource, PhysicalSourceAdmin)
+admin.site.register(Category, CategoryAdmin)
+admin.site.register(Expense, ExpenseAdmin)
+admin.site.register(MultiplePaymentExepense, MultiplePaymentExepenseAdmin)

+ 6 - 0
expenses/apps.py

@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class ExpensesConfig(AppConfig):
+    default_auto_field = "django.db.models.BigAutoField"
+    name = "expenses"

+ 14 - 0
expenses/forms.py

@@ -0,0 +1,14 @@
+from django import forms
+from django.contrib.auth.models import User
+from django.core.exceptions import ValidationError
+
+from expenses.models import Category
+
+
+class CategoryForm(forms.ModelForm):
+    class Meta:
+        model = Category
+        fields = "__all__"
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)

+ 214 - 0
expenses/migrations/0001_initial.py

@@ -0,0 +1,214 @@
+# Generated by Django 4.1.3 on 2022-11-03 14:53
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = []
+
+    operations = [
+        migrations.CreateModel(
+            name="Category",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("created_at", models.DateTimeField(auto_now_add=True, null=True)),
+                ("modified_at", models.DateTimeField(auto_now=True)),
+                ("name", models.CharField(max_length=150)),
+                (
+                    "type",
+                    models.CharField(
+                        choices=[
+                            ("Fixed", "Fixed expense"),
+                            ("Variable", "Variable expense"),
+                        ],
+                        default="Variable",
+                        max_length=20,
+                    ),
+                ),
+            ],
+            options={
+                "verbose_name": "Category",
+                "verbose_name_plural": "Categories",
+                "ordering": ("name", "type"),
+            },
+        ),
+        migrations.CreateModel(
+            name="RawExpense",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("name", models.CharField(max_length=150)),
+                ("date", models.DateField()),
+                ("amount", models.DecimalField(decimal_places=2, max_digits=10)),
+            ],
+        ),
+        migrations.CreateModel(
+            name="Source",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("created_at", models.DateTimeField(auto_now_add=True, null=True)),
+                ("modified_at", models.DateTimeField(auto_now=True)),
+                ("name", models.CharField(max_length=150)),
+            ],
+            options={
+                "abstract": False,
+            },
+        ),
+        migrations.CreateModel(
+            name="OnlineSource",
+            fields=[
+                (
+                    "source_ptr",
+                    models.OneToOneField(
+                        auto_created=True,
+                        on_delete=django.db.models.deletion.CASCADE,
+                        parent_link=True,
+                        primary_key=True,
+                        serialize=False,
+                        to="expenses.source",
+                    ),
+                ),
+                ("url", models.URLField()),
+            ],
+            options={
+                "abstract": False,
+            },
+            bases=("expenses.source",),
+        ),
+        migrations.CreateModel(
+            name="PhysicalSource",
+            fields=[
+                (
+                    "source_ptr",
+                    models.OneToOneField(
+                        auto_created=True,
+                        on_delete=django.db.models.deletion.CASCADE,
+                        parent_link=True,
+                        primary_key=True,
+                        serialize=False,
+                        to="expenses.source",
+                    ),
+                ),
+                ("latitude", models.FloatField()),
+                ("longitude", models.FloatField()),
+            ],
+            options={
+                "abstract": False,
+            },
+            bases=("expenses.source",),
+        ),
+        migrations.CreateModel(
+            name="MultiplePaymentExepense",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("created_at", models.DateTimeField(auto_now_add=True, null=True)),
+                ("modified_at", models.DateTimeField(auto_now=True)),
+                ("name", models.CharField(max_length=150)),
+                ("first_payment_date", models.DateField()),
+                ("amount", models.DecimalField(decimal_places=2, max_digits=10)),
+                ("number_of_payment", models.PositiveIntegerField()),
+                (
+                    "category",
+                    models.OneToOneField(
+                        on_delete=django.db.models.deletion.PROTECT,
+                        related_name="%(app_label)s_%(class)s_related",
+                        to="expenses.category",
+                    ),
+                ),
+                (
+                    "payments",
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.PROTECT,
+                        related_name="multiple_payment_expense",
+                        to="expenses.rawexpense",
+                    ),
+                ),
+                (
+                    "source",
+                    models.OneToOneField(
+                        on_delete=django.db.models.deletion.PROTECT,
+                        related_name="%(app_label)s_%(class)s_related",
+                        to="expenses.source",
+                    ),
+                ),
+            ],
+            options={
+                "abstract": False,
+            },
+        ),
+        migrations.CreateModel(
+            name="Expense",
+            fields=[
+                (
+                    "rawexpense_ptr",
+                    models.OneToOneField(
+                        auto_created=True,
+                        on_delete=django.db.models.deletion.CASCADE,
+                        parent_link=True,
+                        primary_key=True,
+                        serialize=False,
+                        to="expenses.rawexpense",
+                    ),
+                ),
+                ("created_at", models.DateTimeField(auto_now_add=True, null=True)),
+                ("modified_at", models.DateTimeField(auto_now=True)),
+                (
+                    "category",
+                    models.OneToOneField(
+                        on_delete=django.db.models.deletion.PROTECT,
+                        related_name="%(app_label)s_%(class)s_related",
+                        to="expenses.category",
+                    ),
+                ),
+                (
+                    "source",
+                    models.OneToOneField(
+                        on_delete=django.db.models.deletion.PROTECT,
+                        related_name="%(app_label)s_%(class)s_related",
+                        to="expenses.source",
+                    ),
+                ),
+            ],
+            options={
+                "verbose_name": "Expense",
+                "verbose_name_plural": "Expenses",
+                "ordering": ("name", "date", "amount"),
+            },
+            bases=("expenses.rawexpense", models.Model),
+        ),
+    ]

+ 0 - 0
expenses/migrations/__init__.py


BIN
expenses/migrations/__pycache__/0001_initial.cpython-38.pyc


BIN
expenses/migrations/__pycache__/__init__.cpython-38.pyc


+ 87 - 0
expenses/models.py

@@ -0,0 +1,87 @@
+from django.db import models
+
+
+class AuthoringDatesModel(models.Model):
+    created_at = models.DateTimeField(auto_now_add=True, null=True)
+    modified_at = models.DateTimeField(auto_now=True)
+
+    class Meta:
+        abstract = True
+
+
+class Source(AuthoringDatesModel):
+    name = models.CharField(max_length=150)
+
+
+class OnlineSource(Source):
+    url = models.URLField(max_length=200)
+
+
+class PhysicalSource(Source):
+    # TODO auto add coordinates by searching shop address
+    latitude = models.FloatField()
+    longitude = models.FloatField()
+
+
+class Category(AuthoringDatesModel):
+    name = models.CharField(max_length=150)
+    type = models.CharField(
+        max_length=20,
+        choices=[
+            ("Fixed", "Fixed expense"),
+            ("Variable", "Variable expense"),
+        ],
+        default="Variable",
+    )
+    # Surcharge de la manière d'afficher un objet sensor
+    def __str__(self):
+        return self.name
+
+    class Meta:
+        # db_table = 'book' # Permet de personnaliser le nom de la table en BDD
+        verbose_name = "Category"  # Le nom lisbile du modèle
+        verbose_name_plural = "Categories"  # Le nom au pluriel du modèle
+        ordering = ("name", "type")  # Le tri par défaut dans les listes
+
+
+class MetaExpense(AuthoringDatesModel):
+    category = models.OneToOneField(
+        Category,
+        related_name="%(app_label)s_%(class)s_related",
+        on_delete=models.PROTECT,
+    )
+    source = models.OneToOneField(
+        Source, related_name="%(app_label)s_%(class)s_related", on_delete=models.PROTECT
+    )
+
+    class Meta:
+        abstract = True
+
+
+class RawExpense(models.Model):
+    name = models.CharField(max_length=150)
+    date = models.DateField()
+    amount = models.DecimalField(max_digits=10, decimal_places=2)
+
+
+class Expense(RawExpense, MetaExpense):
+
+    # Surcharge de la manière d'afficher un objet sensor
+    def __str__(self):
+        return self.name
+
+    class Meta:
+        # db_table = 'book' # Permet de personnaliser le nom de la table en BDD
+        verbose_name = "Expense"  # Le nom lisbile du modèle
+        verbose_name_plural = "Expenses"  # Le nom au pluriel du modèle
+        ordering = ("name", "date", "amount")  # Le tri par défaut dans les listes
+
+
+class MultiplePaymentExepense(MetaExpense):
+    name = models.CharField(max_length=150)
+    first_payment_date = models.DateField()
+    amount = models.DecimalField(max_digits=10, decimal_places=2)
+    payments = models.ForeignKey(
+        RawExpense, related_name="multiple_payment_expense", on_delete=models.PROTECT
+    )
+    number_of_payment = models.PositiveIntegerField()

+ 64 - 0
expenses/templates/base.html

@@ -0,0 +1,64 @@
+{% load django_bootstrap5 %}
+<!doctype html>
+<html lang="en">
+
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <title>{% block title %}{% endblock %}</title>
+    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootswatch/5.2.1/solar/bootstrap.min.css"
+        integrity="sha512-LhpxG+XCtib/q8R0I0rlktQ4CBE+sRoz22WY/71cBjc2srSqNshLXE0fIZQDsAqQoC7/cHjpNAtRx78MiloTAw=="
+        crossorigin="anonymous" referrerpolicy="no-referrer" />
+    {% block extra_css %}{% endblock %}
+</head>
+
+<body>
+    <nav class="navbar navbar-expand-lg bg-light">
+        <div class="container">
+            <a class="navbar-brand" href="#">Velogest</a>
+            <button class="navbar-toggler" type="button" data-bs-toggle="collapse"
+                data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false"
+                aria-label="Toggle navigation">
+                <span class="navbar-toggler-icon"></span>
+            </button>
+            <div class="collapse navbar-collapse" id="navbarSupportedContent">
+                <ul class="navbar-nav me-auto mb-2 mb-lg-0">
+                    <li class="nav-item">
+                        <a class="nav-link active" aria-current="page" href="{% url 'home' %}">Accueil</a>
+                    </li>
+
+                </ul>
+                <ul class="navbar-nav me-auto mb-2 mb-lg-0">
+                    {% if request.user.is_anonymous %}
+                    <li class="nav-item">
+                        <a class="nav-link active" aria-current="page" href="{% url 'login' %}">Se connecter</a>
+                    </li>
+                    {% else %}
+                    <li class="nav-item">
+                        <span class="navbar-text">
+                            Bienvenue {{ request.user }}
+                        </span>
+                    </li>
+                    <li class="nav-item">
+                        <a class="nav-link active" aria-current="page" href="{% url 'logout' %}">Se déconnecter</a>
+                    </li>
+                    {% endif %}
+                </ul>
+            </div>
+        </div>
+    </nav>
+    <div class="container">
+        <h1>{% block page_title %}{% endblock %}</h1>
+
+        {% bootstrap_messages %}
+
+        {% block content %}{% endblock %}
+    </div>
+
+    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.2/dist/js/bootstrap.bundle.min.js"
+        integrity="sha384-OERcA2EqjJCMA+/3y+gxIOqMEjwtxJY7qPCqsdltbNJuaOe923+mo//f6V8Qbsw3"
+        crossorigin="anonymous"></script>
+    {% block extra_js %}{% endblock %}
+</body>
+
+</html>

+ 16 - 0
expenses/templates/categories_list.html

@@ -0,0 +1,16 @@
+{% extends "base.html" %}
+{% load django_bootstrap5 %}
+
+{% block page_title %}Categories{% endblock %}
+
+
+{% block content %}
+
+<ul>
+    {% for category in categories %}
+    <li>{{ category }}</a></li>
+    {% endfor %}
+</ul>
+
+
+{% endblock content %}

+ 3 - 0
expenses/tests.py

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

+ 9 - 0
expenses/urls.py

@@ -0,0 +1,9 @@
+from django.urls import path
+
+from expenses.views import category_list
+
+app_name = "expenses"
+
+urlpatterns = [
+    path("categories", category_list, name="list"),
+]

+ 19 - 0
expenses/views.py

@@ -0,0 +1,19 @@
+from django.shortcuts import render
+from expenses.models import Category
+from expenses.forms import CategoryForm
+
+
+def homepage(request):
+    return category_list(request)
+
+
+def category_list(request):
+    categories = Category.objects.all()
+    form = CategoryForm(request.GET or None)
+    if form.is_valid():
+        data = form.cleaned_data
+        # print(sensor.pk)
+        # return render(request, 'sensors/sensor_detail.html', {'sensor': sensor})
+    return render(
+        request, "categories_list.html", {"categories": categories, "form": form}
+    )

+ 0 - 0
incomes/__init__.py


BIN
incomes/__pycache__/__init__.cpython-38.pyc


BIN
incomes/__pycache__/admin.cpython-38.pyc


BIN
incomes/__pycache__/apps.cpython-38.pyc


BIN
incomes/__pycache__/models.cpython-38.pyc


+ 14 - 0
incomes/admin.py

@@ -0,0 +1,14 @@
+from django.contrib import admin
+from incomes.models import Income
+
+
+class IncomeAdmin(admin.ModelAdmin):
+    list_display = [
+        "source",
+        "date",
+        "amount",
+    ]
+    date_hierarchy = "date"
+
+
+admin.site.register(Income, IncomeAdmin)

+ 6 - 0
incomes/apps.py

@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class IncomesConfig(AppConfig):
+    default_auto_field = "django.db.models.BigAutoField"
+    name = "incomes"

+ 30 - 0
incomes/migrations/0001_initial.py

@@ -0,0 +1,30 @@
+# Generated by Django 4.1.3 on 2022-11-03 14:56
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = []
+
+    operations = [
+        migrations.CreateModel(
+            name="Income",
+            fields=[
+                (
+                    "id",
+                    models.BigAutoField(
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        verbose_name="ID",
+                    ),
+                ),
+                ("date", models.DateTimeField(null=True)),
+                ("source", models.CharField(max_length=150)),
+                ("amount", models.DecimalField(decimal_places=2, max_digits=10)),
+            ],
+        ),
+    ]

+ 0 - 0
incomes/migrations/__init__.py


BIN
incomes/migrations/__pycache__/0001_initial.cpython-38.pyc


BIN
incomes/migrations/__pycache__/__init__.cpython-38.pyc


+ 10 - 0
incomes/models.py

@@ -0,0 +1,10 @@
+from django.db import models
+
+# Create your models here.
+class Income(models.Model):
+    date = models.DateTimeField(null=True)
+    source = models.CharField(max_length=150, null=False)
+
+    # on forms to force only 2 dec
+    # {{ value|floatformat:2 }}  # outputs 34.23
+    amount = models.DecimalField(max_digits=10, decimal_places=2)

+ 3 - 0
incomes/tests.py

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

+ 3 - 0
incomes/views.py

@@ -0,0 +1,3 @@
+from django.shortcuts import render
+
+# Create your views here.

+ 22 - 0
manage.py

@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+"""Django's command-line utility for administrative tasks."""
+import os
+import sys
+
+
+def main():
+    """Run administrative tasks."""
+    os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'accounting.settings')
+    try:
+        from django.core.management import execute_from_command_line
+    except ImportError as exc:
+        raise ImportError(
+            "Couldn't import Django. Are you sure it's installed and "
+            "available on your PYTHONPATH environment variable? Did you "
+            "forget to activate a virtual environment?"
+        ) from exc
+    execute_from_command_line(sys.argv)
+
+
+if __name__ == '__main__':
+    main()

+ 33 - 0
requirements.txt

@@ -0,0 +1,33 @@
+asgiref==3.5.2
+asttokens==2.1.0
+backcall==0.2.0
+backports.zoneinfo==0.2.1
+black==22.10.0
+click==8.1.3
+decorator==5.1.1
+Django==4.1.3
+django-bootstrap5==22.1
+django-extensions==3.2.1
+django-hijack==3.2.1
+django-shell-plus==1.1.7
+executing==1.2.0
+ipython==8.6.0
+jedi==0.18.1
+matplotlib-inline==0.1.6
+mypy-extensions==0.4.3
+parso==0.8.3
+pathspec==0.10.1
+pexpect==4.8.0
+pickleshare==0.7.5
+platformdirs==2.5.2
+prompt-toolkit==3.0.31
+ptyprocess==0.7.0
+pure-eval==0.2.2
+Pygments==2.13.0
+six==1.16.0
+sqlparse==0.4.3
+stack-data==0.6.0
+tomli==2.0.1
+traitlets==5.5.0
+typing_extensions==4.4.0
+wcwidth==0.2.5

+ 0 - 0
spreadsheet/__init__.py


BIN
spreadsheet/__pycache__/__init__.cpython-38.pyc


BIN
spreadsheet/__pycache__/admin.cpython-38.pyc


BIN
spreadsheet/__pycache__/apps.cpython-38.pyc


BIN
spreadsheet/__pycache__/models.cpython-38.pyc


BIN
spreadsheet/__pycache__/urls.cpython-38.pyc


BIN
spreadsheet/__pycache__/views.cpython-38.pyc


+ 3 - 0
spreadsheet/admin.py

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

+ 6 - 0
spreadsheet/apps.py

@@ -0,0 +1,6 @@
+from django.apps import AppConfig
+
+
+class SpreadConfig(AppConfig):
+    default_auto_field = "django.db.models.BigAutoField"
+    name = "spreadsheet"

+ 0 - 0
spreadsheet/forms.py


+ 0 - 0
spreadsheet/migrations/__init__.py


BIN
spreadsheet/migrations/__pycache__/__init__.cpython-38.pyc


+ 3 - 0
spreadsheet/models.py

@@ -0,0 +1,3 @@
+from django.db import models
+
+# Create your models here.

+ 3 - 0
spreadsheet/tests.py

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

+ 8 - 0
spreadsheet/urls.py

@@ -0,0 +1,8 @@
+from django.urls import path
+from spreadsheet.views import spread_view
+
+app_name = "spreadsheet"
+
+urlpatterns = [
+    path("", spread_view, name="spread"),
+]

+ 34 - 0
spreadsheet/views.py

@@ -0,0 +1,34 @@
+from django.shortcuts import render
+from django.core.exceptions import BadRequest
+from django.http import HttpResponse, HttpResponseRedirect, JsonResponse
+
+from expenses.models import Expense, MultiplePaymentExepense
+
+
+def spread_view(request):
+    if not request.GET.get("month"):
+        return BadRequest()
+    else:
+        expenses = Expense.objects.all()
+        expenses = (
+            expenses.filter(date__month=request.GET.get("month"))
+            .values_list("date", "name")
+            .order_by("date")
+        )
+        multi_expenses = MultiplePaymentExepense.objects.all()
+        multi_expenses = (
+            multi_expenses.filter(
+                first_payment_date__month__lte=request.GET.get("month")
+            )
+            .values_list("first_payment_date", "name")
+            .order_by("first_payment_date")
+        )
+
+        d = {
+            "date": [obs[0] for obs in expenses],
+            "count": [obs[1] for obs in expenses],
+        }
+        """ for k, v in observations:
+            d['date'].append(k)
+            d['count'].append(v) """
+        return JsonResponse(d)