A piggy bank, a calculator, and several coins coming out from inside the Python logo.
Agence web » Actualités du digital » Créons un outil simple de suivi des dépenses

Créons un outil simple de suivi des dépenses

Si vous avez déjà essayé d'établir un budget, vous savez à quel point il peut être difficile de trouver un outil de suivi des dépenses qui fasse ce que vous voulez qu'il fasse. Mais pourquoi ne pas en créer un vous-même ? Apprenons les bases de Python en créant un outil de suivi des dépenses simple que vous pouvez réellement utiliser.

Définition des exigences de notre application de suivi des dépenses

Lorsque j'ai eu cette idée, je voulais créer une application qui soit plus qu'une simple application de terminal en ligne de commande. Nous en avons créé quelques-unes (comme la simple liste de tâches à l'aide de Python). Pour celle-ci, je voulais utiliser une interface graphique, j'ai donc décidé d'importer la bibliothèque Tkinter pour avoir des éléments d'interface utilisateur utilisables.

Les bibliothèques nous permettent de réutiliser du code. Il existe généralement une bibliothèque pour la plupart des choses que vous souhaitez faire en Python. Les importer évite de réécrire tout le code qu'elles contiennent à partir de zéro.

Dans cette application, nous allons implémenter :

  • Saisie des dépenses
  • Définir un budget
  • Comparer nos dépenses à notre budget
  • Afficher l'historique des dépenses

À la fin de cela, nous aurons une bonne idée du fonctionnement de Python et serons en mesure de terminer notre première application Python basée sur une interface graphique.

Mise en place de notre projet

Vous devez vérifier que Python est installé sur votre appareil en vérifiant la version de Python. J'ai déjà expliqué comment lier mon IDE préféré (Visual Studio) à Python. Une fois que vous avez terminé l'installation de Python sur votre appareil et sa mise à jour vers la version actuelle, nous pouvons commencer par créer un nouveau projet.

J'ai choisi Python pour ce projet car c'est l'un des langages les plus simples pour les débutants. Plongeons-nous dans la création de notre outil de suivi des dépenses !

Création de la fenêtre principale de l'application

Comme nous allons nous éloigner du terminal dans cette application, nous devrons plutôt configurer Tkinter pour créer l'interface utilisateur graphique (GUI) de notre application. La conception de l'interface utilisateur en Python peut être complexe, je vais donc rester simple et vous dire simplement ce que fait le code que je vous donne. Voici comment ma fenêtre d'interface utilisateur va être définie. Nous commencerons par importer Tkinter :

import tkinter as tk
from tkinter import ttk, messagebox, simpledialog

Tkinter inclut toutes les fonctions dont nous avons besoin pour créer une interface utilisateur de base. Vous remarquerez que nous importons également Ttk, un moteur de thème dans Tkinter. Cela nous permet de contrôler l'apparence de notre interface utilisateur si nous le souhaitons. Nous allons également utiliser messagebox et simpledialog pour demander à l'utilisateur de définir le budget initial. Nous voulons également pouvoir enregistrer les données mensuelles au cas où nous voudrions y accéder à l'avenir, nous allons donc également ajouter en haut de notre fichier :

import json
from datetime import datetime

En plus d'être très proche de mon propre nom, JSON est en fait assez intéressant comme système de sérialisation. Heureusement, il est fourni en standard avec Python, nous pouvons donc l'importer directement. L'importation de datetime nous aide à conserver nos dates correctement formatées dans toute l'application.

Maintenant que nous avons compris les importations, construisons réellement la fenêtre :

def create_widgets(self):
    
    main_frame = ttk.Frame(self.root, padding="10")
    main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))

    
    input_frame = ttk.LabelFrame(main_frame, text="Add Expense", padding="10")
    input_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=5, pady=5)

    ttk.Label(input_frame, text="Date (YYYY-MM-DD):").grid(row=0, column=0, sticky=tk.W)
    self.date_entry = ttk.Entry(input_frame)
    self.date_entry.grid(row=0, column=1, padx=5, pady=2)

    ttk.Label(input_frame, text="Category:").grid(row=1, column=0, sticky=tk.W)
    self.category_combobox = ttk.Combobox(input_frame, values=self.categories)
    self.category_combobox.grid(row=1, column=1, padx=5, pady=2)

    ttk.Label(input_frame, text="Amount:").grid(row=2, column=0, sticky=tk.W)
    self.amount_entry = ttk.Entry(input_frame)
    self.amount_entry.grid(row=2, column=1, padx=5, pady=2)

    ttk.Button(input_frame, text="Add Expense", command=self.add_expense).grid(row=3, column=0, columnspan=2, pady=5)

    
    category_frame = ttk.LabelFrame(main_frame, text="Manage Categories", padding="10")
    category_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=5, pady=5)

    self.new_category_entry = ttk.Entry(category_frame)
    self.new_category_entry.grid(row=0, column=0, padx=5, pady=2)
    ttk.Button(category_frame, text="Add Category", command=self.add_category).grid(row=0, column=1, padx=5, pady=2)

