Nottier

Unlock Your Personalized Scent Journey

Sign in to access your AI fragrance assistant and get personalized recommendations crafted just for you.

Savannah Wild

Savannah Wild

by Oriflame

star rating4.0|Female

```python import tkinter as tk from tkinter import ttk, messagebox import json import os class CustomPeriodsManager: """ A Tkinter application to manage custom analysis periods, allowing users to add, edit, delete, and persist period definitions. """ DATA_FILE = "custom_periods.json" def __init__(self, master): self.master = master master.title("Custom Analysis Periods Manager") master.geometry("500x400") master.resizable(True, True) self.periods = self._load_periods() self.selected_period_index = None self._create_widgets() self._update_listbox() # Ensure data is saved on window close master.protocol("WM_DELETE_WINDOW", self.on_closing) def _load_periods(self): """Loads custom periods from a JSON file or initializes with defaults.""" if os.path.exists(self.DATA_FILE): try: with open(self.DATA_FILE, 'r') as f: data = json.load(f) # Basic validation for loaded data structure if isinstance(data, list) and all(isinstance(p, dict) and 'name' in p and 'duration' in p for p in data): return data except (json.JSONDecodeError, IOError): messagebox.showerror("Error", "Could not read custom periods file. Using default periods.") # Default periods if file is missing, corrupted, or invalid return [ {"name": "Week", "duration": 7}, {"name": "Fortnight", "duration": 14} ] def _save_periods(self): """Saves current custom periods to a JSON file.""" try: with open(self.DATA_FILE, 'w') as f: json.dump(self.periods, f, indent=4) except IOError: messagebox.showerror("Error", "Could not save custom periods to file.") def _create_widgets(self): """Initializes and places all GUI widgets.""" main_frame = ttk.Frame(self.master, padding="10") main_frame.pack(fill=tk.BOTH, expand=True) # Configure grid to be resizable main_frame.columnconfigure(0, weight=1) main_frame.columnconfigure(1, weight=1) main_frame.rowconfigure(0, weight=1) for i in range(1, 6): # For input fields and buttons main_frame.rowconfigure(i, weight=0) # --- Display Existing Periods --- list_frame = ttk.LabelFrame(main_frame, text="Defined Periods", padding="5") list_frame.grid(row=0, column=0, columnspan=2, sticky="nsew", pady=(0, 10)) list_frame.columnconfigure(0, weight=1) list_frame.rowconfigure(0, weight=1) self.period_listbox = tk.Listbox(list_frame, height=8, selectmode=tk.SINGLE, font=('Arial', 10)) self.period_listbox.grid(row=0, column=0, sticky="nsew") self.period_listbox.bind("<<ListboxSelect>>", self.select_period) scrollbar = ttk.Scrollbar(list_frame, orient="vertical", command=self.period_listbox.yview) scrollbar.grid(row=0, column=1, sticky="ns") self.period_listbox.config(yscrollcommand=scrollbar.set) # --- Input Fields for Add/Edit --- input_frame = ttk.LabelFrame(main_frame, text="Period Details", padding="5") input_frame.grid(row=1, column=0, columnspan=2, sticky="ew", pady=(0, 10)) input_frame.columnconfigure(1, weight=1) # Make entry fields expand ttk.Label(input_frame, text="Name:").grid(row=0, column=0, sticky="w", pady=2) self.name_entry = ttk.Entry(input_frame) self.name_entry.grid(row=0, column=1, sticky="ew", padx=5, pady=2) ttk.Label(input_frame, text="Duration (days):").grid(row=1, column=0, sticky="w", pady=2) self.duration_entry = ttk.Entry(input_frame) self.duration_entry.grid(row=1, column=1, sticky="ew", padx=5, pady=2) # --- Action Buttons --- button_frame = ttk.Frame(main_frame) button_frame.grid(row=2, column=0, columnspan=2, sticky="ew", pady=(0, 10)) button_frame.columnconfigure(0, weight=1) button_frame.columnconfigure(1, weight=1) button_frame.columnconfigure(2, weight=1) ttk.Button(button_frame, text="Add New Period", command=self.add_period).grid(row=0, column=0, padx=5, sticky="ew") ttk.Button(button_frame, text="Edit Selected", command=self.edit_period).grid(row=0, column=1, padx=5, sticky="ew") ttk.Button(button_frame, text="Delete Selected", command=self.delete_period).grid(row=0, column=2, padx=5, sticky="ew") # --- Status Label --- self.status_label = ttk.Label(main_frame, text="", foreground="red") self.status_label.grid(row=3, column=0, columnspan=2, sticky="ew", pady=(0, 5)) def _update_listbox(self): """Refreshes the Listbox with current period data.""" self.period_listbox.delete(0, tk.END) for i, period in enumerate(self.periods): self.period_listbox.insert(tk.END, f"{period['name']}: {period['duration']} days") self._clear_entry_fields() self.selected_period_index = None self.status_label.config(text="") def _clear_entry_fields(self): """Clears the name and duration input fields.""" self.name_entry.delete(0, tk.END) self.duration_entry.delete(0, tk.END) def _validate_input(self, name, duration_str): """ Validates the name and duration inputs. Returns (True, int_duration) on success, (False, error_message) on failure. """ if not name.strip(): return False, "Period name cannot be empty." try: duration = int(duration_str) if duration <= 0: return False, "Duration must be a positive integer." return True, duration except ValueError: return False, "Duration must be a valid integer." def select_period(self, event=None): """ Handles selection of a period in the Listbox, pre-filling input fields for editing. """ selected_indices = self.period_listbox.curselection() if selected_indices: self.selected_period_index = selected_indices[0] period = self.periods[self.selected_period_index] self._clear_entry_fields() self.name_entry.insert(0, period['name']) self.duration_entry.insert(0, str(period['duration'])) self.status_label.config(text="") else: self.selected_period_index = None self._clear_entry_fields() def add_period(self): """Adds a new custom analysis period.""" name = self.name_entry.get() duration_str = self.duration_entry.get() is_valid, result = self._validate_input(name, duration_str) if not is_valid: self.status_label.config(text=result, foreground="red") return new_period = {"name": name.strip(), "duration": result} self.periods.append(new_period) self._save_periods() self._update_listbox() self.status_label.config(text=f"'{name.strip()}' added successfully.", foreground="green") def edit_period(self): """Edits the currently selected custom analysis period.""" if self.selected_period_index is None: self.status_label.config(text="Please select a period to edit.", foreground="red") return name = self.name_entry.get() duration_str = self.duration_entry.get() is_valid, result = self._validate_input(name, duration_str) if not is_valid: self.status_label.config(text=result, foreground="red") return self.periods[self.selected_period_index]['name'] = name.strip() self.periods[self.selected_period_index]['duration'] = result self._save_periods() self._update_listbox() self.status_label.config(text=f"Period updated successfully.", foreground="green") def delete_period(self): """Deletes the currently selected custom analysis period.""" if self.selected_period_index is None: self.status_label.config(text="Please select a period to delete.", foreground="red") return period_name = self.periods[self.selected_period_index]['name'] if messagebox.askyesno("Confirm Delete", f"Are you sure you want to delete '{period_name}'?"): del self.periods[self.selected_period_index] self._save_periods() self._update_listbox() self.status_label.config(text=f"'{period_name}' deleted successfully.", foreground="green") else: self.status_label.config(text="Deletion cancelled.", foreground="blue") def on_closing(self): """Handles operations when the window is closed.""" self._save_periods() # Ensure data is saved on explicit close self.master.destroy() if __name__ == "__main__": root = tk.Tk() app = CustomPeriodsManager(root) root.mainloop() ```

