class WebResource(object): params = ParamStorage() # синглтон для хранения данных def get(self, parent=None): use_ssl_state = unicode( self.params.app_config(key='General/debug_use_ssl')).lower() self.use_ssl = u'true' == use_ssl_state resource = self.use_ssl and Https(parent) or Http(parent) resource.use_ssl = self.use_ssl return resource
class DlgLogin(UiDlgTemplate): ui_file = 'uis/dlg_login.ui' params = ParamStorage() error_msg = None response = None def __init__(self, parent=None, **kwargs): self.stream = self.params.http self.connecting_to = kwargs.get('connecting_to') UiDlgTemplate.__init__(self, parent) def setupUi(self): UiDlgTemplate.setupUi(self) if self.connecting_to: self.editConnecting.setText(self.connecting_to) self.connect(self.buttonOk, SIGNAL('clicked()'), self.applyDialog) self.connect(self.buttonCancel, SIGNAL('clicked()'), self, SLOT('reject()')) def setCallback(self, callback): self.callback = callback def applyDialog(self): login = self.editLogin.text() password = self.editPassword.text() data = dict(login=login, password=password) try: self.exchange(data) except RequestFailedException: self.reject() else: self.accept() def exchange(self, data): data = dict(data) #, version='.'.join(map(str, self.params.version))) if not self.stream.request('/api/login/', 'POST', credentials=data): self.error_msg = self.tr('Unable to make request: %1').arg( self.stream.error_msg) raise RequestFailedException() self.response = self.stream.piston()
class Searching(UiDlgTemplate): """ Класс реализует диалог поиска клиента по имени. Информация о подходящих клиентах/арендаторах приходит в ответ на запрос /api/<mode>/<name>/, где mode - режим (client или renter), а name - полное имя или фамилия. Пользователь выбирает из списка найденных людей одну запись, информация о которой передаётся в модуль manager. """ ui_file = 'uis/dlg_searching.ui' params = ParamStorage() title = None mode = None def __init__(self, parent, *args, **kwargs): self.mode = kwargs.get('mode', 'client') UiDlgTemplate.__init__(self, parent) def setupUi(self): UiDlgTemplate.setupUi(self) self.tableUsers.setSelectionBehavior(QAbstractItemView.SelectRows) header = self.tableUsers.horizontalHeader() header.setStretchLastSection(False) header.setResizeMode(QHeaderView.ResizeToContents) header.setResizeMode(0, QHeaderView.Stretch) self.buttonApply.setText(self.tr('Show')) self.buttonApply.setDisabled(True) self.connect(self.buttonSearch, SIGNAL('clicked()'), self.searchFor) self.connect(self.buttonApply, SIGNAL('clicked()'), self.applyDialog) self.connect(self.buttonClose, SIGNAL('clicked()'), self, SLOT('reject()')) def setCallback(self, callback): self.callback = callback def searchFor(self): name = self.editSearch.text().toUtf8() http = self.params.http if not http.request( '/api/%s/%s/' % (self.mode, name), 'GET', force=True): QMessageBox.critical( self, self.tr('Searching'), self.tr('Unable to search: %s') % http.error_msg) return response = http.parse() self.showList(response) self.buttonApply.setDisabled(False) def showList(self, user_list): while self.tableUsers.rowCount() > 0: self.tableUsers.removeRow(0) self.user_list = {} for index, user in enumerate(user_list): self.user_list[index] = user name_text = '%(last_name)s %(first_name)s' % user rfid_obj = user.get('rfid') rfid_code = rfid_obj.get( 'code') if type(rfid_obj) is dict else '--' lastRow = self.tableUsers.rowCount() self.tableUsers.insertRow(lastRow) name_field = QTableWidgetItem(name_text) # данные нельзя назначать на строку, только на ячейку, используем первую name_field.setData(GET_ID_ROLE, index) self.tableUsers.setItem(lastRow, 0, name_field) self.tableUsers.setItem(lastRow, 1, QTableWidgetItem(user['email'])) self.tableUsers.setItem(lastRow, 2, QTableWidgetItem(rfid_code)) self.tableUsers.setItem(lastRow, 3, QTableWidgetItem(user['registered'])) if len(user_list) > 0: self.tableUsers.selectRow(0) self.buttonSearch.setDisabled(False) self.buttonApply.setFocus(Qt.OtherFocusReason) else: self.buttonApply.setDisabled(True) self.buttonSearch.setFocus(Qt.OtherFocusReason) def applyDialog(self): index = self.tableUsers.currentIndex() selected_user = self.user_list[index.row()] self.callback(selected_user) self.accept()
class EventInfo(UiDlgTemplate): ui_file = 'uis/dlg_event_info.ui' params = ParamStorage() schedule = None # кэшируем здесь данные от сервера event_object = None event_index = None def __init__(self, parent=None): self.stream = self.params.http UiDlgTemplate.__init__(self, parent) def setupUi(self): UiDlgTemplate.setupUi(self) self.connect(self.buttonClose, SIGNAL('clicked()'), self.close) self.connect(self.buttonVisitors, SIGNAL('clicked()'), self.show_visitors) self.connect(self.buttonVisitRFID, SIGNAL('clicked()'), self.search_by_rfid) self.connect(self.buttonVisitManual, SIGNAL('clicked()'), self.search_by_name) self.connect(self.buttonRemove, SIGNAL('clicked()'), self.removeEvent) self.connect(self.buttonChange, SIGNAL('clicked()'), self.change_coaches) # временно отключим кнопку удаления self.buttonRemove.setDisabled(True) self.buttonChange.setDisabled(True) def initData(self, obj, index): """ Метод инициализации диалога. """ self.event_object = obj self.event_index = index self.editStyle.setText(self.event_object.styles) self.editPriceCategory.setText(self.event_object.category) self.editCoaches.setText(self.event_object.coaches) begin = self.event_object.begin end = self.event_object.end self.editBegin.setDateTime(QDateTime(begin)) self.editEnd.setDateTime(QDateTime(end)) for index, room in self.params.static.get('rooms_by_index').items(): self.comboRoom.addItem(room.get('title'), QVariant(index)) self.comboRoom.setCurrentIndex( self.params.static.get('rooms_by_uuid').get( self.event_object.room_uuid)) statuses = dict( enumerate([ self.tr('Unknown'), self.tr('Waits'), self.tr('Occurred'), self.tr('Cancelled') ])) self.editStatus.setText( statuses.get(self.event_object.fixed, self.tr('Unknown'))) # disable controls for events in the past is_past = begin < datetime.now() is_rent = self.event_object.prototype == self.event_object.RENT self.buttonRemove.setDisabled(is_past or is_rent) self.buttonVisitRFID.setDisabled(is_past or is_rent) self.buttonVisitManual.setDisabled(is_past or is_rent) self.buttonChange.setDisabled(is_past or is_rent) self.buttonVisitors.setDisabled(is_rent) def show_visitors(self): dialog = ShowVisitors(self) dialog.setModal(True) dialog.initData(self.event_object.uuid) dialog.exec_() def search_by_rfid(self): """ Поиск пользователя по RFID и получение списка карт, соответствующих событию.""" def callback(rfid): self.rfid_id = rfid # диалог rfid считывателя dialog = WaitingRFID(self, callback=callback) dialog.setModal(True) if QDialog.Accepted == dialog.exec_(): http = self.params.http if not http.request( '/api/client/%s/' % self.rfid_id, 'GET', force=True): QMessageBox.critical( self, self.tr('Client info'), self.tr('Unable to fetch: %s') % http.error_msg) return response = http.parse() if 0 == len(response): QMessageBox.warning(self, self.tr('Warning'), self.tr('This RFID belongs to nobody.')) return False else: self.last_user_uuid = response[0].get('uuid') return self.visit_register(self.last_user_uuid) def search_by_name(self): """ Поиск пользователя по его имени и получение списка карт, соответствующих событию.""" def callback(user): self.last_user_uuid = user.get('uuid') self.dialog = Searching(self, mode='client', apply_title=self.tr('Register')) self.dialog.setModal(True) self.dialog.setCallback(callback) if QDialog.Accepted == self.dialog.exec_(): return self.visit_register(self.last_user_uuid) def select_voucher_list(self, *args, **kwargs): u"""Получает список подходящих ваучеров.""" url = '/api/voucher/%(event_id)s/%(client_id)s/%(start)s/' % kwargs if not self.stream.request(url, 'GET', force=True): raise UnableSendRequest() status, data = self.stream.piston() if not u'ALL_OK' == status: raise BadResponse(status) if 0 == len(data): raise EmptyVoucherList() return data def visit_register(self, user_uuid): u"""Регистрирует посещение события.""" title = self.tr('Client Registration') event = self.event_object # получаем список подходящих ваучеров try: voucher_list = self.select_voucher_list( client_id=user_uuid, event_id=event.uuid, start=event.begin.strftime('%Y%m%d%H%M%S')) except (UnableSendRequest, BadResponse, EmptyVoucherList): msg = self.tr('No voucher for this visit.\n\nBuy one.') QMessageBox.information(self, title, msg) return False # показываем список менеджеру, пусть выбирает def callback(voucher_uuid): self.voucher_uuid = voucher_uuid def make_title(voucher): out = [] card = voucher.get('_card_cache') if card: out.append(card.get('title')) category = voucher.get('_category_cache') if category: out.append(category.get('title')) return ' - '.join(out) dialog = WizardListDlg(params={ 'button_back': self.tr('Cancel'), 'button_next': self.tr('Ok') }) dialog.setModal(True) dialog.prefill(self.tr('Choose the Voucher'), [(v['uuid'], make_title(v)) for v in voucher_list], callback) # ваучер выбран, регистрируем посещение if QDialog.Accepted == dialog.exec_(): params = dict(action='VISIT', event=event.uuid, voucher=self.voucher_uuid) if not self.stream.request('/api/voucher/', 'PUT', params): QMessageBox.warning( self, title, '%s: %i\n\n%s\n\n%s' % (self.tr('Error'), ERR_EVENT_REGISTERVISIT, self.tr('Unable to register the visit: %s') % http.error_msg, self.tr('Call support team!'))) return False status, data = self.stream.piston() print status, data if u'CREATED' == status: msg = self.tr('The client has been registered for this event.') QMessageBox.information(self, title, msg) # распечатаем чек visit_uuid = data.get('uuid') self.print_label(visit_uuid) return True elif u'DUPLICATE_ENTRY' == status: msg = self.tr( 'The client is already registered for this event.') else: msg = self.tr('Unable to register!\nStatus: %s') % status QMessageBox.warning(self, title, msg) return False def print_label(self, visit_uuid): title = self.tr('Print') http = self.params.http if not http.request('/api/visit/%s/' % visit_uuid, 'GET', force=True): QMessageBox.warning( self, title, '%s: %i\n\n%s\n\n%s' % (self.tr('Error'), ERR_EVENT_PRINTLABEL, self.tr('Unable to prepare label: %s') % http.error_msg, self.tr('Call support team!'))) return False status, response = http.piston() print status, response self.params.printer.hardcopy(response) def changeRoom(self, new_index): # Room change: # 1. The choosen room is empty inside whole time period. # Change a room for the event. # 2. The choosen room is busy at all, i.e. two event are equal in time. # Change the rooms. # 3. The choosen room is busy partially. # Cancel the change, raise message. if new_index != self.current_room_index: # make room checking # pass def removeEvent(self): reply = QMessageBox.question( self, self.tr('Event remove'), self.tr('Are you sure to remove this event from the calendar?'), QMessageBox.Yes, QMessageBox.No) if reply == QMessageBox.Yes: params = {'id': self.schedule['id']} if not self.http.request('/manager/cal_event_del/', params): QMessageBox.critical( self, self.tr('Event deletion'), self.tr('Unable to delete: %s') % self.http.error_msg) return default_response = None response = self.http.parse(default_response) if response: index = self.comboRoom.currentIndex() room_id, ok = self.comboRoom.itemData(index).toInt() model = self.parent.schedule.model() model.remove(self.event_object, self.event_index, True) QMessageBox.information(self, self.tr('Event removing'), self.tr('Complete.')) self.accept() else: QMessageBox.information( self, self.tr('Event removing'), self.tr('Unable to remove this event!')) def change_coaches(self): """ Метод реализует функционал замены преподавателей для события. """ title = self.tr('Coaches') http = self.params.http if not http.request('/api/coach/', 'GET'): QMessageBox.critical( self, title, self.tr('Unable to fetch: %s') % http.error_msg) return status, coach_list = http.piston() if 'ALL_OK' == status: def coaches_callback(uuid_list): self.event_object.set_coaches( filter(lambda x: x.get('uuid') in uuid_list, coach_list)) dialog = ShowCoaches(self, callback=coaches_callback) dialog.setModal(True) dialog.initData(self.event_object, coach_list) if QDialog.Accepted == dialog.exec_(): # отображаем изменения на интерфейсе self.initData(self.event_object, self.event_index) # update schedule model to immediate refresh this event model = self.event_index.model() model.change(self.event_object, self.event_index)
class ShowVisitors(UiDlgTemplate): ui_file = 'uis/dlg_event_visitors.ui' params = ParamStorage() event_uuid = None def __init__(self, parent=None): self.stream = self.params.http UiDlgTemplate.__init__(self, parent) def setupUi(self): UiDlgTemplate.setupUi(self) self.tableVisitors.setSelectionBehavior(QAbstractItemView.SelectRows) self.tableVisitors.setContextMenuPolicy(Qt.CustomContextMenu) self.tableVisitors.customContextMenuRequested.connect( self.context_menu) self.connect(self.buttonClose, SIGNAL('clicked()'), self, SLOT('reject()')) def initData(self, event_uuid): self.event_uuid = event_uuid if not self.stream.request( '/api/history/%s/' % self.event_uuid, 'GET', force=True): msg = self.tr('Unable to fetch: %s') % http.error_msg QMessageBox.critical(self, self.tr('Visitors'), msg) return status, data = self.stream.piston() if 'ALL_OK' == status: for item in data.get('visit_set', []): client = item['voucher']['client'] last_name = client.get('last_name') first_name = client.get('first_name') registered = item.get('registered') rfid_code = client.get('rfid') if rfid_code is None: rfid_code = '--' lastRow = self.tableVisitors.rowCount() self.tableVisitors.insertRow(lastRow) name = QTableWidgetItem(last_name) # data may assign on cells only, use first one name.setData(GET_ID_ROLE, self.event_uuid) self.tableVisitors.setItem(lastRow, 0, name) self.tableVisitors.setItem(lastRow, 1, QTableWidgetItem(first_name)) self.tableVisitors.setItem(lastRow, 2, QTableWidgetItem(rfid_code)) self.tableVisitors.setItem(lastRow, 3, QTableWidgetItem(registered)) def context_menu(self, position): """ Create context menu.""" menu = QMenu() action_reprint = menu.addAction(self.tr('Reprint Selected')) action_cancel = menu.addAction(self.tr('Cancel Selected')) action = menu.exec_(self.tableVisitors.mapToGlobal(position)) # choose action if action == action_reprint: self.reprint() elif action == action_cancel: QMessageBox.warning(self, self.tr('Warning'), self.tr('Not yet implemented!')) else: print '%s: %s' % (self.__class__, self.tr('Unknown context menu action')) def reprint(self): selected = self.tableVisitors.selectionModel().selectedRows() for index in selected: model = index.model() visit_id, ok = model.data(index, GET_ID_ROLE).toInt() if ok: print_this = self.get_visit_info(visit_id) self.parent.parent.printer.hardcopy(print_this) def get_visit_info(self, visit_id): if not self.http.request('/manager/reprint_visit/', {'item_id': visit_id}): QMessageBox.critical( self, self.tr('Visit Reprint'), self.tr('Unable to fetch: %s') % self.http.error_msg) return default_response = None response = self.http.parse(default_response) if response and response.get('code', None) == 200 and 'print_this' in response: return response['print_this'] print 'log this'
class ShowCoaches(UiDlgTemplate): """ Класс для отображения диалога со списком преподавателей. Используется при замене преподавателей для занятия.""" ui_file = 'uis/dlg_event_coaches.ui' params = ParamStorage() callback = None event = None def __init__(self, parent, *args, **kwargs): UiDlgTemplate.__init__(self, parent) self.title = self.tr('Registered visitors') self.callback = kwargs.get('callback') def setupUi(self): UiDlgTemplate.setupUi(self) self.tableCoaches.setSelectionBehavior(QAbstractItemView.SelectRows) self.connect(self.buttonApply, SIGNAL('clicked()'), self.apply) self.connect(self.buttonClose, SIGNAL('clicked()'), self, SLOT('reject()')) def initData(self, event, coaches_list): self.event = event # отображаем преподавателей for coach in coaches_list: rfid_code = coach.get('rfid') if not rfid_code: rfid_code = '--' lastRow = self.tableCoaches.rowCount() self.tableCoaches.insertRow(lastRow) name = QTableWidgetItem(coach.get('last_name')) # используем первую ячейку для хранения данных name.setData(GET_ID_ROLE, coach.get('uuid')) self.tableCoaches.setItem(lastRow, 0, name) self.tableCoaches.setItem( lastRow, 1, QTableWidgetItem(coach.get('first_name'))) self.tableCoaches.setItem(lastRow, 2, QTableWidgetItem(rfid_code)) self.tableCoaches.setItem( lastRow, 3, QTableWidgetItem(coach.get('registered'))) def apply(self): selected = self.tableCoaches.selectionModel().selectedRows() if len(selected) > 3: message = self.tr('Select no more three coaches.') else: uuid_list = [ unicode(i.model().data(i, GET_ID_ROLE).toString()) for i in selected ] if len(uuid_list) == 0: message = self.tr('No selection, skip...') else: http = self.params.http params = [ ('action', 'CHANGE_COACH'), ('uuid', self.event.uuid), ] params += [('leaders', i) for i in uuid_list] if not http.request('/api/history/', 'PUT', params): QMessageBox.critical( self, self.tr('Register change'), self.tr('Unable to register: %s') % http.error_msg) return status, response = http.piston() if 'ALL_OK' == status: self.accept() self.callback(uuid_list) return else: message = self.tr('Unable to exchange.') QMessageBox.warning(self, self.tr('Coaches exchange'), message)
class MainWindow(QMainWindow): params = ParamStorage() # синглтон для хранения данных menu_actions = [] menu_desc = None def __init__(self, parent=None): QMainWindow.__init__(self, parent) self.setWindowIcon(QIcon('/usr/share/pixmaps/advisor-client.xpm')) self.mimes = { 'team': 'application/x-team-item', 'event': 'application/x-calendar-event', } self.tree = [] self.rfid_id = None self.params.init_settings(obj=QSettings(), main_window=self) self.params.WEEK_DAYS = ( self.tr('Monday'), self.tr('Tuesday'), self.tr('Wednesday'), self.tr('Thursday'), self.tr('Friday'), self.tr('Saturday'), self.tr('Sunday'), ) self.params.logged_in = False self.params.work_hours = (8, 24) self.params.quant = timedelta(minutes=30) self.params.multiplier = timedelta( hours=1).seconds / self.params.quant.seconds self.menus = [] self.menu_desc = self.app_menu() self.create_menus(self.menu_desc) self.menu_state(MENU_LOGGED_OUT) self.setup_views() # если сервер не определён, показываем диалог настройки приложения settings = QSettings() settings.beginGroup('network') host = settings.value('addressHttpServer', QVariant('WrongHost')) settings.endGroup() self.webresource = WebResource() self.params.http = self.webresource.get(self) if 'WrongHost' == host.toString(): self.app_settings() self.baseTitle = self.tr('Manager\'s interface') self.logoutTitle() self.statusBar().showMessage(self.tr('Ready'), 2000) self.resize(640, 480) def loggedTitle(self, response): last_name = response.get('last_name') first_name = response.get('first_name') if len(last_name) > 0 or len(first_name) > 0: self.setWindowTitle('%s : %s %s' % (self.baseTitle, last_name, first_name)) else: self.setWindowTitle('%s : %s' % (self.baseTitle, response.get('username'))) def logoutTitle(self): self.setWindowTitle( '%s : %s' % (self.baseTitle, self.tr('Login to start session'))) def get_dynamic(self): self.bpMonday.setText( self.schedule.model().getMonday().strftime('%d/%m/%Y')) self.bpSunday.setText( self.schedule.model().getSunday().strftime('%d/%m/%Y')) def get_static(self): """ Метод для получения статической информации с сервера. """ if not self.params.http.request('/api/static/', 'GET', {}): QMessageBox.critical( self, self.tr('Static info'), self.tr('Unable to fetch: %s') % self.params.http.error_msg) return data = self.params.http.parse() if type(data) is dict and data.get('status') == 401: return None else: return data def update_interface(self): """ This method updates application's interface using static information obtained in previous method. """ # rooms rooms = self.params.static.get('rooms') if rooms: for item in rooms: uu_id = item.get('uuid') title = item.get('title') buttonFilter = QPushButton(title) buttonFilter.setCheckable(True) buttonFilter.setDisabled(True) self.panelRooms.addWidget(buttonFilter) self.connect(buttonFilter, SIGNAL('clicked()'), self.prepare_filter(uu_id, title)) def printer_init(self): self.params.printer = Printer( template=self.params.static.get('printer')) run_it = True def show_printer_status(): ok, tip = self.params.printer.get_status() self.printer_widget.setToolTip(tip) if ok: msg = self.tr('Printer is ready') else: msg = self.tr('Printer is not ready') self.printer_widget.setText(msg) self.printer_refresh = self.makeTimer( show_printer_status, self.params.printer.refresh_timeout, run_it) def prepare_filter(self, id, title): def handler(): self.statusBar().showMessage( self.tr('Filter: Room "%s" is changed its state') % title) return handler def setup_views(self): self.panelRooms = QHBoxLayout() self.schedule = QtSchedule(self) self.bpMonday = QLabel('--/--/----') self.bpSunday = QLabel('--/--/----') self.buttonPrev = QPushButton(self.tr('<<')) self.buttonNext = QPushButton(self.tr('>>')) self.buttonToday = QPushButton(self.tr('Today')) self.buttonPrev.setDisabled(True) self.buttonNext.setDisabled(True) self.buttonToday.setDisabled(True) # callback helper function def prev_week(): week_range = self.schedule.model().showPrevWeek() self.showWeekRange(week_range) def next_week(): week_range = self.schedule.model().showNextWeek() self.showWeekRange(week_range) def today(): week_range = self.schedule.model().showCurrWeek() self.showWeekRange(week_range) self.connect(self.buttonPrev, SIGNAL('clicked()'), prev_week) self.connect(self.buttonNext, SIGNAL('clicked()'), next_week) self.connect(self.buttonToday, SIGNAL('clicked()'), today) bottomPanel = QHBoxLayout() bottomPanel.addWidget(QLabel(self.tr('Week:'))) bottomPanel.addWidget(self.bpMonday) bottomPanel.addWidget(QLabel('-')) bottomPanel.addWidget(self.bpSunday) bottomPanel.addStretch(1) bottomPanel.addWidget(self.buttonPrev) bottomPanel.addWidget(self.buttonToday) bottomPanel.addWidget(self.buttonNext) mainLayout = QVBoxLayout() mainLayout.addLayout(self.panelRooms) mainLayout.addWidget(self.schedule) mainLayout.addLayout(bottomPanel) mainWidget = QWidget() mainWidget.setLayout(mainLayout) self.setCentralWidget(mainWidget) self.printer_widget = QLabel('--', self.statusBar()) self.printer_widget.setToolTip(u'Initialization in progress') self.statusBar().addPermanentWidget(self.printer_widget) def showWeekRange(self, week_range): if self.schedule.model().getShowMode() == 'week': monday, sunday = week_range self.bpMonday.setText(monday.strftime('%d/%m/%Y')) self.bpSunday.setText(sunday.strftime('%d/%m/%Y')) def getMime(self, name): return self.mimes.get(name, None) def app_menu(self): return [ (self.tr('File'), [ (self.tr('Open'), 'Ctrl+I', 'open_session', self.tr('Open user session.'), MENU_LOGGED_OUT), (self.tr('Close'), '', 'close_session', self.tr('Close user session.'), MENU_LOGGED_IN), None, (self.tr('Settings'), 'Ctrl+S', 'app_settings', self.tr('Manage the application settings.'), MENU_LOGGED_ANY), None, (self.tr('Exit'), '', 'close', self.tr('Close the application.'), MENU_LOGGED_ANY), ]), (self.tr('Client'), [ (self.tr('New'), 'Ctrl+N', 'client_new', self.tr('Register new client.'), MENU_LOGGED_IN), (self.tr('Search by RFID'), 'Ctrl+D', 'client_search_rfid', self.tr('Search a client with its RFID card.'), MENU_LOGGED_IN | MENU_RFID), (self.tr('Search by name'), 'Ctrl+F', 'client_search_name', self.tr('Search a client with its name.'), MENU_LOGGED_IN), ]), (self.tr('Renter'), [ (self.tr('New'), 'Ctrl+M', 'renter_new', self.tr('Register new renter.'), MENU_LOGGED_IN), (self.tr('Search by name'), 'Ctrl+G', 'renter_search_name', self.tr('Search a renter with its name.'), MENU_LOGGED_IN), ]), (self.tr('Help'), [ (self.tr('About'), '', 'help_about', self.tr('About application dialog.'), MENU_LOGGED_ANY), ]), ] def create_menus(self, desc): """ Метод генерирует по переданному описанию меню приложения. Использование: Опишите меню со всеми его действиями, реализуйте обработчики для каждого элемента меню, передайте описание в данный метод. """ for topic, info in desc: action_handlers = [] menu = self.menuBar().addMenu(topic) for item in info: if item is None: menu.addSeparator() continue else: title, short, name, desc, state = item setattr(self, 'act_%s' % name, QAction(title, self)) action = getattr(self, 'act_%s' % name) action.setShortcut(short) action.setStatusTip(desc) self.connect(action, SIGNAL('triggered()'), getattr(self, name)) menu.addAction(action) self.menus.append(menu) action_handlers.append(( action, state, )) self.menu_actions.append(action_handlers) def menu_state(self, state): """ Метод для смены состояния меню. """ BITS = { 'DISABLED': 0, 'LOGGED_IN': 1, 'LOGGED_OUT': 2, 'RFID': 3, 'PRINTER': 4, } def is_bit_set(value, bitname): try: bitnum = BITS[bitname] except KeyError: return False else: return (state & (1 << bitnum)) != 0 for actions in self.menu_actions: for action, state in actions: if is_bit_set(state, 'DISABLED'): action.setDisabled(True) continue if not self.params.logged_in: if is_bit_set(state, 'LOGGED_OUT'): action.setDisabled(False) else: action.setDisabled(True) continue if self.params.logged_in: if is_bit_set(state, 'LOGGED_IN'): disable = False if is_bit_set(state, 'RFID'): # and RFID not present disable = True elif is_bit_set(state, 'PRINTER'): # and PRINTER not present disable = True action.setDisabled(disable) else: action.setDisabled(True) continue def interface_disable(self, state): # Enable menu's action self.menu_state(state) # Enable the navigation buttons self.buttonPrev.setDisabled(not self.params.logged_in) self.buttonNext.setDisabled(not self.params.logged_in) self.buttonToday.setDisabled(not self.params.logged_in) def refresh_data(self): """ This method get the data from a server. It call periodically using timer. """ # Do nothing until user authoruized if not self.params.http.is_session_open(): return # Just refresh the calendar's model self.schedule.model().update # Menu handlers: The begin def open_session(self, **kwargs): connecting_to = u'%s %s' % ( self.params.http.hostport, self.params.http.use_ssl and self.tr('Secure') or self.tr('Unsecure'), ) self.dialog = DlgLogin(self, connecting_to=connecting_to) dlgStatus = self.dialog.exec_() dialog_title = self.tr('Open session') if not QDialog.Accepted == dlgStatus: QMessageBox.critical(self, dialog_title, self.dialog.error_msg) return status, data = self.dialog.response if status == 'ALL_OK': self.params.logged_in = True self.loggedTitle(data) # подгружаем статическую информацию и список залов static = self.get_static() if not static: print 'Check static!' return self.params.static = static rooms_by_index = {} rooms_by_uuid = {} for index, room in enumerate(self.params.static.get('rooms')): room_uuid = room.get('uuid') rooms_by_index[index] = room rooms_by_uuid[room_uuid] = index self.params.static['rooms_by_index'] = rooms_by_index self.params.static['rooms_by_uuid'] = rooms_by_uuid # здесь только правим текстовые метки self.get_dynamic() # изменяем свойства элементов интерфейса self.update_interface() # загружаем информацию о занятиях на расписание self.schedule.model().showCurrWeek() # # run refresh timer from settings import SCHEDULE_REFRESH_TIMEOUT self.refreshTimer = self.makeTimer(self.refresh_data, SCHEDULE_REFRESH_TIMEOUT, True) self.printer_init() self.interface_disable(MENU_LOGGED_IN | MENU_RFID | MENU_PRINTER) elif status == 'NOT_IMPLEMENTED': QMessageBox.information(self, dialog_title, self.tr('Protocol is deprecated!')) elif status == 'BAD_REQUEST': QMessageBox.warning( self, dialog_title, self.tr('It seems you\'ve entered wrong login/password.')) else: QMessageBox.information(self, dialog_title, self.tr('Unknown error!')) def close_session(self): self.params.logged_in = False self.interface_disable(MENU_LOGGED_OUT) self.setWindowTitle('%s : %s' % (self.baseTitle, self.tr('Open user session'))) self.schedule.model().storage_init() # clear rooms layout layout = self.panelRooms while layout.count() > 0: item = layout.takeAt(0) if not item: continue w = item.widget() if w: w.deleteLater() def app_settings(self): self.dialog = DlgSettings(self) self.dialog.setModal(True) self.dialog.exec_() self.get_dynamic() self.params.http.disconnect() self.params.http = self.webresource.get(self) self.params.http.connect() def client_new(self, klass=ClientInfo): self.user_new(klass) def renter_new(self, klass=RenterInfo): self.user_new(klass) def client_search_name(self, klass=ClientInfo, mode='client'): self.user_search_name(klass, mode) def renter_search_name(self, klass=RenterInfo, mode='renter'): self.user_search_name(klass, mode) def client_search_rfid(self, klass=ClientInfo, mode='client'): self.user_search_rfid(klass, mode) def renter_search_rfid(self, klass=RenterInfo, mode='renter'): return self.user_search_rfid(klass, mode) def user_new(self, klass): self.dialog = klass(self) self.dialog.setModal(True) self.dialog.exec_() def user_search_rfid(self, klass, mode): """ Метод для поиска клиента по RFID. """ def callback(rfid): self.rfid_id = rfid if self.params.logged_in: dialog = WaitingRFID(self, mode=mode, callback=callback) dialog.setModal(True) if QDialog.Accepted == dialog.exec_() and self.rfid_id: h = self.params.http if not h.request( '/api/%s/%s/' % (mode, self.rfid_id), 'GET', force=True): QMessageBox.critical( self, self.tr('Client info'), self.tr('Unable to fetch: %s') % h.error_msg) return response = h.parse() if 0 == len(response): QMessageBox.warning( self, self.tr('Warning'), self.tr('This RFID belongs to nobody.')) else: self.dialog = klass(self) self.dialog.setModal(True) self.dialog.initData(response[0]) self.dialog.exec_() del (self.dialog) self.rfid_id = None def user_search_name(self, klass, mode): def callback(user): self.user = user if self.params.logged_in: self.dialog = Searching(self, mode=mode) self.dialog.setModal(True) self.dialog.setCallback(callback) if QDialog.Accepted == self.dialog.exec_(): self.dialog = klass(self) self.dialog.setModal(True) self.dialog.initData(self.user) self.dialog.exec_() del (self.dialog) def help_about(self): version = '.'.join(map(str, self.params.version)) msg = """ <p>Клиентское приложение учётной системы.</p> <p>Версия %(version)s</p> <p>Сайт: <a href="http://snegiri.dontexist.org/projects/advisor/client/">Учётная система: Клиент</a>.</p> <p>Поддержка: <a href="mailto:[email protected]">Написать письмо</a>.</p> """ % locals() QMessageBox.about(self, self.tr('About application dialog'), msg.decode('utf-8')) def eventTraining(self): def callback(e_date, e_time, room_tuple, team): room, ok = room_tuple title, team_id, count, price, coach, duration = team begin = datetime.combine(e_date, e_time) duration = timedelta(minutes=int(duration * 60)) ajax = HttpAjax(self, '/manager/cal_event_add/', { 'event_id': team_id, 'room_id': room, 'begin': begin, 'ev_type': 0 }, self.session_id) response = ajax.parse_json() event_info = { 'id': int(response['saved_id']), 'title': title, 'price': price, 'count': count, 'coach': coach, 'duration': duration, 'groups': self.tr('Waiting for update.') } eventObj = Event({}) # FIXME self.schedule.insertEvent(room, eventObj) self.dialog = DlgEventAssign('training', self) self.dialog.setModal(True) self.dialog.setCallback(callback) self.dialog.setModel(self.tree) self.dialog.setRooms(self.rooms) self.dialog.exec_() def eventRent(self): def callback(e_date, e_time, e_duration, room_tuple, rent): room, ok = room_tuple rent_id = rent['id'] begin = datetime.combine(e_date, e_time) duration = timedelta(hours=e_duration.hour, minutes=e_duration.minute) params = { 'event_id': rent_id, 'room_id': room, 'begin': begin, 'ev_type': 1, 'duration': float(duration.seconds) / 3600 } ajax = HttpAjax(self, '/manager/cal_event_add/', params, self.session_id) response = ajax.parse_json() id = int(response['saved_id']) eventObj = Event({}) # FIXME self.schedule.insertEvent(room, eventObj) self.dialog = DlgEventAssign('rent', self) self.dialog.setModal(True) self.dialog.setCallback(callback) self.dialog.setRooms(self.rooms) self.dialog.exec_() def addResources(self): def callback(count, price): # ajax = HttpAjax(self, '/manager/add_resource/', # {'from_date': from_range[0], # 'to_date': to_range[0]}, self.session_id) response = ajax.parse_json() self.statusBar().showMessage( self.tr('The week has been copied sucessfully.')) self.dialog = DlgAccounting(self) self.dialog.setModal(True) self.dialog.setCallback(callback) self.dialog.exec_() # Menu handlers: The end def makeTimer(self, handler, timeout=0, run=False): timer = QTimer(self) timer.setInterval(timeout) self.connect(timer, SIGNAL('timeout()'), handler) if run: timer.start() return timer def showEventProperties(self, event, index): #, room_id): self.dialog = EventInfo(self) self.dialog.setModal(True) self.dialog.initData(event, index) self.dialog.exec_() # Drag'n'Drop section begins def mousePressEvent(self, event): if DEBUG_COMMON: print 'press event', event.button() def mouseMoveEvent(self, event): if DEBUG_COMMON: print 'move event', event.pos()
class BaseUserInfo(UiDlgTemplate): ui_file = 'uis/dlg_user_info.ui' params = ParamStorage() model = None user_id = None # новые записи обозначаются отсутствием идентификатора discounts_by_index = {} discounts_by_uuid = {} rfid_id = None rfid_uuid = None changed = False def __init__(self, parent=None): UiDlgTemplate.__init__(self, parent) def setupUi(self, *args, **kwargs): UiDlgTemplate.setupUi(self) self.tableHistory.setSelectionBehavior(QAbstractItemView.SelectRows) self.tableHistory.setContextMenuPolicy(Qt.CustomContextMenu) self.tableHistory.customContextMenuRequested.connect(self.context_menu) # добавляем на диалог все зарегистрированные виды скидок discount_list = kwargs.get('discount', []) for index, item in enumerate(discount_list): checkbox = QCheckBox('%(title)s (%(percent)s%%)' % item) self.discounts_by_index[index] = (checkbox, item) self.discounts_by_uuid[item.get('uuid')] = (checkbox, item) self.discountLayout.addWidget(checkbox) self.discountLayout.addStretch(10) self.connect(self.buttonAssign, SIGNAL('clicked()'), self.assign_item) self.connect(self.buttonRFID, SIGNAL('clicked()'), self.assign_rfid) self.connect(self.buttonSave, SIGNAL('clicked()'), self.save_dialog) self.connect(self.buttonClose, SIGNAL('clicked()'), self, SLOT('reject()')) def context_menu(self, position): raise RuntimeWarning('Reimplement method: context_menu().') def assign_rfid(self): """ Метод для назначения пользователю RFID идентификатора. """ def callback(rfid): self.rfid_code = rfid dialog = WaitingRFID(self, mode='client', callback=callback) dialog.setModal(True) dlgStatus = dialog.exec_() if QDialog.Accepted == dlgStatus: http = self.params.http if not http.request('/api/rfid/%s/' % self.rfid_code, 'POST'): QMessageBox.critical( self, self.tr('Client Information'), self.tr('Unable to fetch: %1').arg(http.error_msg)) return status, response = http.piston() if status == 'DUPLICATE_ENTRY': QMessageBox.warning(self, self.tr('Warning'), self.tr('This RFID is used already!')) elif status == 'CREATED': self.rfid_uuid = response.get('uuid') self.buttonRFID.setText(self.rfid_code) self.buttonRFID.setDisabled(True) def assign_item(self): raise RuntimeWarning('Reimplement method: assign_item().') def save_user(self, *args, **kwargs): """ Метод для сохранения информации о пользователе. @rtype: boolean @return: Результат выполнения операции. """ mode = kwargs.get('mode', 'client') data = [ ('uuid', self.user_id), ('is_active', True), # статусом надо управлять на диалоге ('last_name', self.editLastName.text().toUtf8()), ('first_name', self.editFirstName.text().toUtf8()), ('phone', self.editPhone.text().toUtf8()), ('email', self.editEmail.text().toUtf8()), ('birth_date', self.dateBirth.date().toPyDate()), ('rfid', self.rfid_uuid or u''), ] # соберём информацию о скидках клиента, сохраняем # идентификаторы установленных скидок data = data + [('discount', i) for i, (o, desc) in self.discounts_by_uuid.items() if o.checkState() == Qt.Checked] # передаём на сервер dialog_title = self.tr('Saving') http = self.params.http if not http.request('/api/%s/' % mode, self.user_id is None and 'POST' or 'PUT', data): QMessageBox.critical( self, dialog_title, self.tr('Unable to save: %1').arg(http.error_msg)) return False status, response = http.piston() if status == 'ALL_OK': self.user_id = response.get('uuid') QMessageBox.information(self, dialog_title, self.tr('Information is saved.')) return True else: QMessageBox.warning(self, dialog_title, self.tr('Warning!\nPlease fill all fields.')) return False
class Printer: device_file = '/dev/usblp0' params = ParamStorage() is_ready = False refresh_timeout = 1000 # one second template = None def __init__(self, *args, **kwargs): self.error_msg = QApplication.translate('printer', 'No error') self.flags = [ [(QApplication.translate('printer', 'No media'), 1), (QApplication.translate('printer', 'Pause is active'), 2), (QApplication.translate('printer', 'Buffer is full'), 5), (QApplication.translate('printer', 'Diagnostic mode is active'), 6), (QApplication.translate('printer', 'Check is printing'), 7), (QApplication.translate('printer', 'RAM is corrupted'), 9), (QApplication.translate('printer', 'Head is too cold'), 10), (QApplication.translate('printer', 'Head is too hot'), 11), ], [(QApplication.translate('printer', 'Close the lid'), 2), (QApplication.translate('printer', 'No ribbon'), 3), ], ] self.device_file = kwargs.get('device_file', '/dev/usblp0') self.refresh_timeout = kwargs.get('refresh_timeout', PRINTER_REFRESH_TIMEOUT) self.template = kwargs.get('template', '') self.DEBUG_PRINTER = 'true' == self.params.app_config(key='General/debug_printer') if self.DEBUG_PRINTER: print 'PRINTER DEVICE is', self.device_file #if not DEBUG_PRINTER: # self.RESET() def RESET(self): return self.action('~JR', False) def HEADI(self): return self.action('~HD', True) def HOSTI(self): return self.action('~HS', True) def action(self, mnemonic, do_recv=False, **kwargs): ok = self.send(mnemonic, **kwargs) if ok and do_recv: return self.recv() def get_status(self): """ Read the status lines from printer and set READY flag. """ error_list = [] #import pdb; pdb.set_trace() if self.DEBUG_PRINTER: out = ['\x02030,0,0,1032,000,0,0,0,000,0,0,0\x03\r\n', '\x02000,0,0,0,0,2,6,0,00000000,1,000\x03\r\n', '\x021234,0\x03\r\n'] else: out = self.HOSTI() if not out: self.is_ready = False return (self.is_ready, self.error_msg) else: regexp = re.compile(r'\x02([^\x03]+)\x03\r\n') for index, flags in enumerate(self.flags): body = regexp.match(out[index]) if body: params = body.group(1).split(',') for error_msg, key in flags: #print key, params[key] if params[key] != '0': error_list.append(error_msg) if len(error_list) == 0: self.is_ready = True return (self.is_ready, QApplication.translate('printer', 'Ready')) else: self.is_ready = False return (self.is_ready, '\n'.join(error_list)) def send(self, data, *args, **kwargs): try: p = open(self.device_file, 'w') p.write(data) p.close() return True except IOError: self.error_msg = QApplication.translate('printer', 'No device') return False def recv(self): try: p = open(self.device_file, 'r') data = p.readlines() p.close() return data except IOError: self.error_msg = QApplication.translate('printer', 'No device') return None def hardcopy(self, params): if self.DEBUG_PRINTER: print 'DEBUG_PRINTER' import pprint; pprint.pprint(params) else: output = self.template % params self.send(output.encode('utf-8'))
class CardListModel(QAbstractTableModel): params = ParamStorage() free_visit_used = False def __init__(self, parent=None): QAbstractTableModel.__init__(self, parent) self.storage = [] # here the data is stored, as list of dictionaries self.hidden_fields = 1 # from end of following lists def init_data(self, voucher_list): if not voucher_list: return for item in voucher_list: v_type = item.get('type') # если халявное посещение использовано, отметим это if v_type in ( 'flyer', 'test', ): self.free_visit_used = True record = [] object_data = { 'type': v_type, } for name, delegate, title, action, static in MODEL_MAP_RAW: if name == 'object': pass # добавим объект позже elif action is date2str: value = serialized = item.get(name) if value: value = datetime.strptime(value, '%Y-%m-%d').date() elif action is dt2str: value = item.get(name) if value: value = datetime.strptime(value, '%Y-%m-%d %H:%M:%S') else: value = item.get(name, action == float and '0.00' or '--') object_data[name] = value record.append(value) record.append(dict( item, **object_data)) # в конце добавляем полное описание объекта self.storage.append(record) self.emit(SIGNAL('rowsInserted(QModelIndex, int, int)'), QModelIndex(), 1, self.rowCount()) def get_voucher_info(self, index): return self.storage[index.row()][-1] def current_vouchers(self, formset_prefix): """ Метод для сбора актуальной информации о ваучерах. """ out = [] # пробегаем по хранилищу, берём только последний элемент от # каждой записи (там словарь с данными), добавляем # идентификатор клиент, конвертируем дату/время и дампим в # json. for index, item in enumerate(self.storage): voucher = item[-1] if 'client' not in voucher: # перевод дат в строковую форму, перед конвертацией в json for field, value in voucher.items(): if field.endswith('_date') and isinstance(value, date): value = date2str(value) elif field.endswith('_datetime') and isinstance( value, datetime): value = dt2str(value) elif type(value) is dict: value = value.get('uuid') elif value is None: value = u'' key = '%s-%i-%s' % (formset_prefix, index, field) out.append((key, value)) return out def get_model_as_formset(self, formset_prefix='voucher'): """ Метод для создания набора форм для сохранения данных через Django FormSet.""" # основа return [ ('%s-INITIAL_FORMS' % formset_prefix, '0'), ('%s-TOTAL_FORMS' % formset_prefix, unicode(len(self.storage))), ('%s-MAX_NUM_FORMS' % formset_prefix, unicode(len(self.storage))), ] + self.current_vouchers(formset_prefix) def dump(self, data=None, header=None): import pprint if data: if header: print '=== %s ===' % header.upper() pprint.pprint(data) else: print 'CardListModel dump is' pprint.pprint(self.storage) def is_expired(self, index): """ Проверка, что ваучер ещё действует. """ voucher = self.get_voucher_info(index) end = voucher.get('end') if not end: return False # ваучер ещё не активировали (абонемент) return end < date.today() def is_cancelled(self, index): """ Проверка, что ваучер не отменён. """ voucher = self.get_voucher_info(index) return voucher.get('cancelled') is not None def may_prolongate(self, index): """ Метод для определения возможности пролонгации ваучера. """ voucher = self.get_voucher_info(index) vtype = voucher.get('type') return vtype in ('abonement', ) def is_debt_exist(self, index, *args, **kwargs): """ Метод для проверки наличия долга. Метод не позволяет производить доплату неполностью оплаченных несохранённых ваучеров. Метод учитывает использованые при покупке скидки.""" voucher = self.get_voucher_info(index) if voucher.get('uuid'): vtype = voucher.get('type') if vtype in ('abonement', 'club', 'promo'): price = float(voucher.get('price', 0.00)) paid = float(voucher.get('paid', 0.00)) if 'discount_price' in voucher: # abonement price = float(voucher.get('discount_price', 0.00)) return (paid < price, price - paid) # в остальных случаях, считаем, что долга нет return (False, float(0)) def rowCount(self, parent=None): # base class method if parent and parent.isValid(): return 0 else: return len(self.storage) def columnCount(self, parent=None): # base class method if parent and parent.isValid(): return 0 else: return len(MODEL_MAP) - self.hidden_fields def headerData(self, section, orientation, role): # base class method if orientation == Qt.Horizontal and role == Qt.DisplayRole: return QVariant(MODEL_MAP[section].get('title', '--')) if orientation == Qt.Vertical and role == Qt.DisplayRole: return QVariant(section + 1) # order number return QVariant() def flags(self, index): # base class method if not index.isValid(): return Qt.ItemIsEnabled return Qt.ItemIsEnabled | Qt.ItemIsEditable def insert_new(self, steps): """ Метод для вставки новой записи в модель. """ print 'STEPS TO INSERT', steps v_type = steps.get('type') today = date.today().strftime('%Y-%m-%d') record = [] object_data = { 'type': v_type, } value = steps['card'].get('title', '') object_data['card'] = steps['card'] record.append(value) try: category = steps['category'] except KeyError: value = None else: value = category.get('title', '') object_data['category'] = value record.append(value) value = steps.get('price', 0.0) object_data['price'] = value record.append(value) value = steps.get('begin', today) object_data['begin'] = u'' == value and None or value record.append(value) value = steps.get('end', today) object_data['end'] = u'' == value and None or value record.append(value) value = steps.get('registered', datetime.now()) object_data['registered'] = value record.append(value) value = steps.get('cancelled') object_data['cancelled'] = value record.append(value) value = steps.get('uuid') object_data['uuid'] = value record.append(value) record.append(dict(steps, **object_data)) # # категории нет только у пробных и флаера # if v_type in ('once', 'abonement', 'club'): # steps['category'] = filter(lambda a: a['id'] == steps.get('category', None), # self.params.static.get('category_team') # )[0] # template['category'] = steps['category'] print '\nRECORD:', record self.storage.insert(0, record) self.emit(SIGNAL('rowsInserted(QModelIndex, int, int)'), QModelIndex(), 1, 1) return True def update(self, index): """ Метод для обновления полей модели после изменения основного объекта.""" row = index.row() col = index.column() model_row = self.storage[index.row()] obj = model_row[-1] for col, d in enumerate(MODEL_MAP_RAW[:-1]): value = obj.get(d[0], None) model_row[col] = value def index_to_meta(self, index): row = index.row() col = index.column() field_obj = MODEL_MAP[col] field_name = field_obj['name'] delegate_editor = field_obj['delegate'] action = field_obj['action'] return (row, col, action) def data(self, index, role): # переопределённый метод """ Метод для выдачи данных из модели. Учитывается роль и индекс ячейки.""" # основные проверки if not index.isValid(): return QVariant('error') if role == Qt.ForegroundRole: return self.data_ForegroundRole(index) #if role not in (Qt.DisplayRole, Qt.ToolTipRole, GET_ID_ROLE) : # return QVariant() row, col, action = self.index_to_meta(index) if role == Qt.DisplayRole: record = self.storage[row] value = record[col] # для сложного типа, отображаем его название if type(value) is dict and 'title' in value: return QVariant(value.get('title')) if value is None or value in ('--', ''): return QVariant('--') else: return action(value) elif role == Qt.ToolTipRole: # вывод подсказки out = [] info = self.get_voucher_info(index) vtype = info['type'] if vtype in ( 'flyer', 'test', 'once', ): out.append( info.get('is_utilized') and self.tr('Utilized') or self.tr('Not utilized')) if vtype in ('abonement', 'club', 'promo'): # при обработке цены, проверяем долг клиента, если он # есть, то показываем это debt, amount = self.is_debt_exist(index) if debt: out.append(self.tr('debt %1').arg('%.02f' % amount)) if vtype == 'abonement': # отображаем скидку, если есть if 'discount_price' in info: price = float(info['price']) discount_price = float(info['discount_price']) discount_percent = int(info['discount_percent']) out.append( self.tr('discount %1/%2').arg( '%.02f' % (float(price) - float(discount_price)), ).arg(discount_percent)) out.append('sold %i' % int(info.get('sold', 0))) out.append('used %i' % int(info.get('used', 0))) out.append('available %i' % int(info.get('available', 0))) if vtype == 'club': out.append('days %i' % int(info['card'].get('days', 0))) out.append('used %i' % int(info.get('used', 0))) available = info.get('available') if available: out.append('available %i' % int(available)) if vtype == 'promo': out.append('used %i' % int(info.get('used', 0))) out.append('available %i' % int(info.get('available', 0))) return QVariant('; '.join(map(unicode, out))) else: return QVariant() def data_ForegroundRole(self, index): """ Метод для выдачи цвета шрифта в зависимости от состояния ваучера. """ info = self.get_voucher_info(index) if not info.get('uuid'): # новые записи return QBrush(Qt.green) elif self.is_expired(index) or self.is_cancelled(index): # завершённые и отменённые записи показываем серым return QBrush(Qt.gray) elif self.is_debt_exist(index)[0]: # записи с долгом показываем красным цветом return QBrush(Qt.red) else: # остальный записи показываем чёрным цветом return QBrush(Qt.black)
class BaseModel(QAbstractTableModel): """ Базовая модель. Дочерние модели должны определить заголовки полей (TITLES), ключи полей (FIELDS). Для полей, которые должны отображаться особым образом (например, в поле находится идентификатор объекта, а надо отображать его текстовое описание, хранящееся на внешнем ресурсе), необходимо определить метод-обработчик. Шаблон имени метода: 'handler_KEY', где KEY - имя ключа поля из FIELDS. """ # описание модели TITLES = () FIELDS = () EXCLUDE = () storage = [] # синглтон с параметрами приложения params = ParamStorage() def __init__(self, parent=None): QAbstractTableModel.__init__(self, parent) def init_data(self, event_list): """ Метод для заполнения модели. """ self.storage = [] def insert_new(self, record): """ Метод для вставки новой записи в модель. @type record: dict @param record: Словарь с данными. @rtype: boolean @return: Результат выполнения операции. """ self.storage.append(record) index = len(self.storage) self.emit(SIGNAL('rowsInserted(QModelIndex, int, int)'), QModelIndex(), index, index) return True def export(self): """ Метод для экспорта информации из модели. @rtype: list of dicts @return: Содержимое модели. """ return self.storage def formset(self, **kwargs): """ Метод для создания набора форм для сохранения данных через Django FormSet. @rtype: list of tuples @return: Список с данными набора форм. """ if 'record_list' not in kwargs: record_list = self.export() formset = [ ('formset-TOTAL_FORMS', str(len(record_list))), ('formset-MAX_NUM_FORMS', str(len(record_list))), ('formset-INITIAL_FORMS', '0'), ] for index, record in enumerate(record_list): for key, value in kwargs.get('initial', {}).items(): # инжектим данные row = ('formset-%s-%s' % (index, key), value) formset.append(row) for key in self.FIELDS: if key in self.EXCLUDE: continue row = ('formset-%s-%s' % (index, key), record.get(key)) formset.append(row) return formset def proxy(self, dictionary, key): """ Метод реализующий применение обработчика для поля, если таковой был определён в дочерней модели. @type dictionary: dict @param dictionary: Словарь с данными @type key: unicode @param key: Словарный ключ, значение для которого следует получить. """ value = dictionary.get(key, '--') if hasattr(self, 'handler_%s' % key): handler = getattr(self, 'handler_%s' % key) return handler(value) else: return value ## ## Переопределённые методы базовой модели ## def rowCount(self, parent=None): if parent and parent.isValid(): return 0 else: return len(self.storage) def columnCount(self, parent=None): if parent and parent.isValid(): return 0 else: return len(self.FIELDS) def headerData(self, section, orientation, role): """ Метод для вывода заголовков для полей модели. """ # для горизонтального заголовка выводятся названия полей if orientation == Qt.Horizontal and role == Qt.DisplayRole: return QVariant(self.TITLES[section]) # для вертикального заголовка выводятся порядковые номера записей if orientation == Qt.Vertical and role == Qt.DisplayRole: return QVariant(section + 1) # return QVariant() def flags(self, index): """ Свойства полей модели. Разрешаем только отображение. """ return Qt.ItemIsEnabled def data(self, index, role): """ Метод для выдачи данных из модели по указанному индексу и для переданной роли. """ if not index.isValid(): return QVariant('error') row = index.row() col = index.column() key = self.FIELDS[col] if role == Qt.DisplayRole: value = self.proxy(self.storage[row], key) return QVariant(value) elif role == Qt.ToolTipRole: return QVariant() else: return QVariant()
def __init__(self, parent, callback): self.callback = callback self.params = ParamStorage() UiDlgTemplate.__init__(self, parent)
class AssignRent(UiDlgTemplate): """ Класс диалога для добавления аренды. """ ui_file = 'uis/dlg_assign_rent.ui' params = ParamStorage() user_id = None model = None def __init__(self, parent, *args, **kwargs): self.user_id = kwargs.get('renter') UiDlgTemplate.__init__(self, parent) def setupUi(self): UiDlgTemplate.setupUi(self) # настраиваем отображение событий аренды self.model = RentEvent(self) self.tableItems.setModel(self.model) self.tableItems.setSelectionBehavior(QAbstractItemView.SelectRows) header = self.tableItems.horizontalHeader() header.setStretchLastSection(False) header.setResizeMode(QHeaderView.ResizeToContents) header.setResizeMode(0, QHeaderView.Stretch) self.connect(self.toolBegin, SIGNAL('clicked()'), lambda: self.show_calendar(self.dateBegin)) self.connect(self.toolEnd, SIGNAL('clicked()'), lambda: self.show_calendar(self.dateEnd)) self.connect(self.buttonToday, SIGNAL('clicked()'), self.set_date_today) self.connect(self.buttonAdd, SIGNAL('clicked()'), self.add_item) self.connect(self.buttonSave, SIGNAL('clicked()'), self.save_rent) self.connect(self.buttonClose, SIGNAL('clicked()'), self, SLOT('reject()')) def init_data(self, data=dict()): self.rent_id = data.get('uuid') # для новой аренды сразу заполняем даты начала и конца if not self.rent_id: self.set_date_today() # заполняем список зарегистрированных событий аренд self.tableItems.model().init_data(data.get('rent_item_list', [])) def set_date_today(self): """ Метод для установки сегодняшней даты. """ today = QDate(date.today()) self.dateBegin.setDate(today) self.dateEnd.setDate(today) def show_calendar(self, widget): """ Метод для отображения диалога с календарём. """ # определяем обработчик результатов диалога def handle(selected_date): widget.setDate(selected_date) params = {'title': self.tr('Choose a date')} self.dialog = DlgCalendar(self, **params) self.dialog.setModal(True) self.dialog.setCallback(handle) self.dialog.exec_() def add_item(self): """ Метод для отображения диалога для ввода информации о событии аренды. """ self.dialog = AddItem(self, callback=self.add_item_handle) self.dialog.exec_() def add_item_handle(self, data): """ Метод для обработки данных от диалога назначения события аренды. @type data: dict @param data: Словарь с данными диалога. @rtype: boolean @return: Возможность размещения события на расписании. """ d2s = lambda x: x.date().toPyDate().strftime('%Y-%m-%d') mbox_title = self.tr('Assign rent event') # дополняем переданные данные диапазоном дат, в котором # действуют события params = dict(data, begin_date=d2s(self.dateBegin), end_date=d2s(self.dateEnd)) # делаем проверку через сервер http = self.params.http if not http.request('/api/event/', 'POST', params): QMessageBox.critical( self, mbox_title, self.tr('Unable to save: %1').arg(http.error_msg)) return status, response = http.piston() if status == 'ALL_OK': # добавляем информацию о событии в модель if self.model.insert_new(params): #self.price = self.model.price() return True QMessageBox.warning( self, mbox_title, self.tr('Unable to assign: place already busy.')) elif status == 'DUPLICATE_ENTRY': QMessageBox.warning( self, mbox_title, self.tr('Unable to assign: place already busy.')) else: QMessageBox.warning(self, mbox_title, self.tr('Unknown answer: %1').arg(status)) return False def save_rent(self): """ Метод для получения данных из диалога. Диалог вызывается из user_info.RenterInfo.assign_item.""" dialog_title = self.tr('Rent Save') begin_date = self.dateBegin.date().toPyDate().strftime('%Y-%m-%d') end_date = self.dateEnd.date().toPyDate().strftime('%Y-%m-%d') formset = self.model.formset(initial={ 'begin_date': begin_date, 'end_date': end_date, 'is_active': u'on', }) params = formset + [ ('renter', self.user_id), ('desc', unicode(self.editDesc.toPlainText()).encode('utf-8')), ('is_active', u'on'), ] http = self.params.http if not http.request('/api/rent/', 'POST', params): QMessageBox.critical( self, dialog_title, self.tr('Unable to save: %1').arg(http.error_msg)) return status, response = http.piston() if status == 'CREATED': self.rent_id = response.get('uuid') self.buttonAdd.setDisabled(False) QMessageBox.information(self, dialog_title, self.tr('Information is saved.')) else: QMessageBox.critical(self, dialog_title, self.tr('Unable to save: %1').arg(status))