Ces entrées ne servent qu'à styliser notre fenêtre. Si vous le souhaitez, vous pouvez jouer avec les nombres et voir ce que vous obtenez, car jouer avec eux est inoffensif (à part rendre la fenêtre finale horrible). Nous devons également afficher le cadre et le bouton d'enregistrement, et configurer la grille pour qu'elle soit belle :

 
 display_frame = ttk.LabelFrame(main_frame, text="Expenses and Budget", padding="10")
 display_frame.grid(row=0, column=1, rowspan=2, sticky=(tk.W, tk.E, tk.N, tk.S), padx=5, pady=5)

 self.expense_tree = ttk.Treeview(display_frame, columns=('Date', 'Category', 'Amount'), show='headings')
 self.expense_tree.heading('Date', text='Date')
 self.expense_tree.heading('Category', text='Category')
 self.expense_tree.heading('Amount', text='Amount')
 self.expense_tree.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))

 scrollbar = ttk.Scrollbar(display_frame, orient=tk.VERTICAL, command=self.expense_tree.yview)
 scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
 self.expense_tree.configure(yscrollcommand=scrollbar.set)

 self.budget_label = ttk.Label(display_frame, text=f"Monthly Budget: ${self.monthly_budget:.2f}")
 self.budget_label.grid(row=1, column=0, sticky=tk.W, pady=2)

 self.total_expenses_label = ttk.Label(display_frame, text="Total Expenses: $0.00")
 self.total_expenses_label.grid(row=2, column=0, sticky=tk.W, pady=2)

 self.remaining_budget_label = ttk.Label(display_frame, text=f"Remaining Budget: ${self.monthly_budget:.2f}")
 self.remaining_budget_label.grid(row=3, column=0, sticky=tk.W, pady=2)

 
 ttk.Button(display_frame, text="Save Data", command=self.save_data).grid(row=4, column=0, pady=10)

 
 self.root.columnconfigure(0, weight=1)
 self.root.rowconfigure(0, weight=1)
 main_frame.columnconfigure(1, weight=1)
 main_frame.rowconfigure(0, weight=1)
 display_frame.columnconfigure(0, weight=1)
 display_frame.rowconfigure(0, weight=1)

Comme vous pouvez le voir, l'interface utilisateur prend un parcelle de codage pour réussir. Cependant, puisque vous envisagez d'apprendre les bases de Python, la compréhension de la conception de l'interface utilisateur fait partie de l'ensemble. Maintenant que l'interface utilisateur est opérationnelle, passons aux fonctionnalités.

Définir des catégories et ajouter des dépenses

Pour donner un peu de « corps » à notre application, j'ai prédéfini quelques catégories de dépenses. Je les ai incluses comme en-têtes avec le reste de nos définitions :

class ExpenseTrackerApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Expense Tracker & Budget Planner")
        self.root.geometry("1100x500")

        self.expenses = ()
        self.categories = ("Rent", "Food", "Entertainment", "Car", "Credit Cards")
        
        self.monthly_budget = self.get_initial_budget()
        
        self.create_widgets()

    def get_initial_budget(self):
        while True:
            budget = simpledialog.askfloat("Monthly Budget", "Enter your monthly budget:", minvalue=0.01)
            if budget is not None:
                return budget
            else:
                if messagebox.askyesno("No Budget", "You haven't entered a budget. Do you want to exit?"):
                    self.root.quit()
                    return 0

Des définitions comme celles-ci devraient être placées en haut de notre programme. À cette fin, cet extrait doit être placé avant le code de création de widget que nous avons déjà développé.

La fonction get_initial_budget(self) crée une fenêtre contextuelle au début de l'exécution de l'application pour collecter des données déterminant le montant d'argent dont vous disposez. Si vous ne saisissez pas de valeur, l'application s'arrête immédiatement.

Nous avons également besoin d'une fonctionnalité permettant d'ajouter des dépenses et de mettre à jour des catégories avec de nouvelles catégories personnalisées. Heureusement, cela est également simple à mettre en œuvre :

def add_expense(self):
    try:
        date = self.date_entry.get()
        category = self.category_combobox.get()
        amount = float(self.amount_entry.get())

        self.expenses.append({
            'date': date,
            'category': category,
            'amount': amount
        })

        self.update_expense_list()
        self.update_budget_info()
        self.clear_input_fields()
    except ValueError:
        messagebox.showerror("Error", "Invalid input. Please check your entries.")

