"Fossies" - the Fresh Open Source Software Archive

Member "sofastats-1.5.2/sofa_main/filtselect.py" (3 Sep 2018, 14649 Bytes) of package /linux/misc/sofastats-1.5.2.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "filtselect.py" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 1.4.6_vs_1.5.0.

    1 import wx
    2 
    3 from . import basic_lib as b
    4 from . import my_globals as mg
    5 from . import lib
    6 from . import config_output
    7 from . import getdata
    8 from . import projects
    9 
   10 def get_val(raw_val, flds, fldname):
   11     """
   12     Value is validated first. Raw value will always be a string.
   13 
   14     If numeric, must be a number, an empty string (turned to Null), or any
   15     variant of Null.
   16 
   17     If a date, must be a usable date, an empty string, or Null. Empty strings
   18     are turned to Null. Usable dates are returned as std datetimes.
   19 
   20     If a string, can be anything. Variants of Null are treated specially.
   21 
   22     Null values (or any variant of Null) are turned to None which will be
   23     processed correctly as a Null when clauses are made.
   24     """
   25     debug = False
   26     bolnumeric = flds[fldname][mg.FLD_BOLNUMERIC]
   27     boldatetime = flds[fldname][mg.FLD_BOLDATETIME]
   28     if bolnumeric:
   29         if lib.TypeLib.is_numeric(raw_val):
   30             return float(raw_val)
   31         else:  ## not a num - a valid string?
   32             if isinstance(raw_val, str):
   33                 if raw_val == '' or raw_val.lower() == 'null':
   34                     return None
   35         raise Exception('Only a number, an empty string, or Null can be '
   36             'entered for filtering a numeric field')
   37     elif boldatetime:
   38         usable_datetime = lib.DateLib.is_usable_datetime_str(raw_val)
   39         if usable_datetime:
   40             if debug: print(f"A valid datetime: '{raw_val}'")
   41             return lib.DateLib.get_std_datetime_str(raw_val)
   42         else:  ## not a datetime - a valid string?
   43             if isinstance(raw_val, str):
   44                 if raw_val == '' or raw_val.lower() == 'null':
   45                     return None
   46         raise Exception('Only a datetime, an empty string, or Null can be '
   47             'entered for filtering a datetime field')
   48     else:
   49         if raw_val.lower() == 'null':
   50             return None
   51         else:
   52             return raw_val
   53 
   54 
   55 class DlgFiltSelect(wx.Dialog):
   56     def __init__(self, parent, var_labels, var_notes, var_types, val_dics):
   57         dd = mg.DATADETS_OBJ
   58         self.var_dets = _('Variable Details')
   59         self.var_labels = var_labels
   60         self.var_notes = var_notes
   61         self.var_types = var_types
   62         self.val_dics = val_dics
   63         tbl_filt_label, self.tbl_filt = lib.FiltLib.get_tbl_filt(
   64             dd.dbe, dd.db, dd.tbl)
   65         title = _('Current filter') if self.tbl_filt else _('Apply filter')
   66         wx.Dialog.__init__(self, parent=parent, title=title, 
   67             style=wx.CAPTION|wx.SYSTEM_MENU, pos=(mg.HORIZ_OFFSET+100,100))
   68         self.parent = parent
   69         self.panel = wx.Panel(self)
   70         ## szrs
   71         szr_main = wx.BoxSizer(wx.VERTICAL)
   72         szr_label = wx.BoxSizer(wx.HORIZONTAL)
   73         szr_quick = wx.BoxSizer(wx.HORIZONTAL)
   74         szr_flex = wx.BoxSizer(wx.VERTICAL)
   75         szr_flex_across = wx.BoxSizer(wx.HORIZONTAL)
   76         ## assemble
   77         self.rad_quick = wx.RadioButton(
   78             self.panel, -1, _('Quick'), style=wx.RB_GROUP)
   79         rad_flex = wx.RadioButton(self.panel, -1, _('Flexible'))
   80         self.rad_quick.Bind(wx.EVT_RADIOBUTTON, self.on_rad_quick_sel)
   81         rad_flex.Bind(wx.EVT_RADIOBUTTON, self.on_rad_flex_sel)
   82         btn_help = wx.Button(self.panel, wx.ID_HELP)
   83         btn_help.Bind(wx.EVT_BUTTON, self.on_btn_help)
   84         ## label content
   85         lbl_label = wx.StaticText(self.panel, -1, _('Label (optional):'))
   86         self.txt_label = wx.TextCtrl(self.panel, -1, tbl_filt_label)
   87         szr_label.Add(lbl_label, 0, wx.RIGHT, 10)
   88         szr_label.Add(self.txt_label, 1)
   89         ## quick content
   90         self.drop_vars = wx.Choice(self.panel, -1, size=(300,-1))
   91         self.drop_vars.Bind(wx.EVT_CONTEXT_MENU, self.on_rclick_vars)
   92         self.drop_vars.SetToolTip(
   93             _('Right click variable to view/edit details'))
   94         self.sorted_var_names = []  ## refreshed as required and in order of labels, not raw values
   95         self.setup_vars()
   96         gte_choices = mg.GTES
   97         self.drop_gte = wx.Choice(self.panel, -1, choices=gte_choices)
   98         self.drop_gte.SetSelection(0)
   99         self.txt_val = wx.TextCtrl(self.panel, -1, '')
  100         self.lbl_quick_instructions = wx.StaticText(self.panel, -1,
  101             _("(don't quote strings e.g. John not \"John\". Null for missing)"))
  102         szr_quick.Add(self.rad_quick, 0)
  103         szr_quick.Add(self.drop_vars, 1, wx.LEFT|wx.RIGHT, 5)
  104         szr_quick.Add(self.drop_gte, 0)
  105         szr_quick.Add(self.txt_val, 0)
  106         ## split
  107         ln_split = wx.StaticLine(self.panel)
  108         ## flexible content
  109         self.txt_flex_filter = wx.TextCtrl(
  110             self.panel, -1, '', style=wx.TE_MULTILINE, size=(-1,75))
  111         self.lbl_flex_example = wx.StaticText(
  112             self.panel, -1, _('(enter a filter e.g. agegp > 5)'))
  113         szr_flex_across.Add(rad_flex, 0, wx.RIGHT, 10)
  114         szr_flex_across.Add(btn_help, 0)
  115         szr_flex.Add(szr_flex_across, 0, wx.TOP|wx.BOTTOM, 10)
  116         szr_flex.Add(self.lbl_flex_example, 0)
  117         szr_flex.Add(self.txt_flex_filter, 1, wx.GROW)
  118         if self.tbl_filt:
  119             rad_flex.SetValue(True)
  120             self.enable_quick_dets(False)
  121             self.enable_flex_dets(True)
  122             self.txt_flex_filter.SetValue(self.tbl_filt)
  123         else:
  124             self.rad_quick.SetValue(True)
  125             self.enable_quick_dets(True)
  126             self.enable_flex_dets(False)
  127         self.setup_btns()
  128         szr_main.Add(szr_label, 0, wx.GROW|wx.ALL, 10)
  129         szr_main.Add(szr_quick, 0, wx.ALL, 10)
  130         szr_main.Add(self.lbl_quick_instructions, 0,
  131             wx.ALIGN_RIGHT|wx.LEFT|wx.RIGHT|wx.BOTTOM, 10)
  132         szr_main.Add(ln_split, 0, wx.GROW|wx.LEFT|wx.RIGHT, 10)
  133         szr_main.Add(szr_flex, 0, wx.GROW|wx.ALL, 10)
  134         szr_main.Add(self.szr_btns, 0, wx.ALL|wx.GROW, 10)
  135         self.panel.SetSizer(szr_main)
  136         szr_main.SetSizeHints(self)
  137         self.Layout()
  138         self.txt_label.SetFocus()
  139 
  140     def setup_vars(self, var=None):
  141         var_names = projects.get_approp_var_names()
  142         var_choices, self.sorted_var_names = lib.GuiLib.get_sorted_choice_items(
  143             dic_labels=self.var_labels, vals=var_names)
  144         self.drop_vars.SetItems(var_choices)
  145         idx = self.sorted_var_names.index(var) if var else 0
  146         self.drop_vars.SetSelection(idx)
  147 
  148     def setup_btns(self):
  149         """
  150         Must have ID of wx.ID_... to trigger validators (no event binding
  151         needed) and for std dialog button layout.
  152 
  153         NB can only add some buttons as part of standard sizer to be realised.
  154 
  155         Insert or Add others after the Realize() as required.
  156 
  157         See http://aspn.activestate.com/ASPN/Mail/Message/wxpython-users/3605904
  158         and http://aspn.activestate.com/ASPN/Mail/Message/wxpython-users/3605432
  159         """
  160         btn_var_dets = wx.Button(self.panel, -1, self.var_dets)
  161         btn_var_dets.Bind(wx.EVT_BUTTON, self.on_var_dets)
  162         btn_delete = wx.Button(self.panel, wx.ID_DELETE, _('Remove'))
  163         btn_delete.Bind(wx.EVT_BUTTON, self.on_delete)
  164         btn_cancel = wx.Button(self.panel, wx.ID_CANCEL)
  165         btn_cancel.Bind(wx.EVT_BUTTON, self.on_cancel)
  166         if not self.tbl_filt:
  167             btn_delete.Disable()
  168         btn_ok = wx.Button(self.panel, wx.ID_OK, _('Apply'))
  169         btn_ok.Bind(wx.EVT_BUTTON, self.on_ok)
  170         ## szrs
  171         self.szr_btns = wx.BoxSizer(wx.HORIZONTAL)
  172         szr_extra_btns = wx.BoxSizer(wx.HORIZONTAL)
  173         szr_std_btns = wx.StdDialogButtonSizer()
  174         ## assemble
  175         szr_extra_btns.Add(btn_var_dets, 0, wx.ALIGN_LEFT)
  176         szr_std_btns.AddButton(btn_cancel)
  177         szr_std_btns.AddButton(btn_ok)
  178         szr_std_btns.Realize()
  179         szr_std_btns.Insert(0, btn_delete, 0)
  180         self.szr_btns.Add(szr_extra_btns, 1)
  181         self.szr_btns.Add(szr_std_btns, 0)
  182         btn_ok.SetDefault()
  183 
  184     def on_var_dets(self, event):
  185         """
  186         Open dialog with list of variables. On selection, opens standard get
  187         settings dialog.
  188         """
  189         updated = set()  ## will get populated with a True to indicate update
  190         dlg = config_output.DlgListVars(
  191             self.var_labels, self.var_notes,
  192             self.var_types, self.val_dics, updated)
  193         dlg.ShowModal()
  194         if updated:
  195             idx_var = self.drop_vars.GetSelection()
  196             fldname = self.sorted_var_names[idx_var]
  197             self.setup_vars(var=fldname)
  198         event.Skip()
  199 
  200     def on_delete(self, event):
  201         dd = mg.DATADETS_OBJ
  202         try:
  203             del mg.DBE_TBL_FILTS[dd.dbe][dd.db][dd.tbl]
  204         except KeyError:
  205             raise Exception(
  206                 'Tried to delete filter but not in global dictionary')
  207         self.Destroy()
  208         self.SetReturnCode(wx.ID_DELETE)  ## only for dialogs 
  209         ## (MUST come after Destroy)
  210         event.Skip()
  211 
  212     def on_cancel(self, _event):
  213         self.Destroy()
  214         self.SetReturnCode(wx.ID_CANCEL)  ## only for dialogs 
  215         ## (MUST come after Destroy)
  216 
  217     def on_btn_help(self, event):
  218         demo = self.get_demo()
  219         dlg = lib.DlgHelp(parent=self,
  220             title=_('Filtering Help'), guidance_lbl=_('Filtering Rules'), 
  221             activity_lbl='filtering', guidance=demo, help_pg='filtering_data')
  222         dlg.ShowModal()
  223         event.Skip()
  224 
  225     def get_quick_filter(self):
  226         "Get filter from quick setting"
  227         debug = False
  228         dd = mg.DATADETS_OBJ
  229         idx_var = self.drop_vars.GetSelection()
  230         fldname = self.sorted_var_names[idx_var]
  231         rawval = self.txt_val.GetValue()
  232         val = get_val(rawval, dd.flds, fldname)
  233         gte = self.drop_gte.GetStringSelection()
  234         filt = getdata.make_fld_val_clause(dd.dbe, dd.flds, fldname, val, gte)
  235         if idx_var == 0 and rawval == '':
  236             dlg = wx.MessageDialog(None,
  237                 f'Are you sure you want to apply the filter "{filt}"?',
  238                 'Confirm Filter', wx.YES_NO|wx.ICON_QUESTION)
  239             retval = dlg.ShowModal()
  240             dlg.Destroy()
  241             if retval != wx.ID_YES:
  242                 return
  243         if debug: print(filt)
  244         return filt
  245 
  246     def get_demo(self):
  247         dd = mg.DATADETS_OBJ
  248         val_quoter = getdata.get_val_quoter_func(dd.dbe)
  249         objqtr = getdata.get_obj_quoter_func(dd.dbe)
  250         if dd.dbe == mg.DBE_SQLITE:
  251             sqlite_extra_comment = ' (such as the default SOFA database)'
  252         else:
  253             sqlite_extra_comment = ''
  254         demo = ((_('Filters for %(dbe)s data%(sqlite_extra_comment)s '
  255             'should look like this:') +
  256             '\n\ne.g. %(city)s = %(vancouver)s'
  257             '\ne.g. %(city)s != %(unknown_city)s'
  258             '\ne.g. %(age)s >= 20'
  259             '\ne.g. (%(city)s = %(vancouver)s AND %(age)s >= 20) '
  260             'OR %(gender)s = 2'
  261             '\ne.g. %(satisfaction)s not in (9, 99, 999)'
  262             '\n\nAny valid SQL should work.')
  263             % {'dbe': dd.dbe,
  264                'city': objqtr('city'), 
  265                'vancouver': val_quoter('Vancouver'),
  266                'unknown_city': val_quoter('Unknown City'),
  267                'age': objqtr('age'),
  268                'gender': objqtr('gender'),
  269                'satisfaction': objqtr('satisfaction'),
  270                'sqlite_extra_comment': sqlite_extra_comment})
  271         return demo
  272 
  273     def on_ok(self, event):
  274         debug = False
  275         dd = mg.DATADETS_OBJ
  276         wx.BeginBusyCursor()
  277         tbl_filt_label = self.txt_label.GetValue() 
  278         if self.rad_quick.GetValue():
  279             try:
  280                 tbl_filt = self.get_quick_filter()
  281                 if tbl_filt is None:
  282                     lib.GuiLib.safe_end_cursor()
  283                     wx.MessageBox(
  284                         'Please set a filter and try again. Or just Cancel')
  285                     self.txt_val.SetFocus()
  286                     return
  287             except Exception as e:
  288                 lib.GuiLib.safe_end_cursor()
  289                 wx.MessageBox(_('Problem with design of filter: %s') % b.ue(e))
  290                 self.txt_val.SetFocus()
  291                 return
  292         else:
  293             tbl_filt = self.txt_flex_filter.GetValue()
  294             if not tbl_filt:
  295                 lib.GuiLib.safe_end_cursor()
  296                 wx.MessageBox(_('Please enter a filter'))
  297                 return
  298         ## Must work with a simple query to that database
  299         dbe_tblname = getdata.tblname_qtr(dd.dbe, dd.tbl)
  300         filt_test_SQL = f"SELECT * FROM {dbe_tblname} WHERE ({tbl_filt})"
  301         if debug: print(f'Filter: {filt_test_SQL}')
  302         try:
  303             dd.cur.execute(filt_test_SQL)
  304         except Exception:
  305             demo = self.get_demo()
  306             lib.GuiLib.safe_end_cursor()
  307             wx.MessageBox(
  308                 _("Problem applying filter \"%(filt)s\" to \"%(tbl)s\"")
  309                 % {'filt': tbl_filt, 'tbl': dd.tbl}
  310                 + '\n\n' + demo
  311                 + '\n\nCheck for mistakes in variable names and types '
  312                 "by clicking on the \"%s\" button." % self.var_dets)
  313             return
  314         if dd.dbe not in mg.DBE_TBL_FILTS:
  315             mg.DBE_TBL_FILTS[dd.dbe] = {}
  316         if dd.db not in mg.DBE_TBL_FILTS[dd.dbe]:
  317             mg.DBE_TBL_FILTS[dd.dbe][dd.db] = {}
  318         mg.DBE_TBL_FILTS[dd.dbe][dd.db][dd.tbl] = (tbl_filt_label, tbl_filt)
  319         self.Destroy()
  320         self.SetReturnCode(wx.ID_OK)  ## or nothing happens!  
  321         ## Prebuilt dialogs must do this internally.
  322         event.Skip()
  323 
  324     def on_rad_quick_sel(self, _event):
  325         self.enable_quick_dets(True)
  326         self.enable_flex_dets(False)
  327 
  328     def on_rad_flex_sel(self, _event):
  329         self.enable_quick_dets(False)
  330         self.enable_flex_dets(True)
  331         self.txt_flex_filter.SetFocus()
  332 
  333     def enable_quick_dets(self, enable):
  334         self.drop_vars.Enable(enable)
  335         self.drop_gte.Enable(enable)
  336         self.txt_val.Enable(enable)
  337         self.lbl_quick_instructions.Enable(enable)
  338 
  339     def enable_flex_dets(self, enable):
  340         self.lbl_flex_example.Enable(enable)
  341         self.txt_flex_filter.Enable(enable)
  342 
  343     def get_var(self):
  344         idx = self.drop_vars.GetSelection()
  345         var = self.sorted_var_names[idx]
  346         var_item = self.drop_vars.GetStringSelection()
  347         return var, var_item
  348 
  349     def on_rclick_vars(self, _event):
  350         var_name, choice_item = self.get_var()
  351         var_label = lib.GuiLib.get_item_label(
  352             item_labels=self.var_labels, item_val=var_name)
  353         updated = config_output.set_var_props(choice_item, var_name, var_label,
  354             self.var_labels, self.var_notes, self.var_types, self.val_dics)
  355         if updated:
  356             self.setup_vars(var_name)