Contact Management System Project in Python

In this tutorial, we will show you how to create Contact Management System Project in Python. A contact management system is an essential tool for organizing and maintaining your personal or business contacts. Building one in Python not only enhances your programming skills but also provides you with a customizable solution tailored to your specific needs. This comprehensive guide will walk you through creating a fully functional contact management system using Python, from planning to deployment.
Introduction
In today’s digital world, effective contact management is crucial for both individuals and organizations. A well-designed contact management system helps you store, organize, and quickly retrieve contact information when needed. Python, with its simplicity and powerful libraries, makes it an excellent choice for developing such applications.
A contact management system built in Python offers several advantages:
- Complete customization based on specific requirements
- Cost-effectiveness compared to commercial solutions
- Opportunity to learn and apply programming concepts
- Flexibility to add features as needs evolve
This project is particularly valuable for Python beginners looking to strengthen their programming fundamentals while creating something practical. By the end of this guide, you’ll have developed a functional contact management application with capabilities for adding, viewing, editing, and deleting contacts, along with additional features like search functionality and data export options.
Prerequisites and Setup
Before diving into development, ensure you have the necessary tools and knowledge to successfully complete this project.
Required Python version: Python 3.6 or higher is recommended for this project. You can verify your Python version by running python --version in your terminal.
Essential packages to install:
pip install tkinter pip install sqlite3
Tkinter comes pre-installed with most Python distributions, but SQLite3 is included in Python’s standard library, so no separate installation is typically required.
Development environment setup:
- Create a dedicated project directory:
mkdir contact_management_system - Set up a virtual environment:
python -m venv venv - Activate the environment:
- Windows:
venv\Scripts\activate - Linux/Mac:
source venv/bin/activate
- Windows:
- Create basic project structure:
contact_management_system/ ├── main.py ├── database.py ├── contact.py ├── ui/ │ ├── __init__.py │ ├── main_window.py │ └── dialogs.py └── resources/
Basic Python knowledge including functions, classes, file operations, and error handling is essential. Familiarity with SQL basics will also prove helpful but isn’t mandatory as we’ll cover the necessary concepts.
Project Planning and Overview
A well-planned project saves time and reduces debugging efforts later. Let’s outline our contact management system requirements and architecture.
System Requirements Analysis:
Our contact management system should:
- Store and manage contact information (name, phone, email, address, etc.)
- Provide an intuitive user interface
- Allow adding, viewing, updating, and deleting contacts
- Include search and filter capabilities
- Support data import/export functionality
- Implement data validation to ensure information integrity
- Store data persistently using a database
Features and Functionality Planning:
- Core Features: CRUD operations (Create, Read, Update, Delete)
- Secondary Features: Search, filter, sort contacts
- Advanced Features: Data import/export, contact categorization, reminders
Data Structure Design:
The primary data entity is the Contact, which will have these attributes:
- ID (unique identifier)
- First Name
- Last Name
- Phone Number(s)
- Email Address(es)
- Physical Address
- Notes
- Category/Group
- Date Added/Modified
Application Architecture:
We’ll implement the Model-View-Controller (MVC) pattern to separate concerns:
- Model: Database and contact classes (data layer)
- View: Tkinter UI components (presentation layer)
- Controller: Logic connecting the UI with data operations (business logic layer)
This separation makes the code more maintainable and easier to extend with new features in the future.
Understanding the Core Features
The foundation of our contact management system lies in its core features. Let’s examine these essential functionalities in detail.
Adding New Contacts:
The system must allow users to create new contact entries with validation to ensure data quality. The add contact form will:
- Collect all necessary contact details
- Validate inputs (e.g., email format, phone number pattern)
- Save data to the database
- Provide feedback on successful addition
Viewing Contact Information:
Users should be able to:
- See a list of all contacts with essential information
- View detailed information for a selected contact
- Navigate through multiple contacts efficiently
- Sort contacts by different fields (name, date added, etc.)
Searching and Filtering:
An effective search mechanism is crucial for finding contacts quickly:
- Search by any field (name, phone, email, etc.)
- Filter contacts by categories/groups
- Perform partial matches for convenience
- Display search results in real-time
Updating Existing Contacts:
Contact information frequently changes, so our system must provide:
- Easy access to edit forms for existing contacts
- Pre-filled fields with current data
- Validation for updated information
- Confirmation of successful updates
Deleting Contacts:
The application should allow for:
- Removing individual contacts
- Batch deletion of multiple contacts
- Confirmation dialogs to prevent accidental deletions
- Potential for a “trash” feature for recovery
Importing and Exporting:
For data portability, we’ll implement:
- CSV import/export functionality
- vCard format support
- Export options for backup purposes
Each of these core features requires careful implementation with proper error handling and user feedback to ensure a smooth user experience.
Database Design and Implementation
A well-designed database is critical for our contact management system’s performance and scalability.
Database Selection: SQLite vs. MySQL
For this project, we’ll use SQLite because:
- It’s lightweight and requires no separate server process
- It stores the entire database in a single file
- Python includes built-in support via the sqlite3 module
- It’s perfect for desktop applications with moderate data volume
However, for enterprise applications handling thousands of contacts, MySQL might be more appropriate due to its better performance with large datasets and concurrent access.
Database Schema Design:
Our database will consist of these main tables:
# Primary contact information table
CREATE TABLE contacts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
first_name TEXT NOT NULL,
last_name TEXT NOT NULL,
company TEXT,
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
# Phone numbers table (one-to-many relationship)
CREATE TABLE phone_numbers (
id INTEGER PRIMARY KEY AUTOINCREMENT,
contact_id INTEGER,
phone_type TEXT,
phone_number TEXT NOT NULL,
FOREIGN KEY (contact_id) REFERENCES contacts (id) ON DELETE CASCADE
);
# Email addresses table (one-to-many relationship)
CREATE TABLE email_addresses (
id INTEGER PRIMARY KEY AUTOINCREMENT,
contact_id INTEGER,
email_type TEXT,
email_address TEXT NOT NULL,
FOREIGN KEY (contact_id) REFERENCES contacts (id) ON DELETE CASCADE
);
# Addresses table
CREATE TABLE addresses (
id INTEGER PRIMARY KEY AUTOINCREMENT,
contact_id INTEGER,
address_type TEXT,
street TEXT,
city TEXT,
state TEXT,
zip_code TEXT,
country TEXT,
FOREIGN KEY (contact_id) REFERENCES contacts (id) ON DELETE CASCADE
);
# Groups/categories table
CREATE TABLE groups (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL
);
# Many-to-many relationship between contacts and groups
CREATE TABLE contact_groups (
contact_id INTEGER,
group_id INTEGER,
PRIMARY KEY (contact_id, group_id),
FOREIGN KEY (contact_id) REFERENCES contacts (id) ON DELETE CASCADE,
FOREIGN KEY (group_id) REFERENCES groups (id) ON DELETE CASCADE
);
Database Connection Setup:
Let’s create a database module to handle all database operations:
# database.py
import sqlite3
import os
class Database:
def __init__(self, db_file="contacts.db"):
self.db_file = db_file
self.connection = None
self.initialize_database()
def connect(self):
"""Establish database connection"""
self.connection = sqlite3.connect(self.db_file)
self.connection.row_factory = sqlite3.Row
return self.connection
def initialize_database(self):
"""Create tables if they don't exist"""
conn = self.connect()
cursor = conn.cursor()
# Execute CREATE TABLE statements here
# ...
conn.commit()
conn.close()
# CRUD operations will be implemented here
Data Persistence Considerations:
- Implement regular database backups
- Use transactions for operations that modify multiple tables
- Consider adding indexes for frequently queried fields
- Implement proper error handling and connection management
This database design provides flexibility for future enhancements while maintaining data integrity through proper relationships between tables.
Creating the User Interface with Tkinter
A user-friendly interface is essential for any application. We’ll use Tkinter, Python’s standard GUI toolkit, to create our contact management system’s interface.
Introduction to Tkinter Basics:
Tkinter provides various widgets like buttons, labels, entries, and frames to build interactive interfaces. First, let’s set up our main application window:
import tkinter as tk
from tkinter import ttk
class ContactManagerApp:
def __init__(self, root):
self.root = root
self.root.title("Contact Management System")
self.root.geometry("900x600")
self.root.minsize(800, 500)
# Set application icon
# self.root.iconbitmap("resources/icon.ico") # Uncomment and add your icon
# Configure style
self.style = ttk.Style()
self.style.configure("TButton", padding=6, relief="flat", font=("Arial", 10))
self.style.configure("TFrame", background="#f0f0f0")
self.setup_ui()
def setup_ui(self):
"""Create the main UI components"""
# Create main frames
self.main_frame = ttk.Frame(self.root)
self.main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# Create sidebar for navigation and actions
self.sidebar = ttk.Frame(self.main_frame, width=200)
self.sidebar.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 10))
# Create content area
self.content = ttk.Frame(self.main_frame)
self.content.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True)
# Add navigation buttons to sidebar
ttk.Button(self.sidebar, text="All Contacts", command=self.show_all_contacts).pack(fill=tk.X, pady=2)
ttk.Button(self.sidebar, text="Add Contact", command=self.show_add_contact).pack(fill=tk.X, pady=2)
ttk.Button(self.sidebar, text="Groups", command=self.show_groups).pack(fill=tk.X, pady=2)
ttk.Button(self.sidebar, text="Import/Export", command=self.show_import_export).pack(fill=tk.X, pady=2)
# Show all contacts by default
self.show_all_contacts()
Main Application Window Setup:
Our main window will be divided into:
- A navigation sidebar on the left
- A content area on the right that changes based on the selected function
- A status bar at the bottom for displaying messages
Contact List View Implementation:
The contacts list is a central component of our application:
def show_all_contacts(self):
"""Display all contacts in a table view"""
# Clear existing content
for widget in self.content.winfo_children():
widget.destroy()
# Create search frame
search_frame = ttk.Frame(self.content)
search_frame.pack(fill=tk.X, pady=(0, 10))
ttk.Label(search_frame, text="Search:").pack(side=tk.LEFT, padx=(0, 5))
self.search_var = tk.StringVar()
search_entry = ttk.Entry(search_frame, textvariable=self.search_var, width=30)
search_entry.pack(side=tk.LEFT, padx=(0, 5))
search_entry.bind("", self.search_contacts)
# Create table
columns = ("ID", "Name", "Phone", "Email", "Group")
self.contacts_table = ttk.Treeview(self.content, columns=columns, show="headings")
# Configure columns
self.contacts_table.heading("ID", text="ID")
self.contacts_table.heading("Name", text="Name")
self.contacts_table.heading("Phone", text="Phone")
self.contacts_table.heading("Email", text="Email")
self.contacts_table.heading("Group", text="Group")
self.contacts_table.column("ID", width=50)
self.contacts_table.column("Name", width=150)
self.contacts_table.column("Phone", width=120)
self.contacts_table.column("Email", width=200)
self.contacts_table.column("Group", width=100)
# Add scrollbar
scrollbar = ttk.Scrollbar(self.content, orient=tk.VERTICAL, command=self.contacts_table.yview)
self.contacts_table.configure(yscroll=scrollbar.set)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.contacts_table.pack(fill=tk.BOTH, expand=True)
# Add action buttons below the table
action_frame = ttk.Frame(self.content)
action_frame.pack(fill=tk.X, pady=(10, 0))
ttk.Button(action_frame, text="View/Edit", command=self.edit_selected_contact).pack(side=tk.LEFT, padx=(0, 5))
ttk.Button(action_frame, text="Delete", command=self.delete_selected_contact).pack(side=tk.LEFT, padx=(0, 5))
# Populate with contacts
self.load_contacts()
Form Design for Adding and Editing Contacts:
We’ll create reusable form components for adding and editing contacts:
def show_add_contact(self):
"""Display form for adding a new contact"""
# Clear existing content
for widget in self.content.winfo_children():
widget.destroy()
# Create form
ttk.Label(self.content, text="Add New Contact", font=("Arial", 14, "bold")).grid(row=0, column=0, columnspan=2, sticky=tk.W, pady=(0, 10))
# First name
ttk.Label(self.content, text="First Name:").grid(row=1, column=0, sticky=tk.W, pady=2)
self.first_name_var = tk.StringVar()
ttk.Entry(self.content, textvariable=self.first_name_var, width=30).grid(row=1, column=1, sticky=tk.W, pady=2)
# Last name
ttk.Label(self.content, text="Last Name:").grid(row=2, column=0, sticky=tk.W, pady=2)
self.last_name_var = tk.StringVar()
ttk.Entry(self.content, textvariable=self.last_name_var, width=30).grid(row=2, column=1, sticky=tk.W, pady=2)
# Phone
ttk.Label(self.content, text="Phone:").grid(row=3, column=0, sticky=tk.W, pady=2)
self.phone_var = tk.StringVar()
ttk.Entry(self.content, textvariable=self.phone_var, width=30).grid(row=3, column=1, sticky=tk.W, pady=2)
# Email
ttk.Label(self.content, text="Email:").grid(row=4, column=0, sticky=tk.W, pady=2)
self.email_var = tk.StringVar()
ttk.Entry(self.content, textvariable=self.email_var, width=30).grid(row=4, column=1, sticky=tk.W, pady=2)
# Address
ttk.Label(self.content, text="Address:").grid(row=5, column=0, sticky=tk.W, pady=2)
self.address_var = tk.StringVar()
ttk.Entry(self.content, textvariable=self.address_var, width=30).grid(row=5, column=1, sticky=tk.W, pady=2)
# Group/Category
ttk.Label(self.content, text="Group:").grid(row=6, column=0, sticky=tk.W, pady=2)
self.group_var = tk.StringVar()
ttk.Combobox(self.content, textvariable=self.group_var, values=self.get_groups()).grid(row=6, column=1, sticky=tk.W, pady=2)
# Notes
ttk.Label(self.content, text="Notes:").grid(row=7, column=0, sticky=tk.NW, pady=2)
self.notes_var = tk.StringVar()
text_notes = tk.Text(self.content, width=30, height=5)
text_notes.grid(row=7, column=1, sticky=tk.W, pady=2)
# Save button
ttk.Button(self.content, text="Save Contact", command=self.save_contact).grid(row=8, column=1, sticky=tk.W, pady=(10, 0))
Menu and Navigation Design:
A menu system provides alternative navigation and access to additional functions:
def create_menu(self):
"""Create application menu"""
menubar = tk.Menu(self.root)
# File menu
file_menu = tk.Menu(menubar, tearoff=0)
file_menu.add_command(label="New Contact", command=self.show_add_contact)
file_menu.add_command(label="Import Contacts", command=self.import_contacts)
file_menu.add_command(label="Export Contacts", command=self.export_contacts)
file_menu.add_separator()
file_menu.add_command(label="Exit", command=self.root.quit)
menubar.add_cascade(label="File", menu=file_menu)
# Edit menu
edit_menu = tk.Menu(menubar, tearoff=0)
edit_menu.add_command(label="Preferences", command=self.show_preferences)
menubar.add_cascade(label="Edit", menu=edit_menu)
# Help menu
help_menu = tk.Menu(menubar, tearoff=0)
help_menu.add_command(label="Help Contents", command=self.show_help)
help_menu.add_command(label="About", command=self.show_about)
menubar.add_cascade(label="Help", menu=help_menu)
self.root.config(menu=menubar)
With these components, we create a responsive and intuitive interface that allows users to efficiently manage their contacts.
Implementing Contact Addition Functionality
Adding new contacts is a fundamental feature of our system. Let’s implement this functionality with proper validation and database integration.
Creating the Contact Form:
We’ve already designed the form UI in the previous section. Now, let’s focus on the backend functionality:
def save_contact(self):
"""Save a new contact to the database"""
# Get values from form
first_name = self.first_name_var.get().strip()
last_name = self.last_name_var.get().strip()
phone = self.phone_var.get().strip()
email = self.email_var.get().strip()
address = self.address_var.get().strip()
group = self.group_var.get().strip()
notes = self.notes_text.get("1.0", tk.END).strip()
# Validate inputs
if not first_name or not last_name:
self.show_error("First name and last name are required!")
return
if phone and not self.validate_phone(phone):
self.show_error("Invalid phone number format!")
return
if email and not self.validate_email(email):
self.show_error("Invalid email format!")
return
# Save to database
try:
conn = self.db.connect()
cursor = conn.cursor()
# Insert into contacts table
cursor.execute("""
INSERT INTO contacts (first_name, last_name, notes, created_at, updated_at)
VALUES (?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
""", (first_name, last_name, notes))
contact_id = cursor.lastrowid
# Insert phone number if provided
if phone:
cursor.execute("""
INSERT INTO phone_numbers (contact_id, phone_type, phone_number)
VALUES (?, ?, ?)
""", (contact_id, "mobile", phone))
# Insert email if provided
if email:
cursor.execute("""
INSERT INTO email_addresses (contact_id, email_type, email_address)
VALUES (?, ?, ?)
""", (contact_id, "personal", email))
# Insert address if provided
if address:
# Simplified for this example; in a real app, address would be broken down
cursor.execute("""
INSERT INTO addresses (contact_id, address_type, street)
VALUES (?, ?, ?)
""", (contact_id, "home", address))
# Add to group if provided
if group:
# First, ensure the group exists
cursor.execute("SELECT id FROM groups WHERE name = ?", (group,))
result = cursor.fetchone()
if result:
group_id = result[0]
else:
cursor.execute("INSERT INTO groups (name) VALUES (?)", (group,))
group_id = cursor.lastrowid
cursor.execute("""
INSERT INTO contact_groups (contact_id, group_id)
VALUES (?, ?)
""", (contact_id, group_id))
conn.commit()
self.show_success("Contact added successfully!")
# Clear form
self.clear_contact_form()
# Refresh contacts list if it's visible
if hasattr(self, 'contacts_table'):
self.load_contacts()
except sqlite3.Error as e:
conn.rollback()
self.show_error(f"Database error: {e}")
finally:
conn.close()
Input Field Validation:
Proper validation ensures data integrity:
def validate_phone(self, phone):
"""Validate phone number format"""
import re
# Basic pattern: optional country code, then digits, possibly separated by spaces, dashes, or dots
pattern = r'^\+?[\d\s\-\.]{7,20}$'
return re.match(pattern, phone) is not None
def validate_email(self, email):
"""Validate email format"""
import re
pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
return re.match(pattern, email) is not None
Error Handling:
Providing clear feedback to users is essential:
def show_error(self, message):
"""Display error message"""
from tkinter import messagebox
messagebox.showerror("Error", message)
def show_success(self, message):
"""Display success message"""
from tkinter import messagebox
messagebox.showinfo("Success", message)
Handling Duplicate Entries:
To prevent duplicate contacts, we can add a check before insertion:
def is_duplicate_contact(self, first_name, last_name, phone=None, email=None):
"""Check if contact with same name or contact details already exists"""
conn = self.db.connect()
cursor = conn.cursor()
# Check by name
cursor.execute("""
SELECT id FROM contacts
WHERE first_name = ? AND last_name = ?
""", (first_name, last_name))
if cursor.fetchone():
conn.close()
return True
# Check by phone if provided
if phone:
cursor.execute("""
SELECT c.id FROM contacts c
JOIN phone_numbers p ON c.id = p.contact_id
WHERE p.phone_number = ?
""", (phone,))
if cursor.fetchone():
conn.close()
return True
# Check by email if provided
if email:
cursor.execute("""
SELECT c.id FROM contacts c
JOIN email_addresses e ON c.id = e.contact_id
WHERE e.email_address = ?
""", (email,))
if cursor.fetchone():
conn.close()
return True
conn.close()
return False
This implementation ensures that contacts are properly validated before being added to the database, maintaining data integrity and providing a smooth user experience.
Implementing Contact Viewing and Search
An efficient contact viewing and search system is essential for navigating through your contacts, especially as the database grows.
Retrieving Contacts from the Database:
Let’s implement the function to load contacts from the database:
def load_contacts(self, search_term=None):
"""Load contacts from database into the table view"""
# Clear existing items
for item in self.contacts_table.get_children():
self.contacts_table.delete(item)
conn = self.db.connect()
cursor = conn.cursor()
query = """
SELECT
c.id,
c.first_name,
c.last_name,
p.phone_number,
e.email_address,
g.name as group_name
FROM contacts c
LEFT JOIN phone_numbers p ON c.id = p.contact_id AND p.id = (
SELECT MIN(id) FROM phone_numbers WHERE contact_id = c.id
)
LEFT JOIN email_addresses e ON c.id = e.contact_id AND e.id = (
SELECT MIN(id) FROM email_addresses WHERE contact_id = c.id
)
LEFT JOIN contact_groups cg ON c.id = cg.contact_id
LEFT JOIN groups g ON cg.group_id = g.id
"""
params = []
if search_term:
query += """
WHERE c.first_name LIKE ? OR c.last_name LIKE ?
OR p.phone_number LIKE ? OR e.email_address LIKE ?
OR g.name LIKE ?
"""
search_pattern = f"%{search_term}%"
params = [search_pattern, search_pattern, search_pattern, search_pattern, search_pattern]
query += " GROUP BY c.id ORDER BY c.last_name, c.first_name"
cursor.execute(query, params)
# Insert data into table
for row in cursor.fetchall():
contact_id = row[0]
full_name = f"{row[1]} {row[2]}"
phone = row[3] if row[3] else ""
email = row[4] if row[4] else ""
group = row[5] if row[5] else ""
self.contacts_table.insert("", "end", values=(contact_id, full_name, phone, email, group))
conn.close()
Implementing Search Functionality:
We’ll implement a real-time search that filters contacts as the user types:
def search_contacts(self, event=None):
"""Search contacts based on input"""
search_term = self.search_var.get().strip()
self.load_contacts(search_term)
Filtering Contacts by Different Criteria:
Let’s add more advanced filtering options:
def show_filter_options(self):
"""Display filter options dialog"""
filter_window = tk.Toplevel(self.root)
filter_window.title("Filter Contacts")
filter_window.geometry("300x250")
filter_window.transient(self.root)
filter_window.grab_set()
ttk.Label(filter_window, text="Filter Contacts", font=("Arial", 12, "bold")).pack(pady=(10, 15))
# Group filter
ttk.Label(filter_window, text="By Group:").pack(anchor=tk.W, padx=10)
self.filter_group_var = tk.StringVar()
group_combo = ttk.Combobox(filter_window, textvariable=self.filter_group_var)
group_combo['values'] = ['All Groups'] + self.get_groups()
group_combo.current(0)
group_combo.pack(fill=tk.X, padx=10, pady=(0, 10))
# Has email filter
self.has_email_var = tk.BooleanVar()
ttk.Checkbutton(filter_window, text="Has Email", variable=self.has_email_var).pack(anchor=tk.W, padx=10)
# Has phone filter
self.has_phone_var = tk.BooleanVar()
ttk.Checkbutton(filter_window, text="Has Phone", variable=self.has_phone_var).pack(anchor=tk.W, padx=10)
# Recently added filter
self.recent_var = tk.BooleanVar()
ttk.Checkbutton(filter_window, text="Added in last 30 days", variable=self.recent_var).pack(anchor=tk.W, padx=10)
# Apply button
ttk.Button(filter_window, text="Apply Filter", command=lambda: self.apply_filters(filter_window)).pack(pady=15)
Implementing Sorting:
Allow users to sort contacts by clicking on column headers:
def setup_column_sorting(self):
"""Set up sorting when clicking on column headers"""
for col in ("Name", "Phone", "Email", "Group"):
self.contacts_table.heading(col, command=lambda c=col: self.sort_contacts_by(c))
def sort_contacts_by(self, column):
"""Sort contacts table by the selected column"""
# Get all items with their values
data = [(self.contacts_table.set(child, column), child) for child in self.contacts_table.get_children('')]
# Sort the data
data.sort()
# Rearrange items in sorted positions
for index, (val, child) in enumerate(data):
self.contacts_table.move(child, '', index)
Implementing Pagination:
For large contact lists, pagination is essential:
def setup_pagination(self):
"""Set up pagination controls"""
self.page_size = 20
self.current_page = 1
self.total_pages = 1
# Create pagination frame
pagination_frame = ttk.Frame(self.content)
pagination_frame.pack(fill=tk.X, pady=5)
# Previous page button
self.prev_btn = ttk.Button(pagination_frame, text="« Previous", command=self.previous_page)
self.prev_btn.pack(side=tk.LEFT, padx=5)
# Page indicator
self.page_label = ttk.Label(pagination_frame, text="Page 1 of 1")
self.page_label.pack(side=tk.LEFT, padx=5)
# Next page button
self.next_btn = ttk.Button(pagination_frame, text="Next »", command=self.next_page)
self.next_btn.pack(side=tk.LEFT, padx=5)
# Page size selector
ttk.Label(pagination_frame, text="Items per page:").pack(side=tk.LEFT, padx=(20, 5))
self.page_size_var = tk.StringVar(value="20")
page_size_combo = ttk.Combobox(pagination_frame, textvariable=self.page_size_var, values=["10", "20", "50", "100"], width=5)
page_size_combo.pack(side=tk.LEFT)
page_size_combo.bind("<>", lambda e: self.change_page_size())
This comprehensive implementation provides users with powerful tools to find and view their contacts efficiently, improving the overall usability of our application.
Update and Delete Operations
Effective contact management requires the ability to modify and remove contact information as needed.
Selecting Contacts for Update/Delete:
Users select contacts from the list view:
def get_selected_contact(self):
"""Get the currently selected contact from the table"""
selected_items = self.contacts_table.selection()
if not selected_items:
self.show_error("Please select a contact first")
return None
item = selected_items[0]
contact_id = self.contacts_table.item(item, 'values')[0]
return contact_id
Implementing the Update Form:
The update form pre-fills with the selected contact’s information:
def edit_selected_contact(self):
"""Open form to edit the selected contact"""
contact_id = self.get_selected_contact()
if not contact_id:
return
# Fetch contact data
conn = self.db.connect()
cursor = conn.cursor()
# Get basic contact info
cursor.execute("""
SELECT first_name, last_name, notes
FROM contacts
WHERE id = ?
""", (contact_id,))
contact = cursor.fetchone()
if not contact:
conn.close()
self.show_error("Contact not found")
return
# Get phone numbers
cursor.execute("""
SELECT phone_type, phone_number
FROM phone_numbers
WHERE contact_id = ?
ORDER BY id
""", (contact_id,))
phones = cursor.fetchall()
# Get email addresses
cursor.execute("""
SELECT email_type, email_address
FROM email_addresses
WHERE contact_id = ?
ORDER BY id
""", (contact_id,))
emails = cursor.fetchall()
# Get addresses
cursor.execute("""
SELECT address_type, street, city, state, zip_code, country
FROM addresses
WHERE contact_id = ?
ORDER BY id
""", (contact_id,))
addresses = cursor.fetchall()
# Get groups
cursor.execute("""
SELECT g.name
FROM groups g
JOIN contact_groups cg ON g.id = cg.group_id
WHERE cg.contact_id = ?
""", (contact_id,))
groups = cursor.fetchall()
conn.close()
# Create edit window
edit_window = tk.Toplevel(self.root)
edit_window.title(f"Edit Contact: {contact[0]} {contact[1]}")
edit_window.geometry("500x600")
edit_window.transient(self.root)
edit_window.grab_set()
# Create scrollable frame
canvas = tk.Canvas(edit_window)
scrollbar = ttk.Scrollbar(edit_window, orient="vertical", command=canvas.yview)
scrollable_frame = ttk.Frame(canvas)
scrollable_frame.bind(
"",
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
)
canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
canvas.configure(yscrollcommand=scrollbar.set)
canvas.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")
# Now add all the form fields to scrollable_frame
# ... (add all form fields with pre-filled data) ...
# Save button
ttk.Button(scrollable_frame, text="Save Changes",
command=lambda: self.save_contact_changes(contact_id, edit_window)).grid(
row=100, column=1, sticky=tk.W, pady=(20, 10))
Implementing Delete Functionality:
Confirm before deleting to prevent accidental data loss:
def delete_selected_contact(self):
"""Delete the selected contact"""
contact_id = self.get_selected_contact()
if not contact_id:
return
# Get contact name for confirmation message
conn = self.db.connect()
cursor = conn.cursor()
cursor.execute("SELECT first_name, last_name FROM contacts WHERE id = ?", (contact_id,))
contact = cursor.fetchone()
conn.close()
if not contact:
self.show_error("Contact not found")
return
contact_name = f"{contact[0]} {contact[1]}"
# Confirm deletion
from tkinter import messagebox
confirm = messagebox.askyesno(
"Confirm Deletion",
f"Are you sure you want to delete {contact_name}?\n\nThis action cannot be undone."
)
if not confirm:
return
# Perform deletion
try:
conn = self.db.connect()
cursor = conn.cursor()
# Due to ON DELETE CASCADE constraints, we only need to delete from contacts table
cursor.execute("DELETE FROM contacts WHERE id = ?", (contact_id,))
conn.commit()
self.show_success(f"{contact_name} has been deleted")
# Refresh contacts list
self.load_contacts()
except sqlite3.Error as e:
conn.rollback()
self.show_error(f"Error deleting contact: {e}")
finally:
conn.close()
Batch Operations:
For efficiency, we can implement batch operations:
def batch_delete_contacts(self):
"""Delete multiple selected contacts"""
selected_items = self.contacts_table.selection()
if not selected_items:
self.show_error("Please select at least one contact")
return
# Confirm deletion
from tkinter import messagebox
confirm = messagebox.askyesno(
"Confirm Batch Deletion",
f"Are you sure you want to delete {len(selected_items)} contacts?\n\nThis action cannot be undone."
)
if not confirm:
return
# Perform batch deletion
try:
conn = self.db.connect()
cursor = conn.cursor()
for item in selected_items:
contact_id = self.contacts_table.item(item, 'values')[0]
cursor.execute("DELETE FROM contacts WHERE id = ?", (contact_id,))
conn.commit()
self.show_success(f"{len(selected_items)} contacts have been deleted")
# Refresh contacts list
self.load_contacts()
except sqlite3.Error as e:
conn.rollback()
self.show_error(f"Error deleting contacts: {e}")
finally:
conn.close()
These implementations provide users with complete control over their contact data, allowing them to keep their contact list up-to-date and relevant.
Error Handling and Data Validation
Robust error handling and data validation are crucial for maintaining data integrity and providing a smooth user experience.
Input Validation Strategies:
We’ll implement comprehensive validation for all user inputs:
def validate_contact_data(self, first_name, last_name, phones, emails, addresses):
"""Validate all contact data before saving"""
errors = []
# Validate names
if not first_name.strip():
errors.append("First name is required")
if not last_name.strip():
errors.append("Last name is required")
# Validate phone numbers
for phone_type, phone in phones:
if phone and not self.validate_phone(phone):
errors.append(f"Invalid phone number format: {phone}")
# Validate emails
for email_type, email in emails:
if email and not self.validate_email(email):
errors.append(f"Invalid email format: {email}")
# Validate addresses (simplified validation)
for address in addresses:
if address.get('zip_code') and not self.validate_zip_code(address['zip_code']):
errors.append(f"Invalid ZIP code format: {address['zip_code']}")
return errors
Common Error Scenarios:
Let’s handle various error situations gracefully:
def handle_database_errors(self, func):
"""Decorator to handle database errors"""
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except sqlite3.Error as e:
self.log_error(f"Database error in {func.__name__}: {e}")
self.show_error(f"A database error occurred: {e}")
except Exception as e:
self.log_error(f"Unexpected error in {func.__name__}: {e}")
self.show_error(f"An unexpected error occurred: {e}")
return wrapper
User-Friendly Error Messages:
Clear error messages help users correct issues:
def show_validation_errors(self, errors):
"""Display validation errors in a user-friendly format"""
if not errors:
return
error_window = tk.Toplevel(self.root)
error_window.title("Validation Errors")
error_window.geometry("400x300")
error_window.transient(self.root)
error_window.grab_set()
ttk.Label(error_window, text="Please correct the following errors:",
font=("Arial", 12, "bold")).pack(pady=(10, 5))
# Create scrollable frame for errors
frame = ttk.Frame(error_window)
frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
canvas = tk.Canvas(frame)
scrollbar = ttk.Scrollbar(frame, orient="vertical", command=canvas.yview)
error_frame = ttk.Frame(canvas)
error_frame.bind(
"",
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
)
canvas.create_window((0, 0), window=error_frame, anchor="nw")
canvas.configure(yscrollcommand=scrollbar.set)
canvas.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")
# Display errors as a list
for i, error in enumerate(errors):
ttk.Label(error_frame, text=f"• {error}", wraplength=350).grid(
row=i, column=0, sticky=tk.W, pady=2)
ttk.Button(error_window, text="OK", command=error_window.destroy).pack(pady=10)
Logging for Debugging:
Implement a logging system to track errors:
def setup_logging(self):
"""Setup logging for the application"""
import logging
import os
from datetime import datetime
# Create logs directory if it doesn't exist
if not os.path.exists("logs"):
os.makedirs("logs")
# Setup logging
log_file = f"logs/contacts_manager_{datetime.now().strftime('%Y%m%d')}.log"
logging.basicConfig(
filename=log_file,
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
self.logger = logging
def log_error(self, message):
"""Log an error message"""
self.logger.error(message)
With these error handling and validation mechanisms in place, our application becomes more robust and user-friendly, minimizing data corruption and providing clear guidance when issues arise.
Alternative Implementation: Command-Line Version
A command-line interface (CLI) version of our contact manager provides flexibility for users who prefer terminal-based applications or need to automate contact management tasks.
Converting GUI to CLI:
The CLI version shares the same core functionality but uses a text-based interface:
# cli.py
import sqlite3
import argparse
import sys
from database import Database
class ContactManagerCLI:
def __init__(self):
self.db = Database()
self.setup_parser()
def setup_parser(self):
"""Set up command-line argument parser"""
self.parser = argparse.ArgumentParser(description='Command-line Contact Management System')
subparsers = self.parser.add_subparsers(dest='command', help='Command to execute')
# List contacts command
list_parser = subparsers.add_parser('list', help='List all contacts')
list_parser.add_argument('-s', '--search', help='Search term')
list_parser.add_argument('-g', '--group', help='Filter by group')
# Add contact command
add_parser = subparsers.add_parser('add', help='Add a new contact')
add_parser.add_argument('-f', '--firstname', required=True, help='First name')
add_parser.add_argument('-l', '--lastname', required=True, help='Last name')
add_parser.add_argument('-p', '--phone', help='Phone number')
add_parser.add_argument('-e', '--email', help='Email address')
add_parser.add_argument('-a', '--address', help='Physical address')
add_parser.add_argument('-g', '--group', help='Contact group')
add_parser.add_argument('-n', '--notes', help='Notes about the contact')
# View contact command
view_parser = subparsers.add_parser('view', help='View a contact')
view_parser.add_argument('id', type=int, help='Contact ID')
# Update contact command
update_parser = subparsers.add_parser('update', help='Update a contact')
update_parser.add_argument('id', type=int, help='Contact ID')
update_parser.add_argument('-f', '--firstname', help='First name')
update_parser.add_argument('-l', '--lastname', help='Last name')
update_parser.add_argument('-p', '--phone', help='Phone number')
update_parser.add_argument('-e', '--email', help='Email address')
update_parser.add_argument('-a', '--address', help='Physical address')
update_parser.add_argument('-g', '--group', help='Contact group')
update_parser.add_argument('-n', '--notes', help='Notes about the contact')
# Delete contact command
delete_parser = subparsers.add_parser('delete', help='Delete a contact')
delete_parser.add_argument('id', type=int, help='Contact ID')
# Export contacts command
export_parser = subparsers.add_parser('export', help='Export contacts')
export_parser.add_argument('-f', '--file', required=True, help='Export file (CSV)')
export_parser.add_argument('-g', '--group', help='Export only contacts from this group')
# Import contacts command
import_parser = subparsers.add_parser('import', help='Import contacts')
import_parser.add_argument('-f', '--file', required=True, help='Import file (CSV)')
Menu-Driven Interface Design:
For interactive use, we can implement a menu-driven interface:
def run_interactive_menu(self):
"""Run an interactive menu-driven interface"""
while True:
print("\n===== Contact Management System =====")
print("1. List all contacts")
print("2. Add a new contact")
print("3. View contact details")
print("4. Update a contact")
print("5. Delete a contact")
print("6. Export contacts")
print("7. Import contacts")
print("0. Exit")
choice = input("\nEnter your choice (0-7): ")
if choice == '0':
print("Thank you for using Contact Management System!")
break
elif choice == '1':
self.interactive_list_contacts()
elif choice == '2':
self.interactive_add_contact()
elif choice == '3':
self.interactive_view_contact()
elif choice == '4':
self.interactive_update_contact()
elif choice == '5':
self.interactive_delete_contact()
elif choice == '6':
self.interactive_export_contacts()
elif choice == '7':
self.interactive_import_contacts()
else:
print("Invalid choice! Please enter a number between 0 and 7.")
Implementation Differences:
The CLI version differs from the GUI in several ways:
- Text-based input and output instead of graphical forms
- Command-line arguments for direct operation
- Interactive mode for menu-driven operation
- Streamlined validation with text prompts
- Batch operations through shell scripts
- Output formatting for terminal display
Advantages and Limitations:
The CLI version offers several advantages:
- Faster operation for experienced users
- Lower resource requirements
- Scriptable for automation
- Remote access via SSH
- Integration with other command-line tools
However, it also has limitations:
- Steeper learning curve for beginners
- Less intuitive interface
- Limited visual feedback
- No direct image display for contacts
The CLI version is particularly useful for system administrators, developers, and power users who spend significant time in the terminal or need to integrate contact management with other command-line tools.
Testing the Application
Comprehensive testing ensures our contact management system works correctly and reliably under various conditions.
Testing Strategy:
We’ll implement a multi-layered testing approach:
# tests/test_database.py
import unittest
import os
import sqlite3
from database import Database
class TestDatabase(unittest.TestCase):
def setUp(self):
"""Set up test database"""
self.test_db_file = "test_contacts.db"
# Delete test database if it exists
if os.path.exists(self.test_db_file):
os.remove(self.test_db_file)
self.db = Database(self.test_db_file)
def tearDown(self):
"""Clean up after test"""
if os.path.exists(self.test_db_file):
os.remove(self.test_db_file)
def test_database_initialization(self):
"""Test database initialization creates tables"""
conn = sqlite3.connect(self.test_db_file)
cursor = conn.cursor()
# Check if tables exist
cursor.execute("SELECT name FROM sqlite_master WHERE type='table'")
tables = cursor.fetchall()
table_names = [table[0] for table in tables]
self.assertIn("contacts", table_names)
self.assertIn("phone_numbers", table_names)
self.assertIn("email_addresses", table_names)
self.assertIn("addresses", table_names)
self.assertIn("groups", table_names)
self.assertIn("contact_groups", table_names)
conn.close()
Unit Testing Components:
We’ll test individual components in isolation:
# tests/test_contact.py
import unittest
from contact import Contact
class TestContact(unittest.TestCase):
def test_contact_creation(self):
"""Test contact object creation and properties"""
contact = Contact(
first_name="John",
last_name="Doe",
phones=[("mobile", "123-456-7890")],
emails=[("work", "john.doe@example.com")],
addresses=[{
"type": "home",
"street": "123 Main St",
"city": "Anytown",
"state": "CA",
"zip_code": "12345",
"country": "USA"
}],
groups=["Friends"],
notes="Test contact"
)
self.assertEqual(contact.first_name, "meilana")
self.assertEqual(contact.last_name, "maria")
self.assertEqual(contact.full_name, "meilana maria")
self.assertEqual(contact.primary_phone, "123-456-7890")
self.assertEqual(contact.primary_email, "meilana.maria@example.com")
def test_contact_validation(self):
"""Test contact validation"""
# Invalid first name (empty)
with self.assertRaises(ValueError):
Contact(first_name="", last_name="maria")
# Invalid email format
with self.assertRaises(ValueError):
Contact(
first_name="meilana",
last_name="maria",
emails=[("personal", "invalid-email")]
)
Integration Testing:
We’ll test how components work together:
# tests/test_integration.py
import unittest
import os
from database import Database
from contact import Contact
from contact_manager import ContactManager
class TestIntegration(unittest.TestCase):
def setUp(self):
"""Set up test environment"""
self.test_db_file = "test_integration.db"
if os.path.exists(self.test_db_file):
os.remove(self.test_db_file)
self.db = Database(self.test_db_file)
self.manager = ContactManager(self.db)
def tearDown(self):
"""Clean up after test"""
if os.path.exists(self.test_db_file):
os.remove(self.test_db_file)
def test_add_and_retrieve_contact(self):
"""Test adding and retrieving a contact"""
# Create a contact
contact = Contact(
first_name="nadia",
last_name="shell",
phones=[("home", "555-123-4567")],
emails=[("personal", "nadia.shell@example.com")]
)
# Add to database
contact_id = self.manager.add_contact(contact)
self.assertIsNotNone(contact_id)
# Retrieve from database
retrieved = self.manager.get_contact(contact_id)
self.assertEqual(retrieved.first_name, "nadia")
self.assertEqual(retrieved.last_name, "shell")
self.assertEqual(retrieved.primary_phone, "555-123-4567")
self.assertEqual(retrieved.primary_email, "nadia.shell@example.com")
User Acceptance Testing:
For UI testing, we can use tools like PyAutoGUI or directly test common user workflows:
# Manual test cases for user acceptance testing: # Test Case 1: Add a new contact # 1. Launch application # 2. Click "Add Contact" button # 3. Fill in contact details # 4. Click "Save Contact" # 5. Verify contact appears in the list # Test Case 2: Search for a contact # 1. Launch application # 2. Enter search term in search box # 3. Verify filtered results show matching contacts # Test Case 3: Edit a contact # 1. Launch application # 2. Select a contact from the list # 3. Click "Edit" button # 4. Modify contact details # 5. Click "Save Changes" # 6. Verify changes are reflected in the list
This comprehensive testing approach helps identify and fix issues before users encounter them, ensuring a reliable and robust application.
Advanced Features Implementation
Once the core functionality is solid, we can enhance our contact management system with advanced features.
Contact Grouping/Categorization:
Implement contact groups for better organization:
def manage_groups(self):
"""Open window to manage contact groups"""
groups_window = tk.Toplevel(self.root)
groups_window.title("Manage Contact Groups")
groups_window.geometry("400x500")
groups_window.transient(self.root)
groups_window.grab_set()
# Create left frame for group list
left_frame = ttk.Frame(groups_window)
left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=10, pady=10)
ttk.Label(left_frame, text="Available Groups", font=("Arial", 12, "bold")).pack(anchor=tk.W, pady=(0, 10))
# Create group listbox
group_listbox = tk.Listbox(left_frame, width=25, height=15)
group_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
# Add scrollbar
scrollbar = ttk.Scrollbar(left_frame, orient=tk.VERTICAL, command=group_listbox.yview)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
group_listbox.config(yscrollcommand=scrollbar.set)
# Create right frame for actions
right_frame = ttk.Frame(groups_window)
right_frame.pack(side=tk.RIGHT, fill=tk.Y, padx=10, pady=10)
ttk.Label(right_frame, text="Group Actions", font=("Arial", 12, "bold")).pack(anchor=tk.W, pady=(0, 10))
# Add group frame
add_frame = ttk.LabelFrame(right_frame, text="Add Group")
add_frame.pack(fill=tk.X, pady=(0, 10))
ttk.Label(add_frame, text="Name:").pack(anchor=tk.W, padx=5, pady=(5, 0))
group_name_var = tk.StringVar()
ttk.Entry(add_frame, textvariable=group_name_var).pack(fill=tk.X, padx=5, pady=5)
ttk.Button(add_frame, text="Add Group",
command=lambda: self.add_group(group_name_var.get(), group_listbox)).pack(padx=5, pady=5)
# Rename group frame
rename_frame = ttk.LabelFrame(right_frame, text="Rename Group")
rename_frame.pack(fill=tk.X, pady=(0, 10))
ttk.Label(rename_frame, text="New name:").pack(anchor=tk.W, padx=5, pady=(5, 0))
new_name_var = tk.StringVar()
ttk.Entry(rename_frame, textvariable=new_name_var).pack(fill=tk.X, padx=5, pady=5)
ttk.Button(rename_frame, text="Rename",
command=lambda: self.rename_group(group_listbox.get(tk.ACTIVE), new_name_var.get(), group_listbox)).pack(padx=5, pady=5)
# Delete group button
ttk.Button(right_frame, text="Delete Selected Group",
command=lambda: self.delete_group(group_listbox.get(tk.ACTIVE), group_listbox)).pack(fill=tk.X, pady=(0, 10))
# Refresh button
ttk.Button(right_frame, text="Refresh List",
command=lambda: self.load_groups(group_listbox)).pack(fill=tk.X)
# Load groups
self.load_groups(group_listbox)
Import/Export Functionality:
Allow data portability with import/export functions:
def export_contacts(self):
"""Export contacts to CSV file"""
from tkinter import filedialog
import csv
# Ask for file location
file_path = filedialog.asksaveasfilename(
defaultextension=".csv",
filetypes=[("CSV files", "*.csv"), ("All files", "*.*")],
title="Export Contacts"
)
if not file_path:
return # User cancelled
try:
conn = self.db.connect()
cursor = conn.cursor()
# Get all contacts with their details
query = """
SELECT
c.id, c.first_name, c.last_name, c.notes, c.created_at,
p.phone_number, e.email_address,
a.street, a.city, a.state, a.zip_code, a.country,
g.name as group_name
FROM contacts c
LEFT JOIN phone_numbers p ON c.id = p.contact_id AND p.id = (
SELECT MIN(id) FROM phone_numbers WHERE contact_id = c.id
)
LEFT JOIN email_addresses e ON c.id = e.contact_id AND e.id = (
SELECT MIN(id) FROM email_addresses WHERE contact_id = c.id
)
LEFT JOIN addresses a ON c.id = a.contact_id AND a.id = (
SELECT MIN(id) FROM addresses WHERE contact_id = c.id
)
LEFT JOIN contact_groups cg ON c.id = cg.contact_id
LEFT JOIN groups g ON cg.group_id = g.id
GROUP BY c.id
ORDER BY c.last_name, c.first_name
"""
cursor.execute(query)
contacts = cursor.fetchall()
# Write to CSV
with open(file_path, 'w', newline='') as csvfile:
writer = csv.writer(csvfile)
# Write header
writer.writerow([
"ID", "First Name", "Last Name", "Phone", "Email",
"Street", "City", "State", "ZIP", "Country",
"Group", "Notes", "Created"
])
# Write data
for contact in contacts:
writer.writerow([
contact[0], # ID
contact[1], # First Name
contact[2], # Last Name
contact[5] or "", # Phone
contact[6] or "", # Email
contact[7] or "", # Street
contact[8] or "", # City
contact[9] or "", # State
contact[10] or "", # ZIP
contact[11] or "", # Country
contact[12] or "", # Group
contact[3] or "", # Notes
contact[4] # Created
])
self.show_success(f"Successfully exported {len(contacts)} contacts to {file_path}")
except Exception as e:
self.show_error(f"Error exporting contacts: {e}")
finally:
conn.close()
Adding Images to Contacts:
Enhance contact profiles with photos:
def add_contact_image(self, contact_id):
"""Add or update contact image"""
from tkinter import filedialog
import os
import shutil
# Ask for image file
file_path = filedialog.askopenfilename(
filetypes=[
("Image files", "*.jpg *.jpeg *.png *.gif *.bmp"),
("All files", "*.*")
],
title="Select Contact Image"
)
if not file_path:
return # User cancelled
try:
# Create images directory if it doesn't exist
images_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "images")
if not os.path.exists(images_dir):
os.makedirs(images_dir)
# Copy and rename the image file
file_ext = os.path.splitext(file_path)[1]
new_file_name = f"contact_{contact_id}{file_ext}"
new_file_path = os.path.join(images_dir, new_file_name)
# Remove existing image if any
for ext in ['.jpg', '.jpeg', '.png', '.gif', '.bmp']:
old_file = os.path.join(images_dir, f"contact_{contact_id}{ext}")
if os.path.exists(old_file):
os.remove(old_file)
# Copy the new image
shutil.copy2(file_path, new_file_path)
# Update database to store the image path
conn = self.db.connect()
cursor = conn.cursor()
cursor.execute(
"UPDATE contacts SET image_path = ? WHERE id = ?",
(new_file_name, contact_id)
)
conn.commit()
conn.close()
self.show_success("Contact image updated successfully")
# Refresh contact display if visible
if hasattr(self, 'current_contact_id') and self.current_contact_id == contact_id:
self.display_contact(contact_id)
except Exception as e:
self.show_error(f"Error adding image: {e}")
Birthday Reminders:
Add reminders for important dates:
def setup_birthday_reminder(self):
"""Set up birthday reminder system"""
import datetime
# Add birthday field to database if not exists
conn = self.db.connect()
cursor = conn.cursor()
try:
cursor.execute("ALTER TABLE contacts ADD COLUMN birthday TEXT")
except sqlite3.OperationalError:
# Column already exists
pass
conn.commit()
conn.close()
# Check for upcoming birthdays
self.check_upcoming_birthdays()
def check_upcoming_birthdays(self):
"""Check for birthdays in the next 7 days"""
import datetime
conn = self.db.connect()
cursor = conn.cursor()
today = datetime.date.today()
# Get birthdays in the next 7 days
upcoming_birthdays = []
cursor.execute("SELECT id, first_name, last_name, birthday FROM contacts WHERE birthday IS NOT NULL")
for contact in cursor.fetchall():
if not contact[3]: # Skip if birthday is empty
continue
try:
# Parse stored birthday (format: YYYY-MM-DD)
bday = datetime.datetime.strptime(contact[3], "%Y-%m-%d").date()
# Create this year's birthday
this_year_bday = datetime.date(today.year, bday.month, bday.day)
# If birthday has passed this year, look at next year's birthday
if this_year_bday < today:
this_year_bday = datetime.date(today.year + 1, bday.month, bday.day)
# Calculate days until birthday
days_until = (this_year_bday - today).days
if 0 <= days_until <= 7:
upcoming_birthdays.append({
"id": contact[0],
"name": f"{contact[1]} {contact[2]}",
"birthday": bday.strftime("%B %d"),
"days_until": days_until
})
except ValueError:
# Skip invalid date formats
continue
conn.close()
# Show notification if there are upcoming birthdays
if upcoming_birthdays:
self.show_birthday_notification(upcoming_birthdays)
These advanced features significantly enhance the functionality and user experience of our contact management system, making it a comprehensive solution for personal and professional use.
Deployment and Distribution
Making our application available to users requires proper packaging and distribution.
Packaging the Application:
We’ll use PyInstaller to create a standalone executable:
# setup.py
from setuptools import setup, find_packages
setup(
name="contact_manager",
version="1.0.0",
packages=find_packages(),
install_requires=[
"pillow", # For image processing
],
python_requires=">=3.6",
entry_points={
"console_scripts": [
"contact-manager-cli=contact_manager.cli:main",
],
"gui_scripts": [
"contact-manager=contact_manager.main:main",
],
},
author="Your Name",
author_email="your.email@example.com",
description="A comprehensive contact management system",
keywords="contacts, management, address book",
package_data={
"contact_manager": ["resources/*"],
},
)
Creating Executable Files:
Use PyInstaller to create standalone executables:
# For Windows pyinstaller --name=ContactManager --windowed --icon=resources/icon.ico --add-data "resources;resources" main.py # For Linux pyinstaller --name=ContactManager --windowed --icon=resources/icon.png --add-data "resources:resources" main.py # For macOS pyinstaller --name=ContactManager --windowed --icon=resources/icon.icns --add-data "resources:resources" main.py
System Requirements for End-Users:
- Windows 7/8/10/11, macOS 10.14+, or Linux
- 100MB disk space for application and database
- 2GB RAM recommended
- Screen resolution: 1024×768 or higher
- No additional software required (standalone application)
Cross-Platform Considerations:
To ensure compatibility across platforms:
- Use relative paths for file operations
- Handle platform-specific file separators
- Use appropriate file permissions for each OS
- Test on all target platforms before distribution
- Provide platform-specific installation instructions
Updating Mechanism:
Implement a simple update checker:
def check_for_updates(self):
"""Check for application updates"""
import urllib.request
import json
try:
# Replace with your actual update server URL
update_url = "https://example.com/updates/contact_manager.json"
response = urllib.request.urlopen(update_url, timeout=5)
update_info = json.loads(response.read().decode())
current_version = "1.0.0" # Replace with your version tracking
if update_info["latest_version"] > current_version:
from tkinter import messagebox
result = messagebox.askyesno(
"Update Available",
f"Version {update_info['latest_version']} is available. Would you like to download it now?\n\n"
f"Changes: {update_info['changelog']}"
)
if result:
import webbrowser
webbrowser.open(update_info["download_url"])
except Exception as e:
# Silently fail - updates are not critical
self.log_error(f"Update check failed: {e}")
These deployment strategies ensure that users can easily install and use our contact management system on their preferred platforms.