def add_category(self):
    new_category = self.new_category_entry.get().strip()
    if new_category and new_category not in self.categories:
        self.categories.append(new_category)
        self.category_combobox('values') = self.categories
        self.new_category_entry.delete(0, tk.END)
        messagebox.showinfo("Category Added", f"'{new_category}' has been added to the categories.")
    else:
        messagebox.showerror("Error", "Invalid category name or category already exists.")

Pour compléter notre fonctionnalité de base, nous aurons besoin de fonctions pour mettre à jour la liste des dépenses lorsque nous en saisissons une nouvelle. Nous aurons besoin d'une fonction d'aide pour effacer la liste déroulante après avoir saisi les données. Et enfin, nous devrons calculer la part de notre budget dont nous disposons encore. Ce simple extrait de code nous permettra de faire exactement cela :

def update_expense_list(self):
    for item in self.expense_tree.get_children():
        self.expense_tree.delete(item)
    for expense in self.expenses:
        self.expense_tree.insert('', 'end', values=(
            expense('date'),
            expense('category'),
            f"${expense('amount'):.2f}"
        ))

def update_budget_info(self):
    total_expenses = sum(expense('amount') for expense in self.expenses)
    remaining_budget = self.monthly_budget - total_expenses

    self.total_expenses_label.config(text=f"Total Expenses: ${total_expenses:.2f}")
    self.remaining_budget_label.config(text=f"Remaining Budget: ${remaining_budget:.2f}")

def clear_input_fields(self):
    self.date_entry.delete(0, tk.END)
    self.category_combobox.set('')
    self.amount_entry.delete(0, tk.END)

Alors, après avoir saisi toutes ces données, comment s'assurer de ne pas les perdre ? J'ai implémenté une fonction de sauvegarde simple en utilisant le format JSON que nous avons importé précédemment :

def save_data(self):
    data = {
        'budget': self.monthly_budget,
        'categories': self.categories,
        'expenses': self.expenses
    }
    
    
    current_date = datetime.now().strftime("%Y-%m")
    default_filename = f"expense_data_{current_date}.json"
    
    file_path = filedialog.asksaveasfilename(
        defaultextension=".json",
        filetypes=(("JSON files", "*.json")),
        initialfile=default_filename
    )
    
    if file_path:
        with open(file_path, 'w') as f:
            json.dump(data, f, indent=2)
        messagebox.showinfo("Save Successful", f"Data saved to {file_path}")

Cette fonction sérialise les données et les enregistre dans un emplacement de fichier particulier avec la date du jour ajoutée au nom du fichier. La dernière chose que nous devons faire est de mettre en place la boucle de fonction main() :

if __name__ == "__main__":
    root = tk.Tk()
    app = ExpenseTrackerApp(root)
    root.mainloop()

Cela devrait permettre à notre outil de suivi des dépenses de fonctionner sans aucun problème. Félicitations pour avoir terminé votre première application basée sur une interface graphique en Python !

Ajouts, lacunes et personnalisations

Après avoir terminé cela, vous comprendrez un peu comment fonctionne Tkinter et comment vous pouvez l'utiliser pour la création d'interface utilisateur graphique. Si vous connaissez un peu le CSS ou la conception graphique, vous devriez être capable de comprendre comment utiliser Tkinter rapidement. Si vous souhaitez relever quelques défis, vous pouvez essayer ces choses ensuite :

  • Créez ou utilisez un nouveau thème pour votre application.
  • Créez une fonction « charger » et un bouton pour celle-ci. J'ai spécifiquement laissé de côté la fonction de chargement pour vous donner plus de place pour explorer.
  • Découvrez comment vous pouvez réaliser une répartition graphique de votre budget sous forme de graphique à secteurs.

L'application finale devrait actuellement ressembler à ceci :

Où aller à partir d'ici ?

Ce tutoriel de développement ne vous a donné qu'un aperçu général de Tkinter, mais si vous envisagez de créer davantage d'applications basées sur une interface graphique, vous devrez mieux comprendre ce qu'il peut faire. RealPython peut vous donner une idée de la façon d'utiliser Tkinter ou l'un des autres frameworks ou boîtes à outils d'interface graphique disponibles. Python est assez facile à comprendre au niveau de base, mais il y a tellement de choses que vous pouvez explorer en apprenant les bases de Python. La seule chose qui limite ce que vous pouvez créer est votre imagination.

Si vous êtes bloqué sur le code et que vous souhaitez que mon script Python complet soit comparé au vôtre, il est disponible sur mon GitHub.

★★★★★