model的after_save前sanitize html

Rails应用中的HTML净化
本文介绍了一种在Rails应用中实现HTML净化的方法。通过在模型保存后触发净化操作,利用HTML::WhiteListSanitizer确保了内容的安全性。这种方式避免了与ActiveRecord::Base中的sanitize方法冲突。

ActionView::Helpers::SanitizeHelper中有sanitize方法,可以在view 中使用

如果想在model中使用,可以include ActionView::Helpers::SanitizeHelper. 但是会覆盖掉ActiveRecord::Base的sanitize方法

 

还有一个办法:

after_save :sanitize_html

private
  def sanitize_html
    sanitizer = HTML::WhiteListSanitizer.new
    self.body = sanitizer.sanitize(self.body)
  end
 
import tkinter as tk from tkinter import ttk, messagebox, simpledialog import json import os import re class Contact: """联系人类""" def __init__(self, name, phone, email="", address="", region="", company="", title=""): self.name = name self.phone = phone self.email = email self.address = address self.region = region self.company = company self.title = title def to_dict(self): """将联系人对象转换为字典""" return { 'name': self.name, 'phone': self.phone, 'email': self.email, 'address': self.address, 'region': self.region, 'company': self.company, 'title': self.title } @classmethod def from_dict(cls, data): """从字典创建联系人对象""" return cls( data['name'], data['phone'], data.get('email', ''), data.get('address', ''), data.get('region', ''), data.get('company', ''), data.get('title', '') ) def __str__(self): return (f"姓名: {self.name}, 电话: {self.phone}, 邮箱: {self.email}, " f"地址: {self.address}, 地区: {self.region}, 单位: {self.company}, 职务: {self.title}") class AddressBook: """通讯录类""" def __init__(self, filename="address_book.json"): self.filename = filename self.contacts = [] self.load_contacts() def validate_phone(self, phone): """验证电话号码格式""" # 简单的电话号码验证:只包含数字、空格、括号和连字符 pattern = r'^[\d\s\(\)\-]+$' return re.match(pattern, phone) is not None def validate_email(self, email): """验证邮箱格式""" if not email: # 邮箱可选,空值通过验证 return True pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' return re.match(pattern, email) is not None def add_contact(self, name, phone, email="", address="", region="", company="", title=""): """添加联系人""" # 验证电话号码格式 if not self.validate_phone(phone): messagebox.showerror("错误", "电话号码格式不正确!") return False # 验证邮箱格式 if not self.validate_email(email): messagebox.showerror("错误", "邮箱格式不正确!") return False # 检查电话是否已存在 for contact in self.contacts: if contact.phone == phone: messagebox.showerror("错误", f"电话 {phone} 已存在!") return False new_contact = Contact(name, phone, email, address, region, company, title) self.contacts.append(new_contact) self.save_contacts() messagebox.showinfo("成功", f"联系人 {name} 添加成功!") return True def delete_contact(self, identifier): """删除联系人,可通过姓名或电话标识""" for i, contact in enumerate(self.contacts): if contact.name == identifier or contact.phone == identifier: removed = self.contacts.pop(i) self.save_contacts() messagebox.showinfo("成功", f"联系人 {removed.name} 删除成功!") return True messagebox.showerror("错误", f"未找到联系人: {identifier}") return False def search_contact(self, identifier): """查找联系人,可通过姓名、电话、地区、单位等标识""" results = [] for contact in self.contacts: if (identifier.lower() in contact.name.lower() or identifier in contact.phone or identifier.lower() in contact.region.lower() or identifier.lower() in contact.company.lower() or identifier.lower() in contact.title.lower()): results.append(contact) return results def update_contact(self, identifier, name=None, phone=None, email=None, address=None, region=None, company=None, title=None): """更新联系人信息""" for contact in self.contacts: if contact.name == identifier or contact.phone == identifier: # 验证新电话号码格式 if phone and not self.validate_phone(phone): messagebox.showerror("错误", "电话号码格式不正确!") return False # 验证新邮箱格式 if email and not self.validate_email(email): messagebox.showerror("错误", "邮箱格式不正确!") return False if name: contact.name = name if phone: # 检查新电话是否已存在(排除当联系人) if phone != contact.phone and any(c.phone == phone for c in self.contacts): messagebox.showerror("错误", f"电话 {phone} 已存在!") return False contact.phone = phone if email: contact.email = email if address: contact.address = address if region: contact.region = region if company: contact.company = company if title: contact.title = title self.save_contacts() messagebox.showinfo("成功", "联系人信息更新成功!") return True messagebox.showerror("错误", f"未找到联系人: {identifier}") return False def display_all_contacts(self): """显示所有联系人""" return self.contacts def display_contact_details(self, identifier): """显示联系人的详细信息""" results = self.search_contact(identifier) if not results: messagebox.showinfo("提示", f"未找到联系人: {identifier}") return [] return results def get_contacts_by_region(self, region): """按地区筛选联系人""" return [contact for contact in self.contacts if region.lower() in contact.region.lower()] def get_contacts_by_company(self, company): """按单位筛选联系人""" return [contact for contact in self.contacts if company.lower() in contact.company.lower()] def get_contacts_by_title(self, title): """按职务筛选联系人""" return [contact for contact in self.contacts if title.lower() in contact.title.lower()] def save_contacts(self): """保存通讯录到文件""" data = [contact.to_dict() for contact in self.contacts] with open(self.filename, 'w', encoding='utf-8') as f: json.dump(data, f, ensure_ascii=False, indent=2) def load_contacts(self): """从文件加载通讯录""" if os.path.exists(self.filename): try: with open(self.filename, 'r', encoding='utf-8') as f: data = json.load(f) self.contacts = [Contact.from_dict(item) for item in data] except (json.JSONDecodeError, KeyError): messagebox.showwarning("警告", "通讯录文件损坏,将创建新的通讯录") self.contacts = [] class ContactFormDialog(simpledialog.Dialog): """添加/编辑联系人的对话框""" def __init__(self, parent, title, contact=None): self.contact = contact super().__init__(parent, title) def body(self, master): """创建对话框主体""" # 设置样式 style = ttk.Style() style.configure("Form.TLabel", font=("微软雅黑", 10), foreground="#333333") style.configure("Form.TEntry", font=("微软雅黑", 10)) ttk.Label(master, text="姓名:", style="Form.TLabel").grid(row=0, column=0, sticky="w", padx=15, pady=10) ttk.Label(master, text="电话:", style="Form.TLabel").grid(row=1, column=0, sticky="w", padx=15, pady=10) ttk.Label(master, text="邮箱:", style="Form.TLabel").grid(row=2, column=0, sticky="w", padx=15, pady=10) ttk.Label(master, text="地址:", style="Form.TLabel").grid(row=3, column=0, sticky="w", padx=15, pady=10) ttk.Label(master, text="地区:", style="Form.TLabel").grid(row=4, column=0, sticky="w", padx=15, pady=10) ttk.Label(master, text="单位:", style="Form.TLabel").grid(row=5, column=0, sticky="w", padx=15, pady=10) ttk.Label(master, text="职务:", style="Form.TLabel").grid(row=6, column=0, sticky="w", padx=15, pady=10) self.name_var = tk.StringVar() self.phone_var = tk.StringVar() self.email_var = tk.StringVar() self.address_var = tk.StringVar() self.region_var = tk.StringVar() self.company_var = tk.StringVar() self.title_var = tk.StringVar() # 如果正在编辑现有联系人,填充字段 if self.contact: self.name_var.set(self.contact.name) self.phone_var.set(self.contact.phone) self.email_var.set(self.contact.email) self.address_var.set(self.contact.address) self.region_var.set(self.contact.region) self.company_var.set(self.contact.company) self.title_var.set(self.contact.title) self.name_entry = ttk.Entry(master, textvariable=self.name_var, width=30, style="Form.TEntry") self.phone_entry = ttk.Entry(master, textvariable=self.phone_var, width=30, style="Form.TEntry") self.email_entry = ttk.Entry(master, textvariable=self.email_var, width=30, style="Form.TEntry") self.address_entry = ttk.Entry(master, textvariable=self.address_var, width=30, style="Form.TEntry") self.region_entry = ttk.Entry(master, textvariable=self.region_var, width=30, style="Form.TEntry") self.company_entry = ttk.Entry(master, textvariable=self.company_var, width=30, style="Form.TEntry") self.title_entry = ttk.Entry(master, textvariable=self.title_var, width=30, style="Form.TEntry") self.name_entry.grid(row=0, column=1, padx=15, pady=10) self.phone_entry.grid(row=1, column=1, padx=15, pady=10) self.email_entry.grid(row=2, column=1, padx=15, pady=10) self.address_entry.grid(row=3, column=1, padx=15, pady=10) self.region_entry.grid(row=4, column=1, padx=15, pady=10) self.company_entry.grid(row=5, column=1, padx=15, pady=10) self.title_entry.grid(row=6, column=1, padx=15, pady=10) return self.name_entry # 初始焦点 def apply(self): """应用更改""" self.result = ( self.name_var.get().strip(), self.phone_var.get().strip(), self.email_var.get().strip(), self.address_var.get().strip(), self.region_var.get().strip(), self.company_var.get().strip(), self.title_var.get().strip() ) class ScrollableFrame(ttk.Frame): """可滚动的框架""" def __init__(self, container, *args, **kwargs): super().__init__(container, *args, **kwargs) # 创建Canvas和滚动条 self.canvas = tk.Canvas(self, highlightthickness=0, bg='#f8f9fa') scrollbar = ttk.Scrollbar(self, orient="vertical", command=self.canvas.yview) self.scrollable_frame = ttk.Frame(self.canvas) self.scrollable_frame.bind( "<Configure>", lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all")) ) self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw") self.canvas.configure(yscrollcommand=scrollbar.set) self.canvas.pack(side="left", fill="both", expand=True) scrollbar.pack(side="right", fill="y") # 绑定鼠标滚轮事件 self.canvas.bind("<Enter>", self._bind_mousewheel) self.canvas.bind("<Leave>", self._unbind_mousewheel) def _bind_mousewheel(self, event): self.canvas.bind_all("<MouseWheel>", self._on_mousewheel) def _unbind_mousewheel(self, event): self.canvas.unbind_all("<MouseWheel>") def _on_mousewheel(self, event): self.canvas.yview_scroll(int(-1 * (event.delta / 120)), "units") class ContactDetailWindow: """联系人详情窗口""" def __init__(self, parent, contact): self.contact = contact self.window = tk.Toplevel(parent) self.window.title(f"联系人详情 - {contact.name}") self.window.geometry("550x450") self.window.resizable(True, True) self.window.transient(parent) self.window.configure(bg='#f8f9fa') # 设置窗口位置(居中显示) self.window.update_idletasks() x = parent.winfo_x() + (parent.winfo_width() - self.window.winfo_reqwidth()) // 2 y = parent.winfo_y() + (parent.winfo_height() - self.window.winfo_reqheight()) // 2 self.window.geometry(f"+{x}+{y}") # 延迟设置grab_set,确保窗口已经完全初始化 self.window.after(100, self._set_grab) self.create_widgets() def _set_grab(self): """延迟设置窗口焦点捕获""" try: self.window.grab_set() except tk.TclError: # 如果设置失败,忽略错误 pass def create_widgets(self): """创建详情窗口组件""" # 主框架 main_frame = ttk.Frame(self.window, padding=10) main_frame.pack(fill=tk.BOTH, expand=True) # 标题 title_frame = ttk.Frame(main_frame) title_frame.pack(fill=tk.X, pady=(0, 10)) # 使用图标和标题 title_icon = ttk.Label(title_frame, text="👤", font=("微软雅黑", 16)) title_icon.pack(side=tk.LEFT, padx=(0, 10)) title_label = ttk.Label(title_frame, text=self.contact.name, font=("微软雅黑", 18, "bold"), foreground="#2c3e50") title_label.pack(side=tk.LEFT) # 创建可滚动的详情框架 scrollable_frame = ScrollableFrame(main_frame) scrollable_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) # 详情内容框架 detail_frame = ttk.Frame(scrollable_frame.scrollable_frame, padding=20) detail_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) # 创建详情字段 fields = [ ("📞 电话", self.contact.phone, "#3498db"), ("📧 邮箱", self.contact.email if self.contact.email else "未填写", "#e74c3c"), ("🏠 地址", self.contact.address if self.contact.address else "未填写", "#9b59b6"), ("🌍 地区", self.contact.region if self.contact.region else "未填写", "#2ecc71"), ("🏢 单位", self.contact.company if self.contact.company else "未填写", "#f39c12"), ("💼 职务", self.contact.title if self.contact.title else "未填写", "#1abc9c") ] # 使用卡片式布局 for i, (label, value, color) in enumerate(fields): # 创建卡片框架 card_frame = ttk.Frame(detail_frame, relief="solid", borderwidth=1) card_frame.pack(fill=tk.X, pady=8, padx=5) card_frame.configure(style="Card.TFrame") # 标签 lbl = ttk.Label(card_frame, text=label, font=("微软雅黑", 11, "bold"), foreground=color) lbl.pack(anchor="w", padx=15, pady=(10, 5)) # 值 val_lbl = ttk.Label(card_frame, text=value, font=("微软雅黑", 10), foreground="#2c3e50", wraplength=450, justify="left") val_lbl.pack(anchor="w", padx=15, pady=(0, 10)) # 按钮框架 button_frame = ttk.Frame(main_frame) button_frame.pack(fill=tk.X, pady=(10, 0)) # 关闭按钮 close_btn = ttk.Button(button_frame, text="关闭", command=self.window.destroy, style="Accent.TButton") close_btn.pack(side=tk.RIGHT, padx=5) class AddressBookGUI: """通讯录GUI界面""" def __init__(self, root): self.root = root self.root.title("电子通讯录管理系统") self.root.geometry("1000x700") self.root.configure(bg='#f8f9fa') # 设置应用图标(如果有的话) try: self.root.iconbitmap("address_book.ico") except: pass # 创建通讯录实例 self.address_book = AddressBook() # 设置样式 self.setup_styles() # 创建界面 self.create_widgets() # 刷新联系人列表 self.refresh_contact_list() def setup_styles(self): """设置界面样式""" style = ttk.Style() # 尝试设置现代化主题 try: style.theme_use('clam') except: pass # 配置自定义样式 style.configure("Title.TLabel", font=("微软雅黑", 20, "bold"), background='#f8f9fa', foreground='#2c3e50') style.configure("Subtitle.TLabel", font=("微软雅黑", 11), background='#f8f9fa', foreground='#7f8c8d') style.configure("Custom.TButton", font=("微软雅黑", 10), padding=(12, 8), background='#ecf0f1', foreground='#2c3e50', borderwidth=0, focuscolor='none') style.configure("Accent.TButton", font=("微软雅黑", 10, "bold"), background='#3498db', foreground='white', padding=(12, 8), borderwidth=0, focuscolor='none') style.configure("Search.TButton", font=("微软雅黑", 9), padding=(10, 6), borderwidth=0) style.configure("Treeview", font=("微软雅黑", 10), rowheight=30, background='white', fieldbackground='white', borderwidth=0) style.configure("Treeview.Heading", font=("微软雅黑", 11, "bold"), background='#34495e', foreground='white', borderwidth=0, relief='flat') style.map("Treeview", background=[('selected', '#3498db')], foreground=[('selected', 'white')]) style.map("Accent.TButton", background=[('active', '#2980b9')]) style.map("Custom.TButton", background=[('active', '#d5dbdb')]) style.configure("Card.TFrame", background='white', relief='flat', borderwidth=1) style.configure("Search.TEntry", font=("微软雅黑", 10), padding=(10, 8)) def create_widgets(self): """创建界面组件""" # 标题区域 header_frame = ttk.Frame(self.root, style="Title.TFrame") header_frame.pack(fill=tk.X, padx=0, pady=0) header_frame.configure(style="Header.TFrame") # 渐变背景(模拟) title_container = ttk.Frame(header_frame, style="Title.TFrame") title_container.pack(fill=tk.X, padx=0, pady=0, ipady=20) title_label = ttk.Label(title_container, text="📒 电子通讯录管理系统", style="Title.TLabel") title_label.pack(pady=5) subtitle_label = ttk.Label(title_container, text="高效管理您的联系人信息", style="Subtitle.TLabel") subtitle_label.pack(pady=(0, 10)) # 搜索和操作区域 action_frame = ttk.Frame(self.root, style="Card.TFrame") action_frame.pack(fill=tk.X, padx=20, pady=15) # 搜索框 search_container = ttk.Frame(action_frame) search_container.pack(fill=tk.X, padx=15, pady=15) ttk.Label(search_container, text="搜索联系人:", font=("微软雅黑", 10, "bold"), foreground="#2c3e50").pack(side=tk.LEFT, padx=(0, 10)) self.search_var = tk.StringVar() self.search_entry = ttk.Entry(search_container, textvariable=self.search_var, width=30, font=("微软雅黑", 10), style="Search.TEntry") self.search_entry.pack(side=tk.LEFT, padx=(0, 10)) self.search_entry.bind("<Return>", self.on_search) ttk.Button(search_container, text="搜索", command=self.on_search, style="Search.TButton").pack(side=tk.LEFT, padx=(0, 10)) ttk.Button(search_container, text="显示全部", command=self.refresh_contact_list, style="Search.TButton").pack( side=tk.LEFT) # 主要操作按钮 button_container = ttk.Frame(action_frame) button_container.pack(fill=tk.X, padx=15, pady=(0, 15)) ttk.Button(button_container, text="➕ 添加联系人", command=self.add_contact, style="Accent.TButton").pack( side=tk.LEFT, padx=(0, 10)) ttk.Button(button_container, text="✏️ 编辑联系人", command=self.edit_contact, style="Custom.TButton").pack( side=tk.LEFT, padx=(0, 10)) ttk.Button(button_container, text="🗑️ 删除联系人", command=self.delete_contact, style="Custom.TButton").pack( side=tk.LEFT, padx=(0, 10)) ttk.Button(button_container, text="👁️ 查看详情", command=self.view_details, style="Custom.TButton").pack( side=tk.LEFT, padx=(0, 10)) # 筛选按钮区域 filter_frame = ttk.LabelFrame(button_container, text="筛选", padding=8) filter_frame.pack(side=tk.RIGHT) ttk.Button(filter_frame, text="🌍 按地区", command=self.filter_by_region, style="Search.TButton").pack( side=tk.LEFT, padx=(0, 5)) ttk.Button(filter_frame, text="🏢 按单位", command=self.filter_by_company, style="Search.TButton").pack( side=tk.LEFT, padx=(0, 5)) ttk.Button(filter_frame, text="💼 按职务", command=self.filter_by_title, style="Search.TButton").pack( side=tk.LEFT) # 联系人列表框架 list_frame = ttk.LabelFrame(self.root, text="联系人列表", padding=15) list_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=(0, 15)) columns = ("姓名", "电话", "邮箱", "地区", "单位", "职务") self.contact_tree = ttk.Treeview(list_frame, columns=columns, show="headings", height=15) # 设置列标题和宽度 column_widths = {"姓名": 120, "电话": 140, "邮箱": 180, "地区": 120, "单位": 150, "职务": 120} for col in columns: self.contact_tree.heading(col, text=col) self.contact_tree.column(col, width=column_widths[col]) # 添加滚动条 scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.contact_tree.yview) self.contact_tree.configure(yscrollcommand=scrollbar.set) self.contact_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) # 绑定双击事件 self.contact_tree.bind("<Double-1>", self.on_double_click) # 状态栏 self.status_frame = ttk.Frame(self.root, relief=tk.SUNKEN) self.status_frame.pack(fill=tk.X, side=tk.BOTTOM) self.status_label = ttk.Label(self.status_frame, text=f"共有 {len(self.address_book.contacts)} 个联系人", font=("微软雅黑", 9), foreground="#7f8c8d") self.status_label.pack(side=tk.LEFT, padx=10, pady=5) def refresh_contact_list(self, contacts=None): """刷新联系人列表""" # 清空现有列表 for item in self.contact_tree.get_children(): self.contact_tree.delete(item) # 获取联系人列表 if contacts is None: contacts = self.address_book.display_all_contacts() # 填充列表 for contact in contacts: self.contact_tree.insert("", tk.END, values=( contact.name, contact.phone, contact.email, contact.region, contact.company, contact.title )) # 更新状态栏 self.status_label.config( text=f"共有 {len(self.address_book.contacts)} 个联系人 | 当显示 {len(contacts)} 个联系人") def on_search(self, event=None): """搜索联系人""" keyword = self.search_var.get().strip() if not keyword: messagebox.showwarning("警告", "请输入搜索关键词") return results = self.address_book.search_contact(keyword) if results: self.refresh_contact_list(results) messagebox.showinfo("搜索结果", f"找到 {len(results)} 个匹配的联系人") else: messagebox.showinfo("搜索结果", "未找到匹配的联系人") def add_contact(self): """添加联系人""" dialog = ContactFormDialog(self.root, "添加联系人") if dialog.result: name, phone, email, address, region, company, title = dialog.result if not name or not phone: messagebox.showwarning("警告", "姓名和电话为必填项") return if self.address_book.add_contact(name, phone, email, address, region, company, title): self.refresh_contact_list() def edit_contact(self): """编辑联系人""" selection = self.contact_tree.selection() if not selection: messagebox.showwarning("警告", "请先选择一个联系人") return # 获取选中的联系人信息 item = selection[0] values = self.contact_tree.item(item, "values") name = values[0] # 查找联系人对象 contact = None for c in self.address_book.contacts: if c.name == name: contact = c break if not contact: messagebox.showerror("错误", "未找到选中的联系人") return # 打开编辑对话框 dialog = ContactFormDialog(self.root, "编辑联系人", contact) if dialog.result: new_name, new_phone, new_email, new_address, new_region, new_company, new_title = dialog.result if not new_name or not new_phone: messagebox.showwarning("警告", "姓名和电话为必填项") return if self.address_book.update_contact( name, new_name, new_phone, new_email, new_address, new_region, new_company, new_title ): self.refresh_contact_list() def delete_contact(self): """删除联系人""" selection = self.contact_tree.selection() if not selection: messagebox.showwarning("警告", "请先选择一个联系人") return # 获取选中的联系人信息 item = selection[0] values = self.contact_tree.item(item, "values") name = values[0] # 确认删除 if messagebox.askyesno("确认删除", f"确定要删除联系人 {name} 吗?"): if self.address_book.delete_contact(name): self.refresh_contact_list() def view_details(self): """查看联系人详情""" selection = self.contact_tree.selection() if not selection: messagebox.showwarning("警告", "请先选择一个联系人") return # 获取选中的联系人信息 item = selection[0] values = self.contact_tree.item(item, "values") name = values[0] # 查找联系人对象 contact = None for c in self.address_book.contacts: if c.name == name: contact = c break if not contact: messagebox.showerror("错误", "未找到选中的联系人") return # 使用新的详情窗口类 ContactDetailWindow(self.root, contact) def on_double_click(self, event): """双击联系人查看详情""" self.view_details() def filter_by_region(self): """按地区筛选联系人""" region = simpledialog.askstring("按地区筛选", "请输入地区:") if region: results = self.address_book.get_contacts_by_region(region) if results: self.refresh_contact_list(results) messagebox.showinfo("筛选结果", f"在 {region} 地区找到 {len(results)} 个联系人") else: messagebox.showinfo("筛选结果", f"在 {region} 地区没有找到联系人") def filter_by_company(self): """按单位筛选联系人""" company = simpledialog.askstring("按单位筛选", "请输入单位:") if company: results = self.address_book.get_contacts_by_company(company) if results: self.refresh_contact_list(results) messagebox.showinfo("筛选结果", f"在 {company} 单位找到 {len(results)} 个联系人") else: messagebox.showinfo("筛选结果", f"在 {company} 单位没有找到联系人") def filter_by_title(self): """按职务筛选联系人""" title = simpledialog.askstring("按职务筛选", "请输入职务:") if title: results = self.address_book.get_contacts_by_title(title) if results: self.refresh_contact_list(results) messagebox.showinfo("筛选结果", f"职务为 {title} 的联系人有 {len(results)} 个") else: messagebox.showinfo("筛选结果", f"没有找到职务为 {title} 的联系人") def main(): """主函数""" root = tk.Tk() app = AddressBookGUI(root) root.mainloop() if __name__ == "__main__": main()
最新发布
11-19
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值