[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