[PATCH 5/9] Add declarative EditTUISpoke

Martin Sivak msivak at redhat.com
Tue Mar 12 14:09:20 UTC 2013


---
 pyanaconda/iutil.py                  |  36 +++++++
 pyanaconda/ui/tui/spokes/__init__.py | 190 +++++++++++++++++++++++++++++++++--
 2 files changed, 220 insertions(+), 6 deletions(-)

diff --git a/pyanaconda/iutil.py b/pyanaconda/iutil.py
index 30dbb84..d69b081 100644
--- a/pyanaconda/iutil.py
+++ b/pyanaconda/iutil.py
@@ -481,6 +481,42 @@ class ProxyString(object):
     def __str__(self):
         return self.url
 
+def getdeepattr(obj, name):
+    """This behaves as the standard getattr, but supports
+       composite (containing dots) attribute names.
+
+       As an example:
+
+       >>> import os
+       >>> from os.path import split
+       >>> getdeepattr(os, "path.split") == split
+       True
+    """
+
+    for attr in name.split("."):
+        obj = getattr(obj, attr)
+    return obj
+
+def setdeepattr(obj, name, value):
+    """This behaves as the standard setattr, but supports
+       composite (containing dots) attribute names.
+
+       As an example:
+
+       >>> class O:
+       >>>   pass
+       >>> a = O()
+       >>> a.b = O()
+       >>> a.b.c = O()
+       >>> setdeepattr(a, "b.c.d", True)
+       >>> a.b.c.d
+       True
+    """
+    path = name.split(".")
+    for attr in path[:-1]:
+        obj = getattr(obj, attr)
+    return setattr(obj, path[-1], value)
+
 def strip_accents(s):
     """This function takes arbitrary unicode string
     and returns it with all the diacritics removed.
diff --git a/pyanaconda/ui/tui/spokes/__init__.py b/pyanaconda/ui/tui/spokes/__init__.py
index 2be2847..cb61eb3 100644
--- a/pyanaconda/ui/tui/spokes/__init__.py
+++ b/pyanaconda/ui/tui/spokes/__init__.py
@@ -18,15 +18,19 @@
 #
 # Red Hat Author(s): Martin Sivak <msivak at redhat.com>
 #
-from .. import simpleline as tui
-from pyanaconda.ui.tui.tuiobject import TUIObject
+from pyanaconda.ui.tui import simpleline as tui
+from pyanaconda.ui.tui.tuiobject import TUIObject, YesNoDialog
 from pyanaconda.ui.common import Spoke, StandaloneSpoke, NormalSpoke, PersonalizationSpoke, collect
-import os
+from pyanaconda.users import validatePassword
+from pwquality import PWQError
+import re
+from collections import namedtuple
+from pyanaconda.iutil import setdeepattr, getdeepattr
 
 import gettext
 _ = lambda x: gettext.ldgettext("anaconda", x)
 
-__all__ = ["TUISpoke", "StandaloneSpoke", "NormalSpoke", "PersonalizationSpoke",
+__all__ = ["TUISpoke", "EditTUISpoke", "EditTUIDialog", "StandaloneSpoke", "NormalSpoke", "PersonalizationSpoke",
            "collect_spokes", "collect_categories"]
 
 class TUISpoke(TUIObject, tui.Widget, Spoke):
@@ -80,10 +84,184 @@ class TUISpoke(TUIObject, tui.Widget, Spoke):
         c.render(width)
         self.draw(c)
 
-class StandaloneTUISpoke(TUISpoke, StandaloneSpoke):
+class NormalTUISpoke(TUISpoke, NormalSpoke):
     pass
 
-class NormalTUISpoke(TUISpoke, NormalSpoke):
+class EditTUIDialog(NormalTUISpoke):
+    """Spoke/dialog used to read new value of textual or password data"""
+
+    title = _("New value")
+    PASSWORD = re.compile(".*")
+
+    def __init__(self, app, data, storage, payload, instclass):
+        NormalTUISpoke.__init__(self, app, data, storage, payload, instclass)
+        self.value = None
+
+    def refresh(self, args):
+        self._window = []
+        self.value = None
+        return True
+
+    def prompt(self, (title, value, regex, cond)):
+        if regex == self.PASSWORD:
+            pw = self._app.raw_input(_("%s: ") % title, hidden=True)
+            confirm = self._app.raw_input(_("%s (confirm): ") % title, hidden=True)
+            error = None
+            # just returning an error is either blank or mismatched
+            # passwords.  Raising is because of poor quality.
+            try:
+                error = validatePassword(pw, confirm)
+                if error:
+                    print(error)
+                    return None
+            except PWQError as (e, msg):
+                error = _("You have provided a weak password: %s. " % msg)
+                error += _("\nWould you like to use it anyway?")
+                question_window = YesNoDialog(self._app, error)
+                self._app.switch_screen_modal(question_window)
+                if not question_window.answer:
+                    return None
+
+            self.value = pw
+            return None
+        else:
+            return _("Enter new value for '%s' and press enter\n") % title
+
+    def input(self, (title, value, regex, cond), key):
+        if regex.match(key):
+            self.value = key
+            self.close()
+            return True
+        else:
+            return NormalTUISpoke.input(self, (title, value, regex), key)
+
+class OneShotEditTUIDialog(EditTUIDialog):
+    """The same as EditTUIDialog, but closes automatically after
+       the value is read
+    """
+
+    def prompt(self, (title, value, regex, cond)):
+        ret = EditTUIDialog.prompt(self, (title, value, regex, cond))
+        if ret is None:
+            self.close()
+        return ret
+
+EditTUISpokeEntry = namedtuple("EditTUISpokeEntry", ["title", "attribute", "aux", "visible"])
+
+class EditTUISpoke(NormalTUISpoke):
+    """Spoke with declarative semantics, it contains
+       a list of titles, attribute names and regexps
+       that specify the fields of an object the user
+       allowed to edit.
+    """
+
+    # self.data's subattribute name
+    # empty string means __init__ will provide
+    # something else
+    edit_data = ""
+
+    # constants to be used in the aux field
+    # and mark the entry as a password or checkbox field
+    PASSWORD = EditTUIDialog.PASSWORD
+    CHECK = "check"
+
+    # list of fields in the format of named tuples like:
+    # EditTUISpokeEntry(title, attribute, aux, visible)
+    # title     - Nontranslated title of the entry
+    # attribute - The edited object's attribute name
+    # aux       - Compiled regular expression or one of the
+    #             two constants from above.
+    #             It will be used to check the value typed
+    #             by user and to show the proper entry
+    #             for password, text or checkbox.
+    # visible   - True, False or a function that accepts
+    #             two arguments - self and the edited object
+    #             It is evaluated and used to display or
+    #             hide this attribute's entry
+    edit_fields = [
+    ]
+
+    def __init__(self, app, data, storage, payload, instclass):
+        NormalTUISpoke.__init__(self, app, data, storage, payload, instclass)
+        self.dialog = OneShotEditTUIDialog(app, data, storage, payload, instclass)
+
+        # self.args should hold the object this Spoke is supposed
+        # to edit
+        self.args = None
+
+    def refresh(self, args = None):
+        NormalTUISpoke.refresh(self, args)
+
+        if args:
+            self.args = args
+        elif self.edit_data:
+            self.args = self.data
+            for key in self.edit_data.split("."):
+                self.args = getattr(self.args, key)
+
+        def _prep_text(i, entry):
+            number = tui.TextWidget("%2d)" % i)
+            title = tui.TextWidget(_(entry.title))
+            value = getdeepattr(self.args, entry.attribute)
+            value = tui.TextWidget(value)
+
+            return tui.ColumnWidget([(3, [number]), (None, [title, value])], 1)
+
+        def _prep_check(i, entry):
+            number = tui.TextWidget("%2d)" % i)
+            value = getdeepattr(self.args, entry.attribute)
+            ch = tui.CheckboxWidget(title=_(entry.title), completed=bool(value))
+
+            return tui.ColumnWidget([(3, [number]), (None, [ch])], 1)
+
+        def _prep_password(i, entry):
+            number = tui.TextWidget("%2d)" % i)
+            title = tui.TextWidget(_(entry.title))
+            value = getdeepattr(self.args, entry.attribute)
+            value = tui.TextWidget("".join(["*"] * len(value)))
+
+            return tui.ColumnWidget([(3, [number]), (None, [title, value])], 1)
+
+        for idx,entry in enumerate(self.edit_fields):
+            if callable(entry.visible) and not entry.visible(self, self.args):
+                continue
+            elif not callable(entry.visible) and not entry.visible:
+                continue
+
+            entry_type = entry.aux
+            if entry_type == self.PASSWORD:
+                w = _prep_password(idx+1, entry)
+            elif entry_type == self.CHECK:
+                w = _prep_check(idx+1, entry)
+            else:
+                w = _prep_text(idx+1, entry)
+
+            self._window.append(w)
+
+        return True
+
+    def input(self, args, key):
+        try:
+            idx = int(key) - 1
+            if idx >= 0 and idx < len(self.edit_fields):
+                if self.edit_fields[idx].aux == self.CHECK:
+                    setdeepattr(self.args, self.edit_fields[idx].attribute,
+                                not getdeepattr(self.args, self.edit_fields[idx][1]))
+                    self.app.redraw()
+                    self.apply()
+                else:
+                    self.app.switch_screen_modal(self.dialog, self.edit_fields[idx])
+                    if self.dialog.value is not None:
+                        setdeepattr(self.args, self.edit_fields[idx].attribute,
+                                    self.dialog.value)
+                        self.apply()
+                return True
+        except ValueError:
+            pass
+
+        return NormalTUISpoke.input(self, args, key)
+
+class StandaloneTUISpoke(TUISpoke, StandaloneSpoke):
     pass
 
 class PersonalizationTUISpoke(TUISpoke, PersonalizationSpoke):
-- 
1.7.11.7



More information about the anaconda-patches mailing list