Enhance PhotoTagger by adding date of birth support in the database and GUI. Update data handling to accommodate new input format, including validation for date selection. Revise identification logic to ensure complete data is saved, improving user experience and data integrity.
This commit is contained in:
parent
52b9d37d8c
commit
2fcd200cd0
408
photo_tagger.py
408
photo_tagger.py
@ -163,8 +163,9 @@ class PhotoTagger:
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
first_name TEXT NOT NULL,
|
||||
last_name TEXT NOT NULL,
|
||||
date_of_birth DATE,
|
||||
created_date DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(first_name, last_name)
|
||||
UNIQUE(first_name, last_name, date_of_birth)
|
||||
)
|
||||
''')
|
||||
|
||||
@ -389,9 +390,9 @@ class PhotoTagger:
|
||||
identify_data_cache['photo_paths'] = {row[0]: {'path': row[1], 'filename': row[2]} for row in cursor.fetchall()}
|
||||
|
||||
# Pre-fetch all people names for dropdown
|
||||
cursor.execute('SELECT first_name, last_name FROM people ORDER BY first_name, last_name')
|
||||
cursor.execute('SELECT first_name, last_name, date_of_birth FROM people ORDER BY first_name, last_name')
|
||||
people = cursor.fetchall()
|
||||
identify_data_cache['people_names'] = [f"{first} {last}".strip() for first, last in people]
|
||||
identify_data_cache['people_names'] = [f"{first} {last}".strip() for first, last, dob in people]
|
||||
|
||||
print(f"✅ Pre-fetched {len(identify_data_cache.get('photo_paths', {}))} photo paths and {len(identify_data_cache.get('people_names', []))} people names")
|
||||
|
||||
@ -425,8 +426,16 @@ class PhotoTagger:
|
||||
nonlocal identified_count
|
||||
saved_count = 0
|
||||
|
||||
for face_id, person_name in face_person_names.items():
|
||||
if person_name.strip():
|
||||
for face_id, person_data in face_person_names.items():
|
||||
# Handle both old format (string) and new format (dict)
|
||||
if isinstance(person_data, dict):
|
||||
person_name = person_data.get('name', '').strip()
|
||||
date_of_birth = person_data.get('date_of_birth', '').strip()
|
||||
else:
|
||||
person_name = person_data.strip() if person_data else ''
|
||||
date_of_birth = ''
|
||||
|
||||
if person_name:
|
||||
try:
|
||||
with self.get_db_connection() as conn:
|
||||
cursor = conn.cursor()
|
||||
@ -439,8 +448,8 @@ class PhotoTagger:
|
||||
first_name = parts[0] if parts else ''
|
||||
last_name = ''
|
||||
|
||||
cursor.execute('INSERT OR IGNORE INTO people (first_name, last_name) VALUES (?, ?)', (first_name, last_name))
|
||||
cursor.execute('SELECT id FROM people WHERE first_name = ? AND last_name = ?', (first_name, last_name))
|
||||
cursor.execute('INSERT OR IGNORE INTO people (first_name, last_name, date_of_birth) VALUES (?, ?, ?)', (first_name, last_name, date_of_birth))
|
||||
cursor.execute('SELECT id FROM people WHERE first_name = ? AND last_name = ? AND date_of_birth = ?', (first_name, last_name, date_of_birth))
|
||||
result = cursor.fetchone()
|
||||
person_id = result[0] if result else None
|
||||
|
||||
@ -477,11 +486,27 @@ class PhotoTagger:
|
||||
if not validate_navigation():
|
||||
return # Cancel close
|
||||
|
||||
# Check if there are pending identifications (faces with names but not yet saved)
|
||||
pending_identifications = {
|
||||
k: v for k, v in face_person_names.items()
|
||||
if v.strip() and (k not in face_status or face_status[k] != 'identified')
|
||||
}
|
||||
# Check if there are pending identifications (faces with complete data but not yet saved)
|
||||
pending_identifications = {}
|
||||
for k, v in face_person_names.items():
|
||||
if k not in face_status or face_status[k] != 'identified':
|
||||
# Handle both old format (string) and new format (dict)
|
||||
if isinstance(v, dict):
|
||||
person_name = v.get('name', '').strip()
|
||||
date_of_birth = v.get('date_of_birth', '').strip()
|
||||
# Check if name has both first and last name
|
||||
if person_name and date_of_birth:
|
||||
# Parse name to check for both first and last name
|
||||
if ',' in person_name:
|
||||
last_name, first_name = person_name.split(',', 1)
|
||||
if last_name.strip() and first_name.strip():
|
||||
pending_identifications[k] = v
|
||||
else:
|
||||
# Single name format - not complete
|
||||
pass
|
||||
else:
|
||||
# Old string format - not complete (missing date of birth)
|
||||
pass
|
||||
|
||||
if pending_identifications:
|
||||
# Ask user if they want to save pending identifications
|
||||
@ -563,6 +588,7 @@ class PhotoTagger:
|
||||
input_frame.grid(row=1, column=0, pady=(10, 0), sticky=(tk.W, tk.E))
|
||||
input_frame.columnconfigure(1, weight=1)
|
||||
input_frame.columnconfigure(3, weight=1)
|
||||
input_frame.columnconfigure(5, weight=1)
|
||||
|
||||
# First name input with dropdown
|
||||
ttk.Label(input_frame, text="First name:").grid(row=0, column=0, sticky=tk.W, padx=(0, 10))
|
||||
@ -574,7 +600,256 @@ class PhotoTagger:
|
||||
ttk.Label(input_frame, text="Last name:").grid(row=0, column=2, sticky=tk.W, padx=(10, 10))
|
||||
last_name_var = tk.StringVar()
|
||||
last_name_combo = ttk.Combobox(input_frame, textvariable=last_name_var, width=12, state="normal")
|
||||
last_name_combo.grid(row=0, column=3, sticky=(tk.W, tk.E), padx=(0, 10))
|
||||
last_name_combo.grid(row=0, column=3, sticky=(tk.W, tk.E), padx=(0, 5))
|
||||
|
||||
# Date of birth input with calendar chooser
|
||||
ttk.Label(input_frame, text="Date of birth:").grid(row=0, column=4, sticky=tk.W, padx=(10, 10))
|
||||
date_of_birth_var = tk.StringVar()
|
||||
|
||||
# Create a frame for the date picker
|
||||
date_frame = ttk.Frame(input_frame)
|
||||
date_frame.grid(row=0, column=5, sticky=(tk.W, tk.E), padx=(0, 10))
|
||||
|
||||
# Date display entry (read-only)
|
||||
date_of_birth_entry = ttk.Entry(date_frame, textvariable=date_of_birth_var, width=12, state='readonly')
|
||||
date_of_birth_entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
|
||||
|
||||
# Calendar button
|
||||
calendar_btn = ttk.Button(date_frame, text="📅", width=3, command=lambda: open_calendar())
|
||||
calendar_btn.pack(side=tk.RIGHT, padx=(5, 0))
|
||||
|
||||
def open_calendar():
|
||||
"""Open a visual calendar dialog to select date of birth"""
|
||||
from datetime import datetime, date, timedelta
|
||||
import calendar
|
||||
|
||||
# Create calendar window
|
||||
calendar_window = tk.Toplevel(root)
|
||||
calendar_window.title("Select Date of Birth")
|
||||
calendar_window.resizable(False, False)
|
||||
calendar_window.transient(root)
|
||||
calendar_window.grab_set()
|
||||
|
||||
# Calculate center position before showing the window
|
||||
window_width = 350
|
||||
window_height = 400
|
||||
screen_width = calendar_window.winfo_screenwidth()
|
||||
screen_height = calendar_window.winfo_screenheight()
|
||||
x = (screen_width // 2) - (window_width // 2)
|
||||
y = (screen_height // 2) - (window_height // 2)
|
||||
|
||||
# Set geometry with center position before showing
|
||||
calendar_window.geometry(f"{window_width}x{window_height}+{x}+{y}")
|
||||
|
||||
# Calendar variables
|
||||
current_date = datetime.now()
|
||||
|
||||
# Check if there's already a date selected
|
||||
existing_date_str = date_of_birth_var.get().strip()
|
||||
if existing_date_str:
|
||||
try:
|
||||
existing_date = datetime.strptime(existing_date_str, '%Y-%m-%d').date()
|
||||
display_year = existing_date.year
|
||||
display_month = existing_date.month
|
||||
selected_date = existing_date
|
||||
except ValueError:
|
||||
# If existing date is invalid, use default
|
||||
display_year = current_date.year - 25
|
||||
display_month = 1
|
||||
selected_date = None
|
||||
else:
|
||||
# Default to 25 years ago
|
||||
display_year = current_date.year - 25
|
||||
display_month = 1
|
||||
selected_date = None
|
||||
|
||||
# Month names
|
||||
month_names = ["January", "February", "March", "April", "May", "June",
|
||||
"July", "August", "September", "October", "November", "December"]
|
||||
|
||||
# Configure custom styles for better visual highlighting
|
||||
style = ttk.Style()
|
||||
|
||||
# Selected date style - bright blue background with white text
|
||||
style.configure("Selected.TButton",
|
||||
background="#0078d4",
|
||||
foreground="white",
|
||||
font=("Arial", 9, "bold"),
|
||||
relief="raised",
|
||||
borderwidth=2)
|
||||
style.map("Selected.TButton",
|
||||
background=[("active", "#106ebe")],
|
||||
relief=[("pressed", "sunken")])
|
||||
|
||||
# Today's date style - orange background
|
||||
style.configure("Today.TButton",
|
||||
background="#ff8c00",
|
||||
foreground="white",
|
||||
font=("Arial", 9, "bold"),
|
||||
relief="raised",
|
||||
borderwidth=1)
|
||||
style.map("Today.TButton",
|
||||
background=[("active", "#e67e00")],
|
||||
relief=[("pressed", "sunken")])
|
||||
|
||||
# Calendar-specific normal button style (don't affect global TButton)
|
||||
style.configure("Calendar.TButton",
|
||||
font=("Arial", 9),
|
||||
relief="flat")
|
||||
style.map("Calendar.TButton",
|
||||
background=[("active", "#e1e1e1")],
|
||||
relief=[("pressed", "sunken")])
|
||||
|
||||
# Main frame
|
||||
main_cal_frame = ttk.Frame(calendar_window, padding="10")
|
||||
main_cal_frame.pack(fill=tk.BOTH, expand=True)
|
||||
|
||||
# Header frame with navigation
|
||||
header_frame = ttk.Frame(main_cal_frame)
|
||||
header_frame.pack(fill=tk.X, pady=(0, 10))
|
||||
|
||||
# Month/Year display and navigation
|
||||
nav_frame = ttk.Frame(header_frame)
|
||||
nav_frame.pack()
|
||||
|
||||
def update_calendar():
|
||||
"""Update the calendar display"""
|
||||
# Clear existing calendar
|
||||
for widget in calendar_frame.winfo_children():
|
||||
widget.destroy()
|
||||
|
||||
# Update header
|
||||
month_year_label.config(text=f"{month_names[display_month-1]} {display_year}")
|
||||
|
||||
# Get calendar data
|
||||
cal = calendar.monthcalendar(display_year, display_month)
|
||||
|
||||
# Day headers
|
||||
day_headers = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
|
||||
for i, day in enumerate(day_headers):
|
||||
label = ttk.Label(calendar_frame, text=day, font=("Arial", 9, "bold"))
|
||||
label.grid(row=0, column=i, padx=1, pady=1, sticky="nsew")
|
||||
|
||||
# Calendar days
|
||||
for week_num, week in enumerate(cal):
|
||||
for day_num, day in enumerate(week):
|
||||
if day == 0:
|
||||
# Empty cell
|
||||
label = ttk.Label(calendar_frame, text="")
|
||||
label.grid(row=week_num+1, column=day_num, padx=1, pady=1, sticky="nsew")
|
||||
else:
|
||||
# Day button
|
||||
def make_day_handler(day_value):
|
||||
def select_day():
|
||||
nonlocal selected_date
|
||||
selected_date = date(display_year, display_month, day_value)
|
||||
# Reset all buttons to normal calendar style
|
||||
for widget in calendar_frame.winfo_children():
|
||||
if isinstance(widget, ttk.Button):
|
||||
widget.config(style="Calendar.TButton")
|
||||
# Highlight selected day with prominent style
|
||||
for widget in calendar_frame.winfo_children():
|
||||
if isinstance(widget, ttk.Button) and widget.cget("text") == str(day_value):
|
||||
widget.config(style="Selected.TButton")
|
||||
return select_day
|
||||
|
||||
day_btn = ttk.Button(calendar_frame, text=str(day),
|
||||
command=make_day_handler(day),
|
||||
width=3, style="Calendar.TButton")
|
||||
day_btn.grid(row=week_num+1, column=day_num, padx=1, pady=1, sticky="nsew")
|
||||
|
||||
# Check if this day should be highlighted
|
||||
is_today = (display_year == current_date.year and
|
||||
display_month == current_date.month and
|
||||
day == current_date.day)
|
||||
is_selected = (selected_date and
|
||||
selected_date.year == display_year and
|
||||
selected_date.month == display_month and
|
||||
selected_date.day == day)
|
||||
|
||||
if is_selected:
|
||||
day_btn.config(style="Selected.TButton")
|
||||
elif is_today:
|
||||
day_btn.config(style="Today.TButton")
|
||||
|
||||
# Navigation functions
|
||||
def prev_year():
|
||||
nonlocal display_year
|
||||
display_year = max(1900, display_year - 1)
|
||||
update_calendar()
|
||||
|
||||
def next_year():
|
||||
nonlocal display_year
|
||||
display_year = min(current_date.year, display_year + 1)
|
||||
update_calendar()
|
||||
|
||||
def prev_month():
|
||||
nonlocal display_month, display_year
|
||||
if display_month > 1:
|
||||
display_month -= 1
|
||||
else:
|
||||
display_month = 12
|
||||
display_year = max(1900, display_year - 1)
|
||||
update_calendar()
|
||||
|
||||
def next_month():
|
||||
nonlocal display_month, display_year
|
||||
if display_month < 12:
|
||||
display_month += 1
|
||||
else:
|
||||
display_month = 1
|
||||
display_year = min(current_date.year, display_year + 1)
|
||||
update_calendar()
|
||||
|
||||
# Navigation buttons
|
||||
prev_year_btn = ttk.Button(nav_frame, text="<<", width=3, command=prev_year)
|
||||
prev_year_btn.pack(side=tk.LEFT, padx=(0, 2))
|
||||
|
||||
prev_month_btn = ttk.Button(nav_frame, text="<", width=3, command=prev_month)
|
||||
prev_month_btn.pack(side=tk.LEFT, padx=(0, 5))
|
||||
|
||||
month_year_label = ttk.Label(nav_frame, text="", font=("Arial", 12, "bold"))
|
||||
month_year_label.pack(side=tk.LEFT, padx=5)
|
||||
|
||||
next_month_btn = ttk.Button(nav_frame, text=">", width=3, command=next_month)
|
||||
next_month_btn.pack(side=tk.LEFT, padx=(5, 2))
|
||||
|
||||
next_year_btn = ttk.Button(nav_frame, text=">>", width=3, command=next_year)
|
||||
next_year_btn.pack(side=tk.LEFT)
|
||||
|
||||
# Calendar grid frame
|
||||
calendar_frame = ttk.Frame(main_cal_frame)
|
||||
calendar_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
|
||||
|
||||
# Configure grid weights
|
||||
for i in range(7):
|
||||
calendar_frame.columnconfigure(i, weight=1)
|
||||
for i in range(7):
|
||||
calendar_frame.rowconfigure(i, weight=1)
|
||||
|
||||
# Buttons frame
|
||||
buttons_frame = ttk.Frame(main_cal_frame)
|
||||
buttons_frame.pack(fill=tk.X)
|
||||
|
||||
def select_date():
|
||||
"""Select the date and close calendar"""
|
||||
if selected_date:
|
||||
date_str = selected_date.strftime('%Y-%m-%d')
|
||||
date_of_birth_var.set(date_str)
|
||||
calendar_window.destroy()
|
||||
else:
|
||||
messagebox.showwarning("No Date Selected", "Please select a date from the calendar.")
|
||||
|
||||
def cancel_selection():
|
||||
"""Cancel date selection"""
|
||||
calendar_window.destroy()
|
||||
|
||||
# Buttons
|
||||
ttk.Button(buttons_frame, text="Select", command=select_date).pack(side=tk.LEFT, padx=(0, 5))
|
||||
ttk.Button(buttons_frame, text="Cancel", command=cancel_selection).pack(side=tk.LEFT)
|
||||
|
||||
# Initialize calendar
|
||||
update_calendar()
|
||||
|
||||
# Define update_similar_faces function first - reusing auto-match display logic
|
||||
def update_similar_faces():
|
||||
@ -747,9 +1022,11 @@ class PhotoTagger:
|
||||
unique_key = f"{current_face_id}_{similar_face_id}"
|
||||
face_selection_states[current_face_id][unique_key] = var.get()
|
||||
|
||||
# Save person name
|
||||
# Save person name and date of birth
|
||||
first_name = first_name_var.get().strip()
|
||||
last_name = last_name_var.get().strip()
|
||||
date_of_birth = date_of_birth_var.get().strip()
|
||||
|
||||
if first_name or last_name:
|
||||
if last_name and first_name:
|
||||
current_name = f"{last_name}, {first_name}"
|
||||
@ -759,7 +1036,11 @@ class PhotoTagger:
|
||||
current_name = first_name
|
||||
else:
|
||||
current_name = ""
|
||||
face_person_names[current_face_id] = current_name
|
||||
# Store both name and date of birth
|
||||
face_person_names[current_face_id] = {
|
||||
'name': current_name,
|
||||
'date_of_birth': date_of_birth
|
||||
}
|
||||
|
||||
# Button commands
|
||||
command = None
|
||||
@ -769,10 +1050,27 @@ class PhotoTagger:
|
||||
nonlocal command, waiting_for_input
|
||||
first_name = first_name_var.get().strip()
|
||||
last_name = last_name_var.get().strip()
|
||||
date_of_birth = date_of_birth_var.get().strip()
|
||||
compare_enabled = compare_var.get()
|
||||
|
||||
if not first_name and not last_name:
|
||||
print("⚠️ Please enter at least a first name or last name before identifying")
|
||||
if not first_name:
|
||||
print("⚠️ Please enter a first name before identifying")
|
||||
return
|
||||
|
||||
if not last_name:
|
||||
print("⚠️ Please enter a last name before identifying")
|
||||
return
|
||||
|
||||
if not date_of_birth:
|
||||
print("⚠️ Please select a date of birth before identifying")
|
||||
return
|
||||
|
||||
# Validate date format (YYYY-MM-DD) - should always be valid from calendar
|
||||
try:
|
||||
from datetime import datetime
|
||||
datetime.strptime(date_of_birth, '%Y-%m-%d')
|
||||
except ValueError:
|
||||
print("⚠️ Invalid date format. Please use the calendar to select a date.")
|
||||
return
|
||||
|
||||
# Combine first and last name properly
|
||||
@ -843,12 +1141,27 @@ class PhotoTagger:
|
||||
if not validate_navigation():
|
||||
return # Cancel quit
|
||||
|
||||
# Check if there are pending identifications (faces with names but not yet saved)
|
||||
pending_identifications = {
|
||||
k: v for k, v in face_person_names.items()
|
||||
if v.strip() and (k not in face_status or face_status[k] != 'identified')
|
||||
}
|
||||
|
||||
# Check if there are pending identifications (faces with complete data but not yet saved)
|
||||
pending_identifications = {}
|
||||
for k, v in face_person_names.items():
|
||||
if k not in face_status or face_status[k] != 'identified':
|
||||
# Handle both old format (string) and new format (dict)
|
||||
if isinstance(v, dict):
|
||||
person_name = v.get('name', '').strip()
|
||||
date_of_birth = v.get('date_of_birth', '').strip()
|
||||
# Check if name has both first and last name
|
||||
if person_name and date_of_birth:
|
||||
# Parse name to check for both first and last name
|
||||
if ',' in person_name:
|
||||
last_name, first_name = person_name.split(',', 1)
|
||||
if last_name.strip() and first_name.strip():
|
||||
pending_identifications[k] = v
|
||||
else:
|
||||
# Single name format - not complete
|
||||
pass
|
||||
else:
|
||||
# Old string format - not complete (missing date of birth)
|
||||
pass
|
||||
|
||||
if pending_identifications:
|
||||
# Ask user if they want to save pending identifications
|
||||
@ -944,10 +1257,11 @@ class PhotoTagger:
|
||||
# Button references moved to bottom control panel
|
||||
|
||||
def update_identify_button_state():
|
||||
"""Enable/disable identify button based on name input"""
|
||||
"""Enable/disable identify button based on first name, last name, and date of birth"""
|
||||
first_name = first_name_var.get().strip()
|
||||
last_name = last_name_var.get().strip()
|
||||
if first_name or last_name:
|
||||
date_of_birth = date_of_birth_var.get().strip()
|
||||
if first_name and last_name and date_of_birth:
|
||||
identify_btn.config(state='normal')
|
||||
else:
|
||||
identify_btn.config(state='disabled')
|
||||
@ -955,6 +1269,7 @@ class PhotoTagger:
|
||||
# Bind name input changes to update button state
|
||||
first_name_var.trace('w', lambda *args: update_identify_button_state())
|
||||
last_name_var.trace('w', lambda *args: update_identify_button_state())
|
||||
date_of_birth_var.trace('w', lambda *args: update_identify_button_state())
|
||||
|
||||
# Handle Enter key
|
||||
def on_enter(event):
|
||||
@ -1186,6 +1501,7 @@ class PhotoTagger:
|
||||
else:
|
||||
first_name_var.set("")
|
||||
last_name_var.set("")
|
||||
date_of_birth_var.set("")
|
||||
|
||||
# Keep compare checkbox state persistent across navigation
|
||||
first_name_combo.focus_set()
|
||||
@ -1263,6 +1579,9 @@ class PhotoTagger:
|
||||
print("⚠️ No more unidentified faces - Next button disabled")
|
||||
continue
|
||||
|
||||
# Clear date of birth field when moving to next face
|
||||
date_of_birth_var.set("")
|
||||
|
||||
update_button_states()
|
||||
update_similar_faces()
|
||||
continue
|
||||
@ -1285,6 +1604,37 @@ class PhotoTagger:
|
||||
print("⚠️ No more unidentified faces - Back button disabled")
|
||||
continue
|
||||
|
||||
# Repopulate fields with saved data when going back
|
||||
current_face_id = original_faces[i][0]
|
||||
if current_face_id in face_person_names:
|
||||
person_data = face_person_names[current_face_id]
|
||||
if isinstance(person_data, dict):
|
||||
person_name = person_data.get('name', '').strip()
|
||||
date_of_birth = person_data.get('date_of_birth', '').strip()
|
||||
|
||||
# Parse "Last, First" format back to separate fields
|
||||
if ', ' in person_name:
|
||||
parts = person_name.split(', ', 1)
|
||||
last_name_var.set(parts[0].strip())
|
||||
first_name_var.set(parts[1].strip())
|
||||
else:
|
||||
# Fallback for single name
|
||||
first_name_var.set(person_name)
|
||||
last_name_var.set("")
|
||||
|
||||
# Repopulate date of birth
|
||||
date_of_birth_var.set(date_of_birth)
|
||||
else:
|
||||
# Old format - clear fields
|
||||
first_name_var.set("")
|
||||
last_name_var.set("")
|
||||
date_of_birth_var.set("")
|
||||
else:
|
||||
# No saved data - clear fields
|
||||
first_name_var.set("")
|
||||
last_name_var.set("")
|
||||
date_of_birth_var.set("")
|
||||
|
||||
update_button_states()
|
||||
update_similar_faces()
|
||||
continue
|
||||
@ -1314,8 +1664,8 @@ class PhotoTagger:
|
||||
first_name = parts[0] if parts else ''
|
||||
last_name = ''
|
||||
|
||||
cursor.execute('INSERT OR IGNORE INTO people (first_name, last_name) VALUES (?, ?)', (first_name, last_name))
|
||||
cursor.execute('SELECT id FROM people WHERE first_name = ? AND last_name = ?', (first_name, last_name))
|
||||
cursor.execute('INSERT OR IGNORE INTO people (first_name, last_name, date_of_birth) VALUES (?, ?, ?)', (first_name, last_name, None))
|
||||
cursor.execute('SELECT id FROM people WHERE first_name = ? AND last_name = ? AND date_of_birth IS NULL', (first_name, last_name))
|
||||
result = cursor.fetchone()
|
||||
person_id = result[0] if result else None
|
||||
|
||||
@ -1362,8 +1712,8 @@ class PhotoTagger:
|
||||
first_name = parts[0] if parts else ''
|
||||
last_name = ''
|
||||
|
||||
cursor.execute('INSERT OR IGNORE INTO people (first_name, last_name) VALUES (?, ?)', (first_name, last_name))
|
||||
cursor.execute('SELECT id FROM people WHERE first_name = ? AND last_name = ?', (first_name, last_name))
|
||||
cursor.execute('INSERT OR IGNORE INTO people (first_name, last_name, date_of_birth) VALUES (?, ?, ?)', (first_name, last_name, None))
|
||||
cursor.execute('SELECT id FROM people WHERE first_name = ? AND last_name = ? AND date_of_birth IS NULL', (first_name, last_name))
|
||||
result = cursor.fetchone()
|
||||
person_id = result[0] if result else None
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user