Пример #1
0
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
Пример #2
0
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()
Пример #3
0
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()
Пример #4
0
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)
Пример #5
0
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'
Пример #6
0
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)
Пример #7
0
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()
Пример #8
0
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
Пример #9
0
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'))
Пример #10
0
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)
Пример #11
0
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()
Пример #12
0
    def __init__(self, parent, callback):
        self.callback = callback
        self.params = ParamStorage()

        UiDlgTemplate.__init__(self, parent)
Пример #13
0
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))