Smells like:
fruitywoodycitrusaromaticgreenfloralsweetfresh spicypowdery
perfume image
Popularity trend coming soon

You’ll be able to track how perfume popularity changes over time, using real search trend data to spot what’s rising, stable, or fading.

Reviews

4.0star rating
ExpensiveFairly priced
Briefly lastingLong lasting
Soft sillageExtreme sillage
MenWomen

Fragrance notes

Related perfumes

Ops! In Blue

Ops! In Blue

by O Boticário

star rating4.5
1996|Female
Kiton Men

Kiton Men

by Kiton

1996|Male
Harrods Parfum Pour Homme

Harrods Parfum Pour Homme

by Roja Dove

star rating4.3
2019|Male
Etoile

Etoile

by Alan Bray

Old Spice Original

Old Spice Original

by Shulton Company

star rating4.2
1938|Male
Corteccia di Pino

Corteccia di Pino

by Borsari

star rating5.0
1940|Unisex
Three Kings

Three Kings

by DSH Perfumes

star rating4.0
2009|Unisex
Coco Mademoiselle L'Eau Privée

Coco Mademoiselle L'Eau P...

by Chanel

star rating4.0
2020|Female
Fleurance

Fleurance

by Juvena

star rating3.8
1994|Female
Velvet Cherry

Velvet Cherry

by Miller Harris

For more insights & collecting perfumes, get the App

nottier apps promo banner