class EditClientDetailModule(QDialog, Ui_Dialog): def __init__(self, autoid=None, parent=None): super(EditClientDetailModule, self).__init__(parent) self.setupUi(self) if '54' not in user.powers: self.close() if user.powers['54'] == 0: self.close() self.power = '{:03b}'.format(user.powers['54']) if self.power[1] == '0': self.pushButton_accept.setVisible(False) self.pushButton_cancel.setVisible(False) self.autoid = autoid self.ori_detail = dict() self.new_detail = dict() self.current_img = object self.current_page = object self.province_list = [] self.city_list = [] self.county_list = [] self.province_id = '' self.city_id = '' self.county_id = '' self.qyname = '' self.SC = SaleController() self.IC = ImageController() self.treeWidget_imagenamelist.hideColumn(0) self.treeWidget_imagenamelist.hideColumn(1) # 自动缩放 self.label_image.setScaledContents(True) return_row = ('parentid', 'autoid', 'kindname') condition_key = {'autoid', 'kindname', 'inputcode'} treeheader_name = ['parentid', '编号', '名称'] condition = {'kind': 4} self.lineEdit_tstkind.setup('Treestructure', return_row, condition_key, treeheader_name, condition, treewidth=300) self.get_detail() self.get_province() if len(self.ori_detail): self.get_city(self.ori_detail['province']) self.get_county(self.ori_detail['city']) def get_detail(self): if self.autoid is None: return key_dict = {'autoid': self.autoid} res = self.SC.get_data(0, False, *VALUE_TUPLE_SUP, **key_dict) if not len(res): return self.ori_detail = res[0] self.lineEdit_clientid.setText(self.ori_detail['clientid']) self.lineEdit_clientname.setText(self.ori_detail['clientname']) self.lineEdit_inputcode.setText(self.ori_detail['inputcode']) self.lineEdit_tstkind.setText( str(self.ori_detail['tstkind']) + ' ' + self.ori_detail['tstname']) self.lineEdit_address.setText(self.ori_detail['address']) self.lineEdit_postzip.setText(self.ori_detail['postzip']) self.lineEdit_charger.setText(self.ori_detail['charger']) self.lineEdit_telno.setText(self.ori_detail['telno']) self.lineEdit_chargertitle.setText(self.ori_detail['chargertitle']) self.lineEdit_email.setText(self.ori_detail['email']) self.lineEdit_bankname.setText(self.ori_detail['bankname']) self.lineEdit_bankaccount.setText(self.ori_detail['bankaccount']) self.lineEdit_taxno.setText(self.ori_detail['taxno']) self.plainTextEdit_remark.setPlainText(self.ori_detail['remark']) self.comboBox_kind.setCurrentIndex(self.ori_detail['kind']) self.lineEdit_unitcode.setText(self.ori_detail['unitcode']) def get_province(self): condition = {'itemtype': 1} self.province_list = self.SC.get_data(5, False, *VALUE_TUPLE_RI, **condition) self.comboBox_province.addItems( self.province_list.values_list('itemname', flat=True)) if not len(self.ori_detail) or self.ori_detail['province'] == '': return self.comboBox_province.setCurrentText(self.ori_detail['province']) def get_city(self, province_name=''): self.comboBox_city.clear() condition = {'itemtype': 1, 'itemname': province_name} res = self.SC.get_data(5, True, *VALUE_TUPLE_II, **condition) if not len(res): return condition_city = {'itemtype': 2, 'parentitemid': res[0]} self.city_list = self.SC.get_data(5, False, *VALUE_TUPLE_RI, **condition_city) self.comboBox_city.addItems( self.city_list.values_list('itemname', flat=True)) if not len(self.ori_detail) or self.ori_detail['city'] == '': return if self.comboBox_city.findText(self.ori_detail['city'], Qt.MatchExactly): self.comboBox_city.setCurrentText(self.ori_detail['city']) def get_county(self, county_name=''): self.comboBox_county.clear() condition = {'itemtype': 2, 'itemname': county_name} res = self.SC.get_data(5, True, *VALUE_TUPLE_II, **condition) if not len(res): return condition_city = {'itemtype': 3, 'parentitemid': res[0]} self.county_list = self.SC.get_data(5, False, *VALUE_TUPLE_RI, **condition_city) self.comboBox_county.addItems( self.county_list.values_list('itemname', flat=True)) if not len(self.ori_detail) or self.ori_detail['county'] == '': return if self.comboBox_county.findText(self.ori_detail['county'], Qt.MatchExactly): self.comboBox_county.setCurrentText(self.ori_detail['county']) def get_image(self): self.treeWidget_imagenamelist.clear() if self.autoid is None: return key_dict = {'scid': self.autoid, 'kind': 0} res = self.IC.get_data(0, False, *VALUE_TUPLE_IMRELA, **key_dict) for item in res: qtreeitem = QTreeWidgetItem(self.treeWidget_imagenamelist) qtreeitem.setText(0, str(item['autoid'])) qtreeitem.setText(1, str(item['imageid'])) qtreeitem.setText(2, item['title']) qtreeitem.setText(3, item['creatorid'] + item['creatorname']) qtreeitem.setText(4, str(item['createdate'])) @pyqtSlot(QTreeWidgetItem, int) def on_treeWidget_imagenamelist_itemDoubleClicked(self, qtreeitem, p_int): img_id = int(qtreeitem.text(1)) key_dict = {'autoid': img_id} res = self.IC.get_data(1, False, *VALUE_TUPLE_IM, **key_dict) if not len(res): return ext = res[0]['ext'] image = res[0]['img'] if ext.lower() == 'pdf': self.comboBox_jumpto.setVisible(True) self.pushButton_prepage.setVisible(True) self.pushButton_nextpage.setVisible(True) self.current_img = fitz.Document(stream=image, filetype='pdf') page_count = self.current_img.pageCount page_list = [] self.comboBox_jumpto.clear() for i in range(1, page_count + 1): page_list.append('第' + str(i) + '页') self.comboBox_jumpto.addItems(page_list) self.current_page = self.current_img.loadPage(0) else: self.comboBox_jumpto.setVisible(False) self.pushButton_prepage.setVisible(False) self.pushButton_nextpage.setVisible(False) img = QImage.fromData(image) self.current_img = QPixmap.fromImage(img) self.label_image.setPixmap(self.current_img) # 默认放大为3被,同时自动调用on_horizontalSlider_zoom_valueChanged self.horizontalSlider_zoom.setValue(30) @pyqtSlot(int) def on_horizontalSlider_zoom_valueChanged(self, p_int): try: self.label_zoom.setText(str(p_int * 10) + '%') # 把当前页面转为QPixmap,并缩放为p_int/10 cover = render_pdf_page(self.current_page, p_int / 10, p_int / 10) self.label_image.setPixmap(cover) except (ValueError, AttributeError): size = self.current_img.size() new_pixmap = self.current_img.scaled(size.width() * p_int / 10, size.height() * p_int / 10) self.label_image.setPixmap(new_pixmap) @pyqtSlot(int) def on_comboBox_jumpto_currentIndexChanged(self, p_int): try: self.current_page = self.current_img.loadPage(p_int) self.on_horizontalSlider_zoom_valueChanged( self.horizontalSlider_zoom.value()) except (AttributeError, ValueError): pass @pyqtSlot() def on_pushButton_prepage_clicked(self): index = self.comboBox_jumpto.currentIndex() if index - 1 >= 0: try: self.current_page = self.current_img.loadPage(index - 1) self.comboBox_jumpto.setCurrentIndex(index - 1) except (AttributeError, ValueError): pass @pyqtSlot() def on_pushButton_nextpage_clicked(self): index = self.comboBox_jumpto.currentIndex() if index + 1 < self.comboBox_jumpto.count(): try: self.current_page = self.current_img.loadPage(index + 1) self.comboBox_jumpto.setCurrentIndex(index + 1) except (AttributeError, ValueError): pass @pyqtSlot(QPoint) def on_treeWidget_imagenamelist_customContextMenuRequested(self, pos): if self.power[1] == '0': return # 返回调用者的对象 sender_widget = self.sender() menu = QMenu() button1 = menu.addAction("新增图片") button2 = menu.addAction("修改图片") button3 = menu.addAction("删除图片") global_pos = sender_widget.mapToGlobal(pos) action = menu.exec(global_pos) res = "rollback" # 增加 if action == button1: img_names, img_type = QFileDialog.getOpenFileNames( self, "打开图片", os.path.expanduser("~") + "\Desktop", "*.jpg;;*.png;;*.bmp;;*.gif;;*.pdf;;All Files(*)") for item in img_names: imagename_no_ext = item.split("/")[-1] image_ext = item.split(".")[1] if image_ext.lower() not in ("jpg", "png", "bmp", "gif", "pdf"): continue fp = open(item, 'rb') with fp: image_byte = fp.read() fp.close() imagedetail = {'img': image_byte, 'ext': image_ext} reladetail = { 'kind': 1, 'scid': self.autoid, 'title': imagename_no_ext, 'creatorid': user.user_id, 'creatorname': user.user_name, 'createdate': user.now_date } res = self.IC.update_images(reladetail, imagedetail) # 修改 elif action == button2: rela_id = self.treeWidget_imagenamelist.currentItem().text(0) img_id = self.treeWidget_imagenamelist.currentItem().text(1) img_name, img_type = QFileDialog.getOpenFileName( self, "打开图片", os.path.expanduser("~") + "\Desktop", "*.jpg;;*.png;;*.bmp;;*.gif;;*.pdf;;All Files(*)") imagename_no_ext = img_name.split("/")[-1] image_ext = img_name.split(".")[1] if image_ext.lower() in ("jpg", "png", "bmp", "gif", "pdf"): fp = open(img_name, 'rb') with fp: image_byte = fp.read() fp.close() imagedetail = {'img': image_byte, 'ext': image_ext} reladetail = { 'title': imagename_no_ext, 'creatorid': user.user_id, 'creatorname': user.user_name, 'createdate': user.now_date } res = self.IC.update_images(reladetail, imagedetail, rela_id, img_id) # 删除 elif action == button3: select_item = sender_widget.selectedItems() rela_id = [] img_id = [] for item in select_item: rela_id.append(item.text(0)) img_id.append(item.text(1)) res = self.IC.delete_images(rela_id, img_id) if res == "accept": self.get_image() @pyqtSlot(str) def on_lineEdit_clientid_textChanged(self, p_str): try: if p_str != self.ori_detail['clientid']: self.new_detail['clientid'] = p_str else: try: del self.new_detail['clientid'] except KeyError: pass except KeyError: self.new_detail['clientid'] = p_str @pyqtSlot(bool, str) def on_pushButton_warrantor_signChanged(self, p_bool, p_str): id, name = p_str.split(' ') if p_bool else ('', '') try: if id != self.ori_detail['warrantorid'] or name != \ self.ori_detail[ 'warrantorname']: self.new_detail['warrantorid'] = id self.new_detail['warrantorname'] = name else: try: del self.new_detail['warrantorid'] del self.new_detail['warrantorname'] except KeyError: pass except KeyError: self.new_detail['warrantorid'] = id self.new_detail['warrantorname'] = name @pyqtSlot(str) def on_lineEdit_clientname_textChanged(self, p_str): self.qyname = p_str self.lineEdit_inputcode.setText(Inputcode.make_inputcode(p_str)) try: if p_str != self.ori_detail['clientname']: self.new_detail['clientname'] = p_str else: try: del self.new_detail['clientname'] except KeyError: pass except KeyError: self.new_detail['clientname'] = p_str @pyqtSlot(int) def on_comboBox_kind_currentIndexChanged(self, p_int): try: if p_int != self.ori_detail['kind']: self.new_detail['kind'] = p_int else: try: del self.new_detail['kind'] except KeyError: pass except KeyError: self.new_detail['kind'] = p_int @pyqtSlot(str) def on_comboBox_province_currentTextChanged(self, p_str): for item in self.province_list: if item['itemname'] == p_str: self.province_id = item['itemid'] break try: if p_str != self.ori_detail['province']: self.get_city(p_str) self.new_detail['province'] = p_str else: try: del self.new_detail['province'] except KeyError: pass except KeyError: self.new_detail['province'] = p_str @pyqtSlot(str) def on_comboBox_city_currentTextChanged(self, p_str): for item in self.city_list: if item['itemname'] == p_str: self.city_id = item['itemid'] break try: if p_str != self.ori_detail['city']: self.new_detail['city'] = p_str self.get_county(p_str) else: try: del self.new_detail['city'] except KeyError: pass except KeyError: self.new_detail['city'] = p_str @pyqtSlot(str) def on_comboBox_county_currentTextChanged(self, p_str): for item in self.county_list: if item['itemname'] == p_str: self.county_id = item['itemid'] break try: if p_str != self.ori_detail['county']: self.new_detail['county'] = p_str else: try: del self.new_detail['county'] except KeyError: pass except KeyError: self.new_detail['county'] = p_str @pyqtSlot(str) def on_lineEdit_unitcode_textChanged(self, p_str): try: if p_str != self.ori_detail['unitcode']: self.new_detail['unitcode'] = p_str else: try: del self.new_detail['unitcode'] except KeyError: pass except KeyError: self.new_detail['unitcode'] = p_str @pyqtSlot(str) def on_lineEdit_tstkind_textChanged(self, p_str): if len(p_str.split(' ')) != 2 and p_str != '': return id, name = p_str.split(' ') if p_str != '' else ('', '') try: if id != self.ori_detail['tstkind'] or name != \ self.ori_detail['tstname']: self.new_detail['tstkind'] = id self.new_detail['tstname'] = name else: try: del self.new_detail['tstkind'] del self.new_detail['tstname'] except KeyError: pass except KeyError: self.new_detail['tstkind'] = id self.new_detail['tstname'] = name @pyqtSlot(str) def on_lineEdit_inputcode_textChanged(self, p_str): try: if p_str != self.ori_detail['inputcode']: self.new_detail['inputcode'] = p_str else: try: del self.new_detail['inputcode'] except KeyError: pass except KeyError: self.new_detail['inputcode'] = p_str @pyqtSlot(str) def on_lineEdit_address_textChanged(self, p_str): try: if p_str != self.ori_detail['address']: self.new_detail['address'] = p_str else: try: del self.new_detail['address'] except KeyError: pass except KeyError: self.new_detail['address'] = p_str @pyqtSlot(str) def on_lineEdit_postzip_textChanged(self, p_str): try: if p_str != self.ori_detail['postzip']: self.new_detail['postzip'] = p_str else: try: del self.new_detail['postzip'] except KeyError: pass except KeyError: self.new_detail['postzip'] = p_str @pyqtSlot(str) def on_lineEdit_charger_textChanged(self, p_str): try: if p_str != self.ori_detail['charger']: self.new_detail['charger'] = p_str else: try: del self.new_detail['charger'] except KeyError: pass except KeyError: self.new_detail['charger'] = p_str @pyqtSlot(str) def on_lineEdit_telno_textChanged(self, p_str): try: if p_str != self.ori_detail['telno']: self.new_detail['telno'] = p_str else: try: del self.new_detail['telno'] except KeyError: pass except KeyError: self.new_detail['telno'] = p_str @pyqtSlot(str) def on_lineEdit_chargertitle_textChanged(self, p_str): try: if p_str != self.ori_detail['chargertitle']: self.new_detail['chargertitle'] = p_str else: try: del self.new_detail['chargertitle'] except KeyError: pass except KeyError: self.new_detail['chargertitle'] = p_str @pyqtSlot(str) def on_lineEdit_email_textChanged(self, p_str): try: if p_str != self.ori_detail['email']: self.new_detail['email'] = p_str else: try: del self.new_detail['email'] except KeyError: pass except KeyError: self.new_detail['email'] = p_str @pyqtSlot(str) def on_lineEdit_bankname_textChanged(self, p_str): try: if p_str != self.ori_detail['bankname']: self.new_detail['bankname'] = p_str else: try: del self.new_detail['bankname'] except KeyError: pass except KeyError: self.new_detail['bankname'] = p_str @pyqtSlot(str) def on_lineEdit_bankaccount_textChanged(self, p_str): try: if p_str != self.ori_detail['bankaccount']: self.new_detail['bankaccount'] = p_str else: try: del self.new_detail['bankaccount'] except KeyError: pass except KeyError: self.new_detail['bankaccount'] = p_str @pyqtSlot(str) def on_lineEdit_taxno_textChanged(self, p_str): try: if p_str != self.ori_detail['taxno']: self.new_detail['taxno'] = p_str else: try: del self.new_detail['taxno'] except KeyError: pass except KeyError: self.new_detail['taxno'] = p_str @pyqtSlot() def on_plainTextEdit_remark_textChanged(self): p_str = self.plainTextEdit_remark.toPlainText() try: if p_str != self.ori_detail['remark']: self.new_detail['remark'] = p_str else: try: del self.new_detail['remark'] except KeyError: pass except KeyError: self.new_detail['remark'] = p_str @pyqtSlot() def on_pushButton_get_unitcode_clicked(self): detail = SelectClientCodeModule(self.province_id, self.city_id, self.county_id, self.qyname, self) detail.selected.connect(self.set_clientname_and_unitcode) detail.show() def set_clientname_and_unitcode(self, p_str_1, p_str_2): self.lineEdit_clientname.setText(p_str_1) self.lineEdit_unitcode.setText(p_str_2) @pyqtSlot() def on_pushButton_accept_clicked(self): if not len(self.new_detail): return condition = dict() if self.autoid is not None: condition['autoid'] = self.autoid self.SC.update_data(0, condition, **self.new_detail) self.accept() @pyqtSlot() def on_pushButton_cancel_clicked(self): self.close() @pyqtSlot(int) def on_tabWidget_currentChanged(self, p_int): if p_int == 0: self.get_detail() elif p_int == 1: self.get_image() else: pass
class ClientListModule(QWidget, Ui_Form): def __init__(self, parent=None): super(ClientListModule, self).__init__(parent) self.setupUi(self) if '54' not in user.powers: self.close() if user.powers['54'] == 0: self.close() self.power = '{:03b}'.format(user.powers['54']) self.SC = SaleController() self.IC = ImageController() self.kind_list = [] self.curremt_kind = 0 self.treeWidget_kind.hideColumn(1) self.treeWidget_detail.hideColumn(0) self.get_kind_list() self.get_client_list() def get_kind_list(self): self.treeWidget_kind.clear() key_dict = {'kind': 4} self.kind_list = self.SC.get_data(4, False, *VALUES_TUPLE_KIND, **key_dict) # if not len(self.kind_list): # return self.set_kind_list(self.kind_list) # self.treeWidget_kind.resizeColumnToContents(0) self.treeWidget_kind.expandAll() def set_kind_list(self, items): qtreeitem_all = QTreeWidgetItem(self.treeWidget_kind) qtreeitem_all.setText(0, "全部") qtreeitem_all.setText(1, "0") for item in items: res = self.treeWidget_kind.findItems(str(item['autoid']), Qt.MatchRecursive, 1) if len(res): continue if item['parentid'] != 0: parent_item = self.find_kind_item(item['parentid']) qtreeitem = QTreeWidgetItem(parent_item) else: qtreeitem = QTreeWidgetItem(qtreeitem_all) qtreeitem.setText(0, item['kindname']) qtreeitem.setText(1, str(item['autoid'])) def find_kind_item(self, parentid, i=0): res = self.treeWidget_kind.findItems(str(parentid), Qt.MatchRecursive, 1) if len(res): return res[0] else: parent = self.treeWidget_kind.topLevelItem(0) try: if i > 10: # 手动设置最大循环深度位10 raise RecursionError for item in self.kind_list: if item['autoid'] == parentid: parent_id = item['parentid'] if parent_id == 0: parent = QTreeWidgetItem( self.treeWidget_kind.topLevelItem(0)) parent.setText(0, item['kindname']) parent.setText(1, str(item['autoid'])) break else: i += 1 parent = self.find_kind_item(parent_id, i) break return parent except RecursionError: return parent def get_client_list(self): self.treeWidget_detail.clear() key_dict = dict() if self.curremt_kind != 0: key_dict['tstkind'] = self.curremt_kind res = self.SC.get_data(0, False, *VALUES_TUPLE_SP, **key_dict) if not len(res): return for item in res: qtreeitem = QTreeWidgetItem(self.treeWidget_detail) qtreeitem.setText(0, str(item['autoid'])) qtreeitem.setText(1, item['clientid']) qtreeitem.setText(2, item['clientname']) qtreeitem.setText(3, item['address']) qtreeitem.setText(4, item['charger']) qtreeitem.setText(5, item['telno']) qtreeitem.setText(6, item['taxno']) for i in range(1, 7): self.treeWidget_detail.resizeColumnToContents(i) @pyqtSlot(QTreeWidgetItem, int) def on_treeWidget_kind_itemClicked(self, qtreeitem, p_int): self.curremt_kind = int(qtreeitem.text(1)) self.get_client_list() @pyqtSlot(QTreeWidgetItem, int) def on_treeWidget_detail_itemDoubleClicked(self, qtreeitem, p_int): # if self.power[1] == '0': # return id = int(qtreeitem.text(0)) detail = EditClientDetailModule(id, self) detail.accepted.connect(self.get_client_list) detail.show() @pyqtSlot(QPoint) def on_treeWidget_kind_customContextMenuRequested(self, pos): if self.power[1] == '0': return sender_widget = self.sender() current_item = sender_widget.currentItem() menu = QMenu() button1 = menu.addAction("增加分类") button2 = menu.addAction("修改分类") button3 = menu.addAction("删除分类") global_pos = sender_widget.mapToGlobal(pos) action = menu.exec(global_pos) if action == button1: if current_item is None: parent_item = self.treeWidget_kind.topLevelItem(0) else: parent_item = current_item parent_id = int(parent_item.text(1)) kwargs = { 'kind': 4, 'kindname': "新建分类", 'parentid': parent_id, 'inputcode': Inputcode.make_inputcode("新建分类") } kind_object = self.SC.update_data(4, **kwargs) qtreeitem = QTreeWidgetItem(parent_item) qtreeitem.setText(0, "新建分类") qtreeitem.setText(1, str(kind_object.autoid)) self.treeWidget_kind.openPersistentEditor(qtreeitem, 0) self.treeWidget_kind.setCurrentItem(qtreeitem) elif action == button2: if current_item is None: return self.treeWidget_kind.setCurrentItem(None) self.treeWidget_kind.openPersistentEditor(current_item, 0) self.treeWidget_kind.setCurrentItem(current_item, 0) elif action == button3: if current_item is None: return id = int(current_item.text(1)) condition = {'autoid': id} self.SC.delete_data(4, condition) self.get_kind_list() @pyqtSlot(QTreeWidgetItem, int) def on_treeWidget_kind_itemChanged(self, qtreeitem, p_int): if self.treeWidget_kind.isPersistentEditorOpen(qtreeitem, p_int): self.treeWidget_kind.closePersistentEditor(qtreeitem, p_int) id = int(qtreeitem.text(1)) new_kindname = qtreeitem.text(0) condition = {'autoid': id} kwargs = {'kindname': new_kindname} self.SC.update_data(4, condition, **kwargs) @pyqtSlot(QTreeWidgetItem, QTreeWidgetItem) def on_treeWidget_kind_currentItemChanged(self, qtreeitem, qtreeitem_1): if self.treeWidget_kind.isPersistentEditorOpen(qtreeitem_1, 0): self.treeWidget_kind.closePersistentEditor(qtreeitem_1, 0) id = int(qtreeitem_1.text(1)) new_kindname = qtreeitem_1.text(0) condition = {'autoid': id} kwargs = {'kindname': new_kindname} self.SC.update_data(4, condition, **kwargs) @pyqtSlot(QTreeWidgetItem, int) def on_treeWidget_kind_itemDoubleClicked(self, qtreeitem, p_int): self.treeWidget_kind.openPersistentEditor(qtreeitem, p_int) @pyqtSlot(QPoint) def on_treeWidget_detail_customContextMenuRequested(self, pos): if self.power[1] == '0': return sender_widget = self.sender() current_item = sender_widget.currentItem() menu = QMenu() button1 = menu.addAction("增加记录") button2 = menu.addAction("修改记录") button3 = menu.addAction("删除记录") global_pos = sender_widget.mapToGlobal(pos) action = menu.exec(global_pos) if action == button1: detail = EditClientDetailModule(parent=self) detail.accepted.connect(self.get_client_list) detail.show() elif action == button2: if current_item is None: return id = int(current_item.text(0)) detail = EditClientDetailModule(id, self) detail.accepted.connect(self.get_client_list) detail.show() elif action == button3: if current_item is None: return id = int(current_item.text(0)) condition = {'autoid': id} condition_3 = {'scid': id, 'kind': 1} self.SC.delete_data(0, condition) img_id_list = self.IC.get_data(0, True, *VALUES_TUPLE_IM, **condition_3) condition_4 = {'autoid__in': img_id_list} self.IC.delete_data(1, condition_4) self.IC.delete_data(0, condition_3) self.get_client_list()