Linux

Contact Management System Project in Python

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:

  1. Create a dedicated project directory: mkdir contact_management_system
  2. Set up a virtual environment: python -m venv venv
  3. Activate the environment:
    • Windows: venv\Scripts\activate
    • Linux/Mac: source venv/bin/activate
  4. 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:

  1. Core Features: CRUD operations (Create, Read, Update, Delete)
  2. Secondary Features: Search, filter, sort contacts
  3. 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:

  1. A navigation sidebar on the left
  2. A content area on the right that changes based on the selected function
  3. 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:

  1. Text-based input and output instead of graphical forms
  2. Command-line arguments for direct operation
  3. Interactive mode for menu-driven operation
  4. Streamlined validation with text prompts
  5. Batch operations through shell scripts
  6. 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:

  1. Use relative paths for file operations
  2. Handle platform-specific file separators
  3. Use appropriate file permissions for each OS
  4. Test on all target platforms before distribution
  5. 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.

VPS Manage Service Offer
If you don’t have time to do all of this stuff, or if this is not your area of expertise, we offer a service to do “VPS Manage Service Offer”, starting from $10 (Paypal payment). Please contact us to get the best deal!

r00t

r00t is an experienced Linux enthusiast and technical writer with a passion for open-source software. With years of hands-on experience in various Linux distributions, r00t has developed a deep understanding of the Linux ecosystem and its powerful tools. He holds certifications in SCE and has contributed to several open-source projects. r00t is dedicated to sharing her knowledge and expertise through well-researched and informative articles, helping others navigate the world of Linux with confidence.
Back to top button