Merge lp:~tealeg/landscape-client/settings-ui into lp:~landscape/landscape-client/trunk

Proposed by Geoff Teale
Status: Merged
Approved by: Free Ekanayaka
Approved revision: 475
Merged at revision: 424
Proposed branch: lp:~tealeg/landscape-client/settings-ui
Merge into: lp:~landscape/landscape-client/trunk
Diff against target: 1666 lines (+1496/-29)
12 files modified
landscape/broker/config.py (+4/-1)
landscape/broker/tests/test_config.py (+13/-0)
landscape/configuration.py (+18/-24)
landscape/tests/test_configuration.py (+4/-4)
landscape/ui/controller/app.py (+32/-0)
landscape/ui/controller/configuration.py (+232/-0)
landscape/ui/controller/tests/test_app.py (+124/-0)
landscape/ui/controller/tests/test_configuration.py (+223/-0)
landscape/ui/view/configuration.py (+152/-0)
landscape/ui/view/tests/test_configuration.py (+270/-0)
landscape/ui/view/ui/landscape-client-settings.glade (+407/-0)
scripts/landscape-client-settings-ui (+17/-0)
To merge this branch: bzr merge lp:~tealeg/landscape-client/settings-ui
Reviewer Review Type Date Requested Status
Free Ekanayaka (community) Approve
Fernando Correa Neto (community) Approve
Review via email: mp+87393@code.launchpad.net

Description of the change

Firstly see: https://wiki.canonical.com/Landscape/EEP/GUIPrefs

This branch adds a script "landscape-client-settings-ui" that displays a dialog allowing the user to:

   * Selected either a hosted or dedicated server to connect to.
   * Set the account name and registration password for a hosted instance.
   * Set the server host name for a dedicated server.

In the event that the user is unhappy with their changes they are able to revert them to the set the dialog was initialised with, otherwise closing the dialog will result in the data being written back to the configuration file.

You can run unit test for this code using:

trial landscape.ui

You can run the dialog thus:

./scripts/landscape-client-settings-ui

This branch does *not* do the following:

  * Attempt to re-register the client following changes (this needs discussion)
  * Package the program for installation
  * Install the program into the settings panel.
  * Run in secure context

...all of these things will follow once this branch is merged.

To post a comment you must log in.
452. By Geoff Teale

Resolved divergance and conflicts

453. By Geoff Teale

Fixed typo: configfuration -> configuration

Revision history for this message
Fernando Correa Neto (fcorrea) wrote :
Download full text (6.0 KiB)

Nice work Geoff.

The code looks good, just marking as "Needs fixing" as there are some aesthetics that could be worked out.

[1]

82 + def __init__(self, configuration):
83 + self.__lock_out = True
84 + self.__lock = threading.Lock()
85 + self.__initial_server_host_name = self.DEFAULT_SERVER_HOST_NAME
86 + self.__configuration = configuration
87 + self.__configuration
88 + self.__configuration.load([])
89 + self.__load_data_from_config()
90 + self.__modified = False
91 + self.unlock()

We don't need that self.__configuration in 87.

[2]

93 + def default_dedicated(self):
94 + """
95 + Set L{server_host_name} to something sane when switching from hosted to
96 + dedicated
97 + """

We need a period at the end of line.

[3]

160 + def __derive_server_host_name_from_url(self, url):
161 + "Extract the hostname part from a url"
162 + try:
163 + without_protocol = url[url.index("://") + 3:]
164 + except ValueError:
165 + without_protocol = url
166 + try:
167 + return without_protocol[:without_protocol.index("/")]
168 + except ValueError:
169 + return without_protocol

Maybe we could store the result of urlparse.urlparse(url) and reuse it whenever we need it.
Also, the docstring must be a sentence ending with a period. I've observed it in some other methods as well.

[4]

354 + def setUp(self):
355 + super(LandscapeSettingsApplicationControllerUISetupTest, self).setUp()
356 +
357 + def fake_run(obj):
358 + """
359 + Retard X11 mapping.
360 + """
361 + pass
362 + self._real_run = Gtk.Dialog.run
363 + Gtk.Dialog.run = fake_run

Do you think the test suite will evolve much more for GUI? Maybe we could have another iteration to create some GUI specific base class and add stuff like this in it.

[5]

365 + def get_config():
366 + configdata = """
367 +[client]
368 +data_path = %s
369 +http_proxy = http://proxy.localdomain:3192
370 +tags = a_tag
371 +url = https://landscape.canonical.com/message-system
372 +account_name = foo
373 +registration_password = bar
374 +computer_title = baz
375 +https_proxy = https://proxy.localdomain:6192
376 +ping_url = http://landscape.canonical.com/ping
377 +
378 +""" % sys.path[0]

Maybe we could make it less unindented:

    def get_config():
        configdata = ("[client]"
                     "data_path = %s"
                     "http_proxy = http://proxy.localdomain:3192"
                      "tags = a_tag"
                      "url = https://landscape.canonical.com/message-system"
                      "account_name = foo"
                      "registration_password = bar"
                      "computer_title = baz"
                      "https_proxy = https://proxy.localdomain:6192"
                      "ping_url = http://landscape.canonical.com/ping") % sys.path[0]

[6]

381 + class MyLandscapeSettingsConfiguration(
382 + LandscapeSettingsConfiguration):
383 + default_config_filenames = [config_filena...

Read more...

review: Needs Fixing
Revision history for this message
Free Ekanayaka (free.ekanayaka) wrote :

Hi Geoff, since the changeset is somehow large, please consider it splitting in smaller branches next time. It will speed up reviewing, and possibly let you get early comments.

[1]

-ping_url = http://localhost:8081/ping
+ping_url = http://localhost:8080/ping

Why this? This configuration is supposed to work locally against ./dev/advicedog, I believe this change breaks it.

[2]

landscape/ui/controller/configuration.py:124: redefinition of function 'server_host_name' from line 120
landscape/ui/controller/configuration.py:159: redefinition of function 'account_name' from line 155
landscape/ui/controller/configuration.py:172: redefinition of function 'registration_password' from line 168
landscape/ui/controller/app.py:3: 'register' imported but unused

[3]

+ # register(config)

Is this a leftover?

[4]

+ self.__configuration = configuration
+ self.__configuration
+ self.__configuration.load([])

Second line seems a leftover too.

[5]

I think we could safely drop all "Landscape" prefixes from the class names, as the namespace is clear from the modules path, and probably we don't have to disambiguate anything. This will make things a tad more terse/readable.

[6]

+ return LandscapeSettingsConfiguration([])

and

+class LandscapeSettingsConfiguration(LandscapeSetupConfiguration):

As discussed on IRC, passing [] to the constructor is probably broken, and it'd be good to use LandscapeSetupConfiguration directly. For this you could refactor LandscapeSetupConfiguration like this:

class LandscapeSetupConfiguration(BrokerConfiguration):

    fetch_import_url = fetch_import_url # You will need to move fetch_import_url to the top,
                                        # or even better make it a method of the class if it's
                                        # not use elsewhere, then tests can overwrite it.

and LandscapeSetupConfiguration.__init__ can be dropped entirely.

review: Needs Fixing
Revision history for this message
Geoff Teale (tealeg) wrote :

OK, so I have addressed those issues. Can you re-review please.

454. By Geoff Teale

Convert double underscore prefixed symbols to single underscore prefixed symbols at fcorrea's suggestion

455. By Geoff Teale

Skip tests if GObject Introspection (Gtk3) is not available

456. By Geoff Teale

Remove leftover line

457. By Geoff Teale

Add full stop to comment

458. By Geoff Teale

Fixed some docstrings according to fcorreas suggestions.

459. By Geoff Teale

- Let LandscapeSettingsConfiguration pass None to it's superclass __init__ instead of passing this to each new instance.
- Remove instatiation with empty list from get_config

460. By Geoff Teale

Tidying up in line with comments from fcorrea

461. By Geoff Teale

Tidy up setUp in line with comments from fcorrea

462. By Geoff Teale

Reformat setUp code in-line with comments from fcorrea

463. By Geoff Teale

- Reset the landscape-client.conf to trunk values after accidental check-in

464. By Geoff Teale

Removed Landscape prefix from class names in line with review comments from free.ekanayaka

465. By Geoff Teale

Fixed lint issues

466. By Geoff Teale

- Removed fetch_import_url from __init__ of LandscapeSetupConfiguration
- Made fetch_import_url a method of LandscapeSetupConfiguration
- Fixed tests.

467. By Geoff Teale

Fixed failing tests caused by bad call to __init__ in superclass (old signature).

468. By Geoff Teale

- Fixed more failing tests caused by old signatures

Revision history for this message
Fernando Correa Neto (fcorrea) wrote :

Looks great, +1!

review: Approve
469. By Geoff Teale

Derive Glade file location from module location.

470. By Geoff Teale

Make broker configuration default URL if it is not provided instead of exiting

471. By Geoff Teale

Remove SettingsConfiguration
 - Remove landscape.ui.model.configuration
 - Replace references to SettingsConfiguration with LandscapeSetupConfiguration

Revision history for this message
Free Ekanayaka (free.ekanayaka) wrote :
Download full text (3.8 KiB)

Hi Geoff, some more comments.

[7]

+if os.path.dirname(os.path.abspath(sys.argv[0])) == script_dir:
+ sys.path.insert(0, "./")
+ data_path = os.path.abspath(os.path.join(script_dir, os.path.pardir))
+ print data_path
+else:
+ from landscape.lib.warning import hide_warnings
+ hide_warnings()
+ data_path = None
+ print "B"

I suspect the print statements here are a leftover.

[8]

+ # self.select_landscape_hosting()

This line is commented.

[9]

+ if data_path is None:
+ self._ui_path = os.path.join(
+ controller.data_path, "ui",
+ ClientSettingsDialog.GLADE_FILE)

As discussed on IRC, data_path is for run-time data, better to put the glade file in the same directory as the python module.

[10]

+class SettingsConfiguration(LandscapeSetupConfiguration):
+ required_options = []

As mentioned, instead of subclassing it'd be better to drop the required "url" option from the base class and add a sensible default for it instead (the url of hosted).

[11]

+ with self._lock:

This will make the module fail to import from the tests on Python 2.5.

[12]

+ self._configuration.load([])

As mentioned, this should be passed argv, so we can have:

./scripts/landscape-client-settings-ui -c root-client.conf

(root-client.conf is the default file that you would use for testing a branch against a local server, like ./scripts/landscape-client -c root-client.conf)

[13]

+ @server_host_name.setter
+ def server_host_name(self, value):

I'd rename it to _set_server_host_name, to clearness and to make lint tools happy (there's are redefinition warning). Same for:

+ @account_name.setter
+ def account_name(self, value):

[14]

+ self._configuration.load([])
+ self._load_data_from_config()

This is minor, but I general I find __init__ methods that end up reading files or querying databases or similar "big" side effects a bit odd, and they often make the class tad less testable (indeed you have to use 2 test case classes to, with one to test the empty config file case). They should usually just set some attributes.

What do you think of moving the loading logic to a "load" method (or some other appropriate name), that you can drive from the tests? So we could have a single test like this:

class ConfigControllerTest(LandscapeTest):

    def setUp(self):
        super(ConfigControllerTest, self).setUp()
        config = "[client]"
        config += "data_path = /var/lib/landscape/client\n"
        config += "http_proxy = http://proxy.localdomain:3192\n"
        config += "tags = a_tag\n"
        config += "url = https://landscape.canonical.com/message-system\n"
        config += "account_name = foo\n"
        config += "registration_password = bar\n"
        config += "computer_title = baz\n"
        config += "https_proxy = https://proxy.localdomain:6192\n"
        config += "ping_url = http://landscape.canonical.com/ping\n"
        self.config_filename = self.makeFile(config)

        class MySettingsConfiguration(SettingsConfiguration):
            default_config_filenames = [self.config_filename]

        self.config = MySettingsConfiguration()
...

Read more...

472. By Geoff Teale

Allow landscape-client-settings-ui to accept a configuration file on the command line using the -c switch

473. By Geoff Teale

Replace with statements with explicit lock.acquire() and lock.release() statements to avoid problems with python < 2.6

474. By Geoff Teale

Fixed lint issues

475. By Geoff Teale

Restructured __init__ and tests in line with Free's comments

Revision history for this message
Geoff Teale (tealeg) wrote :

Hi Free.

OK, everything on the above list is now either done, or we have discussed an alternative fix on IRC which I have implemented. Please can you check again.

Revision history for this message
Free Ekanayaka (free.ekanayaka) wrote :

Great work, thanks for bearing with me and addressing all comments :)

+1!

[15]

+ Test that is we don't explicitly pass a URL that this value is

Typos/phrasing, "Test that is" -> "Test that if" and "URL that this" -> "URL, then this"

[16]

landscape/ui/model is now emtpy and can be dropped.

[17]

+ def __init__(self, args=[], data_path=None):

I believe the data_path argument is now unused, and can be dropped (all the way down the stack till ClientSettingsDialog).

[18]

+ def __init__(self, controller, data_path=None, *args, **kwargs):

Are *args and **kwargs necessary? If not, I'd drop them.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'landscape/broker/config.py'
--- landscape/broker/config.py 2011-11-11 16:45:32 +0000
+++ landscape/broker/config.py 2012-01-09 11:38:25 +0000
@@ -11,7 +11,7 @@
11 @cvar required_options: C{["url"]}11 @cvar required_options: C{["url"]}
12 """12 """
1313
14 required_options = ["url"]14 DEFAULT_URL = "https://landscape.canonical.com/message-system"
1515
16 def __init__(self):16 def __init__(self):
17 super(BrokerConfiguration, self).__init__()17 super(BrokerConfiguration, self).__init__()
@@ -115,3 +115,6 @@
115 os.environ["https_proxy"] = self.https_proxy115 os.environ["https_proxy"] = self.https_proxy
116 elif self._original_https_proxy:116 elif self._original_https_proxy:
117 os.environ["https_proxy"] = self._original_https_proxy117 os.environ["https_proxy"] = self._original_https_proxy
118
119 if self.url is None:
120 self.url = self.DEFAULT_URL
118121
=== modified file 'landscape/broker/tests/test_config.py'
--- landscape/broker/tests/test_config.py 2011-08-19 15:47:27 +0000
+++ landscape/broker/tests/test_config.py 2012-01-09 11:38:25 +0000
@@ -78,3 +78,16 @@
78 self.assertEqual(configuration.urgent_exchange_interval, 12)78 self.assertEqual(configuration.urgent_exchange_interval, 12)
79 self.assertEqual(configuration.exchange_interval, 34)79 self.assertEqual(configuration.exchange_interval, 34)
80 self.assertEqual(configuration.ping_interval, 6)80 self.assertEqual(configuration.ping_interval, 6)
81
82 def test_missing_url_is_defaulted(self):
83 """
84 Test that is we don't explicitly pass a URL that this value is
85 defaulted.
86 """
87 filename = self.makeFile("[client]\n")
88
89 configuration = BrokerConfiguration()
90 configuration.load(["--config", filename])
91
92 self.assertEqual(configuration.url,
93 "https://landscape.canonical.com/message-system")
8194
=== modified file 'landscape/configuration.py'
--- landscape/configuration.py 2011-12-01 14:01:29 +0000
+++ landscape/configuration.py 2012-01-09 11:38:25 +0000
@@ -68,9 +68,8 @@
68 unsaved_options = ("no_start", "disable", "silent", "ok_no_register",68 unsaved_options = ("no_start", "disable", "silent", "ok_no_register",
69 "import_from")69 "import_from")
7070
71 def __init__(self, fetch_import_url):71 def __init__(self):
72 super(LandscapeSetupConfiguration, self).__init__()72 super(LandscapeSetupConfiguration, self).__init__()
73 self._fetch_import_url = fetch_import_url
7473
75 def _load_external_options(self):74 def _load_external_options(self):
76 """Handle the --import parameter.75 """Handle the --import parameter.
@@ -89,7 +88,7 @@
89 os.environ["http_proxy"] = self.http_proxy88 os.environ["http_proxy"] = self.http_proxy
90 if self.https_proxy:89 if self.https_proxy:
91 os.environ["https_proxy"] = self.https_proxy90 os.environ["https_proxy"] = self.https_proxy
92 content = self._fetch_import_url(self.import_from)91 content = self.fetch_import_url(self.import_from)
93 parser.readfp(StringIO(content))92 parser.readfp(StringIO(content))
94 elif not os.path.isfile(self.import_from):93 elif not os.path.isfile(self.import_from):
95 raise ImportOptionError("File %s doesn't exist." %94 raise ImportOptionError("File %s doesn't exist." %
@@ -109,6 +108,21 @@
109 options.update(self._command_line_options)108 options.update(self._command_line_options)
110 self._command_line_options = options109 self._command_line_options = options
111110
111 def fetch_import_url(self, url):
112 """Handle fetching of URLs passed to --url."""
113
114 print_text("Fetching configuration from %s..." % url)
115 error_message = None
116 try:
117 content = fetch(url)
118 except FetchError, error:
119 error_message = str(error)
120 if error_message is not None:
121 raise ImportOptionError(
122 "Couldn't download configuration from %s: %s" %
123 (url, error_message))
124 return content
125
112 def make_parser(self):126 def make_parser(self):
113 """127 """
114 Specialize the parser, adding configure-specific options.128 Specialize the parser, adding configure-specific options.
@@ -584,28 +598,8 @@
584 return result598 return result
585599
586600
587def fetch_import_url(url):
588 """Handle fetching of URLs passed to --url.
589
590 This is done out of LandscapeSetupConfiguration since it has to deal
591 with interaction with the user and downloading of files.
592 """
593
594 print_text("Fetching configuration from %s..." % url)
595 error_message = None
596 try:
597 content = fetch(url)
598 except FetchError, error:
599 error_message = str(error)
600 if error_message is not None:
601 raise ImportOptionError(
602 "Couldn't download configuration from %s: %s" %
603 (url, error_message))
604 return content
605
606
607def main(args):601def main(args):
608 config = LandscapeSetupConfiguration(fetch_import_url)602 config = LandscapeSetupConfiguration()
609 if args in (["-h"], ["--help"]):603 if args in (["-h"], ["--help"]):
610 # We let landscape-config --help to be run as normal user604 # We let landscape-config --help to be run as normal user
611 config.load(args)605 config.load(args)
612606
=== modified file 'landscape/tests/test_configuration.py'
--- landscape/tests/test_configuration.py 2011-12-01 14:01:29 +0000
+++ landscape/tests/test_configuration.py 2012-01-09 11:38:25 +0000
@@ -11,7 +11,7 @@
11 print_text, LandscapeSetupScript, LandscapeSetupConfiguration,11 print_text, LandscapeSetupScript, LandscapeSetupConfiguration,
12 register, setup, main, setup_init_script_and_start_client,12 register, setup, main, setup_init_script_and_start_client,
13 stop_client_and_disable_init_script, ConfigurationError,13 stop_client_and_disable_init_script, ConfigurationError,
14 fetch_import_url, ImportOptionError, store_public_key_data)14 ImportOptionError, store_public_key_data)
15from landscape.broker.registration import InvalidCredentialsError15from landscape.broker.registration import InvalidCredentialsError
16from landscape.sysvconfig import SysVConfig, ProcessError16from landscape.sysvconfig import SysVConfig, ProcessError
17from landscape.tests.helpers import (17from landscape.tests.helpers import (
@@ -27,7 +27,7 @@
27url = https://landscape.canonical.com/message-system27url = https://landscape.canonical.com/message-system
28""")28""")
29 args.extend(["--config", filename, "--data-path", self.makeDir()])29 args.extend(["--config", filename, "--data-path", self.makeDir()])
30 config = LandscapeSetupConfiguration(fetch_import_url)30 config = LandscapeSetupConfiguration()
31 config.load(args)31 config.load(args)
32 return config32 return config
3333
@@ -91,7 +91,7 @@
9191
92 class MyLandscapeSetupConfiguration(LandscapeSetupConfiguration):92 class MyLandscapeSetupConfiguration(LandscapeSetupConfiguration):
93 default_config_filenames = [self.config_filename]93 default_config_filenames = [self.config_filename]
94 self.config = MyLandscapeSetupConfiguration(None)94 self.config = MyLandscapeSetupConfiguration()
95 self.script = LandscapeSetupScript(self.config)95 self.script = LandscapeSetupScript(self.config)
9696
97 def test_show_help(self):97 def test_show_help(self):
@@ -1889,7 +1889,7 @@
1889 When registration fails because of an unknown error, a message is1889 When registration fails because of an unknown error, a message is
1890 printed and the program exits.1890 printed and the program exits.
1891 """1891 """
1892 configuration = LandscapeSetupConfiguration(None)1892 configuration = LandscapeSetupConfiguration()
18931893
1894 # We'll just mock the remote here to have it raise an exception.1894 # We'll just mock the remote here to have it raise an exception.
1895 connector_factory = self.mocker.replace(1895 connector_factory = self.mocker.replace(
18961896
=== added directory 'landscape/ui'
=== added file 'landscape/ui/__init__.py'
=== added directory 'landscape/ui/controller'
=== added file 'landscape/ui/controller/__init__.py'
=== added file 'landscape/ui/controller/app.py'
--- landscape/ui/controller/app.py 1970-01-01 00:00:00 +0000
+++ landscape/ui/controller/app.py 2012-01-09 11:38:25 +0000
@@ -0,0 +1,32 @@
1from gi.repository import Gtk
2
3from landscape.configuration import LandscapeSetupConfiguration
4from landscape.ui.view.configuration import ClientSettingsDialog
5from landscape.ui.controller.configuration import ConfigController
6
7
8APPLICATION_ID = "com.canonical.landscape-client.settings.ui"
9
10
11class SettingsApplicationController(Gtk.Application):
12 """
13 Core application controller for the landscape settings application.
14 """
15
16 def __init__(self, args=[], data_path=None):
17 super(SettingsApplicationController, self).__init__(
18 application_id=APPLICATION_ID)
19 self._args = args
20 self.data_path = data_path
21 self.connect("activate", self.setup_ui)
22
23 def get_config(self):
24 return LandscapeSetupConfiguration()
25
26 def setup_ui(self, data=None):
27 config = self.get_config()
28 controller = ConfigController(config, args=self._args)
29 controller.load()
30 self.settings_dialog = ClientSettingsDialog(controller,
31 data_path=self.data_path)
32 self.settings_dialog.run()
033
=== added file 'landscape/ui/controller/configuration.py'
--- landscape/ui/controller/configuration.py 1970-01-01 00:00:00 +0000
+++ landscape/ui/controller/configuration.py 2012-01-09 11:38:25 +0000
@@ -0,0 +1,232 @@
1import threading
2
3
4class ConfigControllerLockError(Exception):
5 pass
6
7
8class ConfigController(object):
9 """
10 L{ConfigContoller} defines actions to take against a configuration object,
11 providing starting values from the file, allowing them to be changed
12 transiently, reverted or committed.
13 """
14
15 HOSTED_HOST_NAME = "landscape.canonical.com"
16 DEFAULT_SERVER_HOST_NAME = "landscape.localdomain"
17
18 def __init__(self, configuration, args=[]):
19 self._initial_server_host_name = self.DEFAULT_SERVER_HOST_NAME
20 self._configuration = configuration
21 self._args = args
22 self._lock_out = False
23 self._lock = threading.Lock()
24
25 def load(self):
26 "Load the initial data from the configuration"
27 self.lock()
28 self._configuration.load(self._args)
29 self._pull_data_from_config()
30 self._modified = False
31 self.unlock()
32
33 def default_dedicated(self):
34 """
35 Set L{server_host_name} to something sane when switching from hosted to
36 dedicated.
37 """
38 if self._initial_server_host_name != self.HOSTED_HOST_NAME:
39 self._server_host_name = self._initial_server_host_name
40 else:
41 self._server_host_name = self.DEFAULT_SERVER_HOST_NAME
42 self._url = self._derive_url_from_host_name(
43 self._server_host_name)
44 self._ping_url = self._derive_ping_url_from_host_name(
45 self._server_host_name)
46 self._modified = True
47
48 def default_hosted(self):
49 """
50 Set L{server_host_name} in a recoverable fashion when switching from
51 dedicated to hosted.
52 """
53 if self._server_host_name != self.HOSTED_HOST_NAME:
54 self._server_host_name = self.HOSTED_HOST_NAME
55 self._url = self._derive_url_from_host_name(
56 self._server_host_name)
57 self._ping_url = self._derive_ping_url_from_host_name(
58 self._server_host_name)
59 self._modified = True
60
61 def _pull_data_from_config(self):
62 """
63 Pull in data set from configuration class.
64 """
65 self._lock.acquire()
66 self._data_path = self._configuration.data_path
67 self._http_proxy = self._configuration.http_proxy
68 self._tags = self._configuration.tags
69 self._url = self._configuration.url
70 self._ping_url = self._configuration.ping_url
71 self._account_name = self._configuration.account_name
72 self._registration_password = \
73 self._configuration.registration_password
74 self._computer_title = self._configuration.computer_title
75 self._https_proxy = self._configuration.https_proxy
76 self._ping_url = self._configuration.ping_url
77 if self._url:
78 self._server_host_name = \
79 self._derive_server_host_name_from_url(self._url)
80 else:
81 self._server_host_name = self.HOSTED_HOST_NAME
82 self._initial_server_host_name = self._server_host_name
83 self._modified = False
84 self._lock.release()
85
86 def lock(self):
87 "Block updates to the data set."
88 self._lock.acquire()
89 self._lock_out = True
90 self._lock.release()
91
92 def unlock(self):
93 "Allow updates to the data set."
94 self._lock.acquire()
95 self._lock_out = False
96 self._lock.release()
97
98 def is_locked(self):
99 "Check if updates are locked out."
100 self._lock.acquire()
101 lock_state = self._lock_out
102 self._lock.release()
103 return lock_state
104
105 def _derive_server_host_name_from_url(self, url):
106 "Extract the hostname part from a URL."
107 try:
108 without_protocol = url[url.index("://") + 3:]
109 except ValueError:
110 without_protocol = url
111 try:
112 return without_protocol[:without_protocol.index("/")]
113 except ValueError:
114 return without_protocol
115
116 def _derive_url_from_host_name(self, host_name):
117 "Extrapolate a url from a host name."
118 #Reuse this code to make sure it's a proper host name
119 host_name = self._derive_server_host_name_from_url(host_name)
120 return "https://" + host_name + "/message-system"
121
122 def _derive_ping_url_from_host_name(self, host_name):
123 "Extrapolate a ping_url from a host name."
124 #Reuse this code to make sure it's a proper host name
125 host_name = self._derive_server_host_name_from_url(host_name)
126 return "http://" + host_name + "/ping"
127
128 def _get_server_host_name(self):
129 return self._server_host_name
130
131 def _set_server_host_name(self, value):
132 self._lock.acquire()
133 if self._lock_out:
134 self._lock.release()
135 raise ConfigControllerLockError
136 else:
137 if value != self.HOSTED_HOST_NAME:
138 self._initial_server_host_name = value
139 self._server_host_name = value
140 self._url = self._derive_url_from_host_name(
141 self._server_host_name)
142 self._ping_url = self._derive_ping_url_from_host_name(
143 self._server_host_name)
144 self._modified = True
145 self._lock.release()
146 server_host_name = property(_get_server_host_name, _set_server_host_name)
147
148 @property
149 def data_path(self):
150 return self._data_path
151
152 @property
153 def url(self):
154 return self._url
155
156 @property
157 def http_proxy(self):
158 return self._http_proxy
159
160 @property
161 def tags(self):
162 return self._tags
163
164 def _get_account_name(self):
165 return self._account_name
166
167 def _set_account_name(self, value):
168 self._lock.acquire()
169 if self._lock_out:
170 self._lock.release()
171 raise ConfigControllerLockError
172 else:
173 self._account_name = value
174 self._modified = True
175 self._lock.release()
176 account_name = property(_get_account_name, _set_account_name)
177
178 def _get_registration_password(self):
179 return self._registration_password
180
181 def _set_registration_password(self, value):
182 self._lock.acquire()
183 if self._lock_out:
184 self._lock.release()
185 raise ConfigControllerLockError
186 else:
187 self._registration_password = value
188 self._modified = True
189 self._lock.release()
190 registration_password = property(_get_registration_password,
191 _set_registration_password)
192
193 @property
194 def computer_title(self):
195 return self._computer_title
196
197 @property
198 def https_proxy(self):
199 return self._https_proxy
200
201 @property
202 def ping_url(self):
203 return self._ping_url
204
205 @property
206 def hosted(self):
207 return self.server_host_name == self.HOSTED_HOST_NAME
208
209 @property
210 def is_modified(self):
211 return self._modified
212
213 def revert(self):
214 "Revert settings to those the configuration object originally found."
215 self._configuration.reload()
216 self._pull_data_from_config()
217
218 def commit(self):
219 "Persist settings via the configuration object."
220 self._configuration.data_path = self._data_path
221 self._configuration.http_proxy = self._http_proxy
222 self._configuration.tags = self._tags
223 self._configuration.url = self._url
224 self._configuration.ping_url = self._ping_url
225 self._configuration.account_name = self._account_name
226 self._configuration.registration_password = \
227 self._registration_password
228 self._configuration.computer_title = self._computer_title
229 self._configuration.https_proxy = self._https_proxy
230 self._configuration.ping_url = self._ping_url
231 self._configuration.write()
232 self._modified = False
0233
=== added directory 'landscape/ui/controller/tests'
=== added file 'landscape/ui/controller/tests/__init__.py'
=== added file 'landscape/ui/controller/tests/test_app.py'
--- landscape/ui/controller/tests/test_app.py 1970-01-01 00:00:00 +0000
+++ landscape/ui/controller/tests/test_app.py 2012-01-09 11:38:25 +0000
@@ -0,0 +1,124 @@
1import sys
2
3try:
4 from gi.repository import Gtk
5 got_gobject_introspection = True
6except ImportError:
7 got_gobject_introspection = False
8 gobject_skip_message = "GObject Introspection module unavailable"
9
10
11from landscape.tests.helpers import LandscapeTest
12from landscape.ui.controller.app import SettingsApplicationController
13from landscape.ui.controller.configuration import ConfigController
14from landscape.ui.view.configuration import ClientSettingsDialog
15from landscape.configuration import LandscapeSetupConfiguration
16
17
18class ConnectionRecordingSettingsApplicationController(
19 SettingsApplicationController):
20
21 _connections = set()
22 _connection_args = {}
23 _connection_kwargs = {}
24
25 def __init__(self, get_config_f=None):
26 super(ConnectionRecordingSettingsApplicationController,
27 self).__init__()
28 if get_config_f:
29 self.get_config = get_config_f
30
31 def _make_connection_name(self, signal, func):
32 return signal + ">" + func.__name__
33
34 def _record_connection(self, signal, func, *args, **kwargs):
35 connection_name = self._make_connection_name(signal, func)
36 self._connections.add(connection_name)
37 signal_connection_args = self._connection_args.get(
38 connection_name, [])
39 signal_connection_args.append(repr(args))
40 self._connection_args = signal_connection_args
41 signal_connection_kwargs = self._connection_kwargs.get(
42 connection_name, [])
43 signal_connection_kwargs.append(repr(kwargs))
44 self._connection_kwargs = signal_connection_kwargs
45
46 def is_connected(self, signal, func):
47 connection_name = self._make_connection_name(signal, func)
48 return self._connections.issuperset(set([connection_name]))
49
50 def connect(self, signal, func, *args, **kwargs):
51 self._record_connection(signal, func)
52
53
54class SettingsApplicationControllerInitTest(LandscapeTest):
55
56 def setUp(self):
57 super(SettingsApplicationControllerInitTest, self).setUp()
58
59 def test_init(self):
60 """
61 Test we connect activate to something useful on application
62 initialisation.
63 """
64 app = ConnectionRecordingSettingsApplicationController()
65 self.assertTrue(app.is_connected("activate", app.setup_ui))
66
67 if not got_gobject_introspection:
68 test_init.skip = gobject_skip_message
69
70
71class SettingsApplicationControllerUISetupTest(LandscapeTest):
72
73 def setUp(self):
74 super(SettingsApplicationControllerUISetupTest, self).setUp()
75
76 def fake_run(obj):
77 """
78 Retard X11 mapping.
79 """
80 pass
81
82 self._real_run = Gtk.Dialog.run
83 Gtk.Dialog.run = fake_run
84
85 def get_config():
86 configdata = "[client]\n"
87 configdata += "data_path = %s\n" % sys.path[0]
88 configdata += "http_proxy = http://proxy.localdomain:3192\n"
89 configdata += "tags = a_tag\n"
90 configdata += \
91 "url = https://landscape.canonical.com/message-system\n"
92 configdata += "account_name = foo\n"
93 configdata += "registration_password = bar\n"
94 configdata += "computer_title = baz\n"
95 configdata += "https_proxy = https://proxy.localdomain:6192\n"
96 configdata += "ping_url = http://landscape.canonical.com/ping\n"
97 config_filename = self.makeFile(configdata)
98
99 class MyLandscapeSetupConfiguration(LandscapeSetupConfiguration):
100 default_config_filenames = [config_filename]
101
102 config = MyLandscapeSetupConfiguration()
103 return config
104
105 self.app = ConnectionRecordingSettingsApplicationController(
106 get_config_f=get_config)
107
108 def tearDown(self):
109 Gtk.Dialog.run = self._real_run
110 super(
111 SettingsApplicationControllerUISetupTest, self).tearDown()
112
113 def test_setup_ui(self):
114 """
115 Test that we correctly setup the L{ClientSettingsDialog} with
116 the config object and correct data
117 """
118 self.app.setup_ui(data=None)
119 self.assertIsInstance(self.app.settings_dialog, ClientSettingsDialog)
120 self.assertIsInstance(self.app.settings_dialog.controller,
121 ConfigController)
122
123 if not got_gobject_introspection:
124 test_setup_ui.skip = gobject_skip_message
0125
=== added file 'landscape/ui/controller/tests/test_configuration.py'
--- landscape/ui/controller/tests/test_configuration.py 1970-01-01 00:00:00 +0000
+++ landscape/ui/controller/tests/test_configuration.py 2012-01-09 11:38:25 +0000
@@ -0,0 +1,223 @@
1from landscape.tests.helpers import LandscapeTest
2from landscape.ui.controller.configuration import (
3 ConfigController, ConfigControllerLockError)
4from landscape.configuration import LandscapeSetupConfiguration
5
6
7class ConfigControllerTest(LandscapeTest):
8
9 def setUp(self):
10 super(ConfigControllerTest, self).setUp()
11 config = "[client]"
12 config += "data_path = /var/lib/landscape/client\n"
13 config += "http_proxy = http://proxy.localdomain:3192\n"
14 config += "tags = a_tag\n"
15 config += "url = https://landscape.canonical.com/message-system\n"
16 config += "account_name = foo\n"
17 config += "registration_password = bar\n"
18 config += "computer_title = baz\n"
19 config += "https_proxy = https://proxy.localdomain:6192\n"
20 config += "ping_url = http://landscape.canonical.com/ping\n"
21 self.config_filename = self.makeFile(config)
22
23 class MyLandscapeSetupConfiguration(LandscapeSetupConfiguration):
24 default_config_filenames = [self.config_filename]
25
26 self.config = MyLandscapeSetupConfiguration()
27 self.controller = ConfigController(self.config)
28
29 def test_init(self):
30 """
31 Test that when we create a controller it has initial state read in
32 directly from the configuration file.
33 """
34 self.controller.load()
35 self.assertEqual(self.controller.data_path,
36 "/var/lib/landscape/client/")
37 self.assertEqual(self.controller.http_proxy,
38 "http://proxy.localdomain:3192")
39 self.assertEqual(self.controller.tags, "a_tag")
40 self.assertEqual(self.controller.url,
41 "https://landscape.canonical.com/message-system")
42 self.assertEqual(self.controller.account_name, "foo")
43 self.assertEqual(self.controller.registration_password, "bar")
44 self.assertEqual(self.controller.computer_title, "baz")
45 self.assertEqual(self.controller.https_proxy,
46 "https://proxy.localdomain:6192")
47 self.assertEqual(self.controller.ping_url,
48 "http://landscape.canonical.com/ping")
49 self.assertEqual(self.controller.server_host_name,
50 "landscape.canonical.com")
51
52 def test_set_server_hostname(self):
53 """
54 Test we can set the server_hostname correctly, and derive L{url} and
55 L{ping_url} from it.
56 """
57 self.controller.load()
58 self.assertEqual(self.controller.url,
59 "https://landscape.canonical.com/message-system")
60 self.assertEqual(self.controller.ping_url,
61 "http://landscape.canonical.com/ping")
62 self.assertEqual(self.controller.server_host_name,
63 "landscape.canonical.com")
64 new_server_host_name = "landscape.localdomain"
65 self.controller.server_host_name = new_server_host_name
66 self.assertEqual(self.controller.server_host_name,
67 new_server_host_name)
68 self.assertEqual(self.controller.url,
69 "https://landscape.localdomain/message-system")
70 self.assertEqual(self.controller.ping_url,
71 "http://landscape.localdomain/ping")
72
73 def test_setting_server_host_name_also_sets_hosted(self):
74 """
75 Test that when we set the L{server_host_name} the L{hosted} value is
76 also derived.
77 """
78 self.controller.load()
79 self.assertTrue(self.controller.hosted)
80 self.controller.server_host_name = "landscape.localdomain"
81 self.assertFalse(self.controller.hosted)
82 self.controller.server_host_name = "landscape.canonical.com"
83 self.assertTrue(self.controller.hosted)
84
85 def test_set_account_name(self):
86 """
87 Test that we can set the L{account_name} property.
88 """
89 self.controller.load()
90 self.assertEqual(self.controller.account_name, "foo")
91 self.controller.account_name = "shoe"
92 self.assertEqual(self.controller.account_name, "shoe")
93
94 def test_set_registration_password(self):
95 """
96 Test that we can set the L{registration_password} property.
97 """
98 self.controller.load()
99 self.assertEquals(self.controller.registration_password, "bar")
100 self.controller.registration_password = "nucker"
101 self.assertEquals(self.controller.registration_password, "nucker")
102
103 def test_revert(self):
104 """
105 Test that we can revert the controller to it's initial state.
106 """
107 self.controller.load()
108 self.assertEqual(self.controller.server_host_name,
109 "landscape.canonical.com")
110 self.controller.server_host_name = "landscape.localdomain"
111 self.assertEqual(self.controller.server_host_name,
112 "landscape.localdomain")
113 self.controller.revert()
114 self.assertEqual(self.controller.server_host_name,
115 "landscape.canonical.com")
116
117 def test_is_modified(self):
118 """
119 Test that we can determine when something has been modified.
120 """
121 self.controller.load()
122 self.assertFalse(self.controller.is_modified)
123 self.controller.server_host_name = "bing.bang.a.bang"
124 self.assertTrue(self.controller.is_modified)
125 self.controller.revert()
126 self.assertFalse(self.controller.is_modified)
127 self.controller.account_name = "soldierBlue"
128 self.assertTrue(self.controller.is_modified)
129 self.controller.revert()
130 self.assertFalse(self.controller.is_modified)
131 self.controller.registration_password = "HesAnIndianCowboyInTheRodeo"
132 self.assertTrue(self.controller.is_modified)
133
134 def test_commit(self):
135 """
136 Test that we can write configuration settings back to the config file.
137 """
138 self.controller.load()
139 self.assertEqual(self.controller.server_host_name,
140 "landscape.canonical.com")
141 self.controller.server_host_name = "landscape.localdomain"
142 self.assertEqual(self.controller.server_host_name,
143 "landscape.localdomain")
144 self.controller.commit()
145 self.assertEqual(self.controller.server_host_name,
146 "landscape.localdomain")
147 self.controller.revert()
148 self.assertEqual(self.controller.server_host_name,
149 "landscape.localdomain")
150
151 def test_lock(self):
152 """
153 Test that we can lock out updates.
154 """
155 self.controller.load()
156 self.controller.lock()
157 self.assertRaises(ConfigControllerLockError, setattr, self.controller,
158 "server_host_name", "faily.fail.com")
159 self.assertFalse(self.controller.is_modified)
160 self.controller.unlock()
161 self.controller.server_host_name = "successy.success.org"
162 self.assertTrue(self.controller.is_modified)
163
164 self.controller.revert()
165 self.assertFalse(self.controller.is_modified)
166
167 self.controller.lock()
168 self.assertRaises(ConfigControllerLockError,
169 setattr,
170 self.controller,
171 "account_name",
172 "Failbert")
173 self.assertFalse(self.controller.is_modified)
174 self.controller.unlock()
175 self.assertFalse(self.controller.is_modified)
176 self.controller.account_name = "Winbob"
177 self.assertTrue(self.controller.is_modified)
178
179 self.controller.revert()
180 self.assertFalse(self.controller.is_modified)
181
182 self.controller.lock()
183 self.assertRaises(ConfigControllerLockError,
184 setattr,
185 self.controller,
186 "registration_password",
187 "I Fail")
188 self.assertFalse(self.controller.is_modified)
189 self.controller.unlock()
190 self.assertFalse(self.controller.is_modified)
191 self.controller.registration_password = "I Win"
192 self.assertTrue(self.controller.is_modified)
193
194 def test_defaulting(self):
195 """
196 Test we set the correct values when switching between hosted and
197 dedicated.
198 """
199 self.makeFile("", path=self.config_filename) # empty the config file
200 self.controller.load()
201 self.assertEqual(self.controller.account_name, None)
202 self.assertEqual(self.controller.registration_password, None)
203 self.assertEqual(self.controller.server_host_name,
204 "landscape.canonical.com")
205 self.controller.default_dedicated()
206 self.assertEqual(self.controller.account_name, None)
207 self.assertEqual(self.controller.registration_password, None)
208 self.assertEqual(self.controller.server_host_name,
209 "landscape.localdomain")
210 self.controller.default_hosted()
211 self.assertEqual(self.controller.account_name, None)
212 self.assertEqual(self.controller.registration_password, None)
213 self.assertEqual(self.controller.server_host_name,
214 "landscape.canonical.com")
215 self.controller.default_dedicated()
216 self.controller.server_host_name = "test.machine"
217 self.controller.default_dedicated()
218 self.assertEqual(self.controller.server_host_name, "test.machine")
219 self.controller.default_hosted()
220 self.assertEqual(self.controller.server_host_name,
221 "landscape.canonical.com")
222 self.controller.default_dedicated()
223 self.assertEqual(self.controller.server_host_name, "test.machine")
0224
=== added directory 'landscape/ui/model'
=== added file 'landscape/ui/model/__init__.py'
=== added directory 'landscape/ui/view'
=== added file 'landscape/ui/view/__init__.py'
=== added file 'landscape/ui/view/configuration.py'
--- landscape/ui/view/configuration.py 1970-01-01 00:00:00 +0000
+++ landscape/ui/view/configuration.py 2012-01-09 11:38:25 +0000
@@ -0,0 +1,152 @@
1import os
2
3from gi.repository import Gtk
4
5
6class ClientSettingsDialog(Gtk.Dialog):
7
8 GLADE_FILE = "landscape-client-settings.glade"
9
10 def __init__(self, controller, data_path=None, *args, **kwargs):
11 super(ClientSettingsDialog, self).__init__(*args, **kwargs)
12 self.controller = controller
13 self._ui_path = os.path.join(
14 os.path.dirname(__file__), "ui",
15 ClientSettingsDialog.GLADE_FILE)
16 self._builder = Gtk.Builder()
17 self._builder.add_from_file(self._ui_path)
18 self._setup_ui()
19 self._hosted_toggle = None
20 self._dedicated_toggle = None
21 self.revert(self._revert_button)
22
23 def _setup_window(self):
24 """
25 Configure the dialog window and pack content from the Glade UI file
26 into the main content area.
27 """
28 self.set_title("Client Settings")
29 content_area = self.get_content_area()
30 vbox = self._builder.get_object(
31 "landscape-client-settings-dialog-vbox")
32 vbox.unparent()
33 content_area.pack_start(vbox, expand=True, fill=True, padding=0)
34
35 def _setup_action_buttons(self):
36 """
37 Obtain handles for action buttons and connect them to handlers.
38 """
39 self._close_button = self._builder.get_object("close-button")
40 self._revert_button = self._builder.get_object("revert-button")
41 self._revert_button.connect("clicked", self.revert)
42 self._close_button.connect("clicked", self._possibly_save_and_exit)
43
44 def _setup_entries(self):
45 """
46 Obtain handles for entry widgets, set initial state and connect them to
47 handlers.
48 """
49 self._account_entry = self._builder.get_object("account-name-entry")
50 self._password_entry = self._builder.get_object(
51 "reigstered-password-entry")
52 self._server_host_name_entry = self._builder.get_object(
53 "server-host-name-entry")
54 self._account_entry.set_sensitive(False)
55 self._password_entry.set_sensitive(False)
56 self._server_host_name_entry.set_sensitive(False)
57 self._account_entry.connect("changed", self._update_account)
58 self._password_entry.connect("changed", self._update_password)
59 self._server_host_name_entry.connect("changed",
60 self._update_server_host_name)
61
62 def _setup_radiobuttons(self):
63 """
64 Obtain handles on radiobuttons and connect them to handler.
65 """
66 self._hosted_radiobutton = self._builder.get_object(
67 "hosted-radiobutton")
68 self._dedicated_radiobutton = self._builder.get_object(
69 "dedicated-radiobutton")
70
71 def _setup_ui(self):
72 self._setup_window()
73 self._setup_radiobuttons()
74 self._setup_entries()
75 self._setup_action_buttons()
76
77 def _load_data(self):
78 self.controller.lock()
79 if not self.controller.account_name is None:
80 self._account_entry.set_text(self.controller.account_name)
81 if not self.controller.registration_password is None:
82 self._password_entry.set_text(
83 self.controller.registration_password)
84 if not self.controller.server_host_name is None:
85 self._server_host_name_entry.set_text(
86 self.controller.server_host_name)
87 self.controller.unlock()
88
89 def _update_account(self, event):
90 if not self.controller.is_locked():
91 self.controller.account_name = self._account_entry.get_text()
92
93 def _update_password(self, event):
94 if not self.controller.is_locked():
95 self.controller.registration_password = \
96 self._password_entry.get_text()
97
98 def _update_server_host_name(self, event):
99 if not self.controller.is_locked():
100 self.controller.server_host_name = \
101 self._server_host_name_entry.get_text()
102
103 def _set_entry_sensitivity(self, hosted):
104 if hosted:
105 self._account_entry.set_sensitive(True)
106 self._password_entry.set_sensitive(True)
107 self._server_host_name_entry.set_sensitive(False)
108 else:
109 self._account_entry.set_sensitive(False)
110 self._password_entry.set_sensitive(False)
111 self._server_host_name_entry.set_sensitive(True)
112
113 def select_landscape_hosting(self):
114 hosted = self._hosted_radiobutton.get_active()
115 self._set_entry_sensitivity(hosted)
116 if hosted:
117 self.controller.default_hosted()
118 self._load_data()
119 else:
120 self.controller.default_dedicated()
121 self._load_data()
122
123 def _on_toggle_server_type_radiobutton(self, radiobutton):
124 self.select_landscape_hosting()
125 return True
126
127 def revert(self, button):
128 self.controller.revert()
129 if self._hosted_toggle:
130 self._hosted_radiobutton.disconnect(self._hosted_toggle)
131 if self._dedicated_toggle:
132 self._dedicated_radiobutton.disconnect(self._dedicated_toggle)
133 if self.controller.hosted:
134 self._hosted_radiobutton.set_active(True)
135 else:
136 self._dedicated_radiobutton.set_active(True)
137 self._set_entry_sensitivity(self.controller.hosted)
138 self._load_data()
139 self._hosted_toggle = self._hosted_radiobutton.connect(
140 "toggled",
141 self._on_toggle_server_type_radiobutton)
142 self._dedicated_toggle = self._dedicated_radiobutton.connect(
143 "toggled",
144 self._on_toggle_server_type_radiobutton)
145
146 def _write_back(self):
147 self.controller.commit()
148
149 def _possibly_save_and_exit(self, button):
150 if self.controller.is_modified:
151 self._write_back()
152 self.destroy()
0153
=== added directory 'landscape/ui/view/tests'
=== added file 'landscape/ui/view/tests/__init__.py'
=== added file 'landscape/ui/view/tests/test_configuration.py'
--- landscape/ui/view/tests/test_configuration.py 1970-01-01 00:00:00 +0000
+++ landscape/ui/view/tests/test_configuration.py 2012-01-09 11:38:25 +0000
@@ -0,0 +1,270 @@
1import sys
2
3try:
4 from gi.repository import Gtk
5 got_gobject_introspection = True
6except ImportError:
7 got_gobject_introspection = False
8 gobject_skip_message = "GObject Introspection module unavailable"
9
10from landscape.tests.helpers import LandscapeTest
11from landscape.configuration import LandscapeSetupConfiguration
12from landscape.ui.view.configuration import ClientSettingsDialog
13from landscape.ui.controller.configuration import ConfigController
14
15
16class ConfigurationViewTest(LandscapeTest):
17
18 def setUp(self):
19 super(ConfigurationViewTest, self).setUp()
20 config = "[client]\n"
21 config += "data_path = %s\n" % sys.path[0]
22 config += "http_proxy = http://proxy.localdomain:3192\n"
23 config += "tags = a_tag\n"
24 config += "url = https://landscape.canonical.com/message-system\n"
25 config += "account_name = foo\n"
26 config += "registration_password = bar\n"
27 config += "computer_title = baz\n"
28 config += "https_proxy = https://proxy.localdomain:6192\n"
29 config += "ping_url = http://landscape.canonical.com/ping\n"
30
31 self.config_filename = self.makeFile(config)
32
33 class MySetupConfiguration(LandscapeSetupConfiguration):
34 default_config_filenames = [self.config_filename]
35
36 self.config = MySetupConfiguration()
37
38 def test_init(self):
39 """
40 Test that we correctly initialise the L{ConfigurationView} correctly
41 from the controller.
42 """
43 controller = ConfigController(self.config)
44 dialog = ClientSettingsDialog(controller)
45 content_area = dialog.get_content_area()
46 children = content_area.get_children()
47 self.assertEqual(len(children), 2)
48 box = children[0]
49 self.assertIsInstance(box, Gtk.Box)
50 self.assertTrue(dialog._hosted_radiobutton.get_active())
51 self.assertFalse(dialog._dedicated_radiobutton.get_active())
52 self.assertTrue(dialog._account_entry.get_sensitive())
53 self.assertTrue(dialog._password_entry.get_sensitive())
54 self.assertFalse(dialog._server_host_name_entry.get_sensitive())
55
56 def test_toggle_radio_button(self):
57 """
58 Test that we disable and enable the correct entries when we toggle the
59 dialog radiobuttons.
60 """
61 controller = ConfigController(self.config)
62 dialog = ClientSettingsDialog(controller)
63 self.assertTrue(dialog._hosted_radiobutton.get_active())
64 self.assertFalse(dialog._dedicated_radiobutton.get_active())
65 self.assertTrue(dialog._account_entry.get_sensitive())
66 self.assertTrue(dialog._password_entry.get_sensitive())
67 self.assertFalse(dialog._server_host_name_entry.get_sensitive())
68 dialog._dedicated_radiobutton.set_active(True)
69 self.assertFalse(dialog._hosted_radiobutton.get_active())
70 self.assertTrue(dialog._dedicated_radiobutton.get_active())
71 self.assertFalse(dialog._account_entry.get_sensitive())
72 self.assertFalse(dialog._password_entry.get_sensitive())
73 self.assertTrue(dialog._server_host_name_entry.get_sensitive())
74 dialog._hosted_radiobutton.set_active(True)
75 self.assertTrue(dialog._hosted_radiobutton.get_active())
76 self.assertFalse(dialog._dedicated_radiobutton.get_active())
77 self.assertTrue(dialog._account_entry.get_sensitive())
78 self.assertTrue(dialog._password_entry.get_sensitive())
79 self.assertFalse(dialog._server_host_name_entry.get_sensitive())
80
81 def test_load_data_from_config(self):
82 """
83 Test that we load data into the appropriate entries from the
84 configuration file.
85 """
86 controller = ConfigController(self.config)
87 dialog = ClientSettingsDialog(controller)
88 self.assertEqual(dialog._account_entry.get_text(), "foo")
89 self.assertEqual(dialog._password_entry.get_text(), "bar")
90 self.assertEqual(dialog._server_host_name_entry.get_text(),
91 "landscape.canonical.com")
92
93 def test_revert(self):
94 """
95 Test that we can revert the UI values using the controller.
96 """
97 controller = ConfigController(self.config)
98 dialog = ClientSettingsDialog(controller)
99 self.assertEqual(dialog._account_entry.get_text(), "foo")
100 self.assertEqual(dialog._password_entry.get_text(), "bar")
101 self.assertEqual(dialog._server_host_name_entry.get_text(),
102 "landscape.canonical.com")
103 dialog._dedicated_radiobutton.set_active(True)
104 dialog._server_host_name_entry.set_text("more.barn")
105 self.assertEqual(dialog._account_entry.get_text(), "foo")
106 self.assertEqual(dialog._password_entry.get_text(), "bar")
107 self.assertEqual(dialog._server_host_name_entry.get_text(),
108 "more.barn")
109 self.assertTrue(dialog._dedicated_radiobutton.get_active())
110 self.assertFalse(dialog._hosted_radiobutton.get_active())
111 dialog.revert(None)
112 self.assertEqual(dialog._account_entry.get_text(), "foo")
113 self.assertEqual(dialog._password_entry.get_text(), "bar")
114 self.assertEqual(dialog._server_host_name_entry.get_text(),
115 "landscape.canonical.com")
116 self.assertFalse(dialog._dedicated_radiobutton.get_active())
117 self.assertTrue(dialog._hosted_radiobutton.get_active())
118
119 if not got_gobject_introspection:
120 test_revert.skip = gobject_skip_message
121 test_load_data_from_config.skip = gobject_skip_message
122 test_toggle_radio_button.skip = gobject_skip_message
123 test_init.skip = gobject_skip_message
124
125
126class ConfigurationViewCommitTest(LandscapeTest):
127
128 def setUp(self):
129 super(ConfigurationViewCommitTest, self).setUp()
130 config = "[client]\n"
131 config += "data_path = %s\n" % sys.path[0]
132 config += "http_proxy = http://proxy.localdomain:3192\n"
133 config += "tags = a_tag\n"
134 config += "url = https://landscape.canonical.com/message-system\n"
135 config += "account_name = foo\n"
136 config += "registration_password = bar\n"
137 config += "computer_title = baz\n"
138 config += "https_proxy = https://proxy.localdomain:6192\n"
139 config += "ping_url = http://landscape.canonical.com/ping\n"
140 self.config_filename = self.makeFile(config)
141
142 class MySetupConfiguration(LandscapeSetupConfiguration):
143 default_config_filenames = [self.config_filename]
144
145 self.config = MySetupConfiguration()
146 self.real_write_back = ClientSettingsDialog._write_back
147 self.write_back_called = False
148
149 def fake_write_back(obj):
150 self.write_back_called = True
151
152 ClientSettingsDialog._write_back = fake_write_back
153 self.controller = ConfigController(self.config)
154 self.dialog = ClientSettingsDialog(self.controller)
155
156 def tearDown(self):
157 self.controller = None
158 self.dialog.destroy()
159 self.dialog = None
160 while Gtk.events_pending():
161 Gtk.main_iteration()
162 self.write_back_called = False
163 ClientSettingsDialog._write_back = self.real_write_back
164 super(ConfigurationViewCommitTest, self).tearDown()
165
166 def test_commit_fresh_dialog(self):
167 """
168 Test that we don't save anything from an untouched dialog on exit
169 """
170 while Gtk.events_pending():
171 Gtk.main_iteration()
172 self.dialog._possibly_save_and_exit(None)
173 self.assertFalse(self.write_back_called)
174
175 def test_commit_hosted_account_name_change(self):
176 """
177 Test that we do save changes when we set a new account name for hosted
178 accounts.
179 """
180 self.dialog._hosted_radiobutton.set_active(True)
181 while Gtk.events_pending():
182 Gtk.main_iteration()
183 self.dialog._account_entry.set_text("glow")
184 while Gtk.events_pending():
185 Gtk.main_iteration()
186 self.dialog._possibly_save_and_exit(None)
187 self.assertTrue(self.write_back_called)
188
189 def test_commit_hosted_password_change(self):
190 """
191 Test that we do save changes when we set a new account name for hosted
192 accounts.
193 """
194 self.dialog._hosted_radiobutton.set_active(True)
195 self.dialog._password_entry.set_text("sticks")
196 while Gtk.events_pending():
197 Gtk.main_iteration()
198 self.dialog._possibly_save_and_exit(None)
199 self.assertTrue(self.write_back_called)
200
201 def test_commit_dedicated_server_host_name_change(self):
202 """
203 Test that we do save changes when we set a new server host name for
204 a dedicated server.
205 """
206 self.dialog._dedicated_radiobutton.set_active(True)
207 self.dialog._server_host_name_entry.set_text(
208 "that.isolated.geographic")
209 while Gtk.events_pending():
210 Gtk.main_iteration()
211 self.dialog._possibly_save_and_exit(None)
212 self.assertTrue(self.write_back_called)
213
214 if not got_gobject_introspection:
215 test_commit_dedicated_server_host_name_change.skip = \
216 gobject_skip_message
217 test_commit_hosted_password_change.skip = gobject_skip_message
218 test_commit_hosted_account_name_change.skip = gobject_skip_message
219 test_commit_fresh_dialog.skip = gobject_skip_message
220
221
222class DedicatedConfigurationViewTest(LandscapeTest):
223
224 def setUp(self):
225 super(DedicatedConfigurationViewTest, self).setUp()
226 config = "[client]\n"
227 config += "data_path = %s\n" % sys.path[0]
228 config += "url = https://landscape.localdomain/message-system\n"
229 config += "computer_title = baz\n"
230 config += "ping_url = http://landscape.localdomain/ping\n"
231 self.config_filename = self.makeFile(config)
232
233 class MySetupConfiguration(LandscapeSetupConfiguration):
234 default_config_filenames = [self.config_filename]
235
236 self.config = MySetupConfiguration()
237
238 def test_init(self):
239 """
240 Test that we correctly initialise the L{ConfigurationView} correctly
241 from the controller.
242 """
243 controller = ConfigController(self.config)
244 dialog = ClientSettingsDialog(controller)
245 content_area = dialog.get_content_area()
246 children = content_area.get_children()
247 self.assertEqual(len(children), 2)
248 box = children[0]
249 self.assertIsInstance(box, Gtk.Box)
250 self.assertFalse(dialog._hosted_radiobutton.get_active())
251 self.assertTrue(dialog._dedicated_radiobutton.get_active())
252 self.assertFalse(dialog._account_entry.get_sensitive())
253 self.assertFalse(dialog._password_entry.get_sensitive())
254 self.assertTrue(dialog._server_host_name_entry.get_sensitive())
255
256 def test_load_data_from_config(self):
257 """
258 Test that we load data into the appropriate entries from the
259 configuration file.
260 """
261 controller = ConfigController(self.config)
262 dialog = ClientSettingsDialog(controller)
263 self.assertEqual(dialog._account_entry.get_text(), "")
264 self.assertEqual(dialog._password_entry.get_text(), "")
265 self.assertEqual(dialog._server_host_name_entry.get_text(),
266 "landscape.localdomain")
267
268 if not got_gobject_introspection:
269 test_load_data_from_config.skip = gobject_skip_message
270 test_init.skip = gobject_skip_message
0271
=== added directory 'landscape/ui/view/ui'
=== added file 'landscape/ui/view/ui/landscape-client-settings.glade'
--- landscape/ui/view/ui/landscape-client-settings.glade 1970-01-01 00:00:00 +0000
+++ landscape/ui/view/ui/landscape-client-settings.glade 2012-01-09 11:38:25 +0000
@@ -0,0 +1,407 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<interface>
3 <!-- interface-requires gtk+ 3.0 -->
4 <object class="GtkDialog" id="landscape-client-settings-dialog">
5 <property name="can_focus">False</property>
6 <property name="border_width">5</property>
7 <property name="title" translatable="yes">Landscape Client Settings</property>
8 <property name="resizable">False</property>
9 <property name="modal">True</property>
10 <property name="window_position">center</property>
11 <property name="icon_name">landscape-client-settings</property>
12 <property name="type_hint">dialog</property>
13 <property name="has_resize_grip">False</property>
14 <child internal-child="vbox">
15 <object class="GtkBox" id="landscape-client-settings-dialog-vbox">
16 <property name="can_focus">False</property>
17 <property name="orientation">vertical</property>
18 <property name="spacing">2</property>
19 <child internal-child="action_area">
20 <object class="GtkButtonBox" id="dialog-action_area1">
21 <property name="can_focus">False</property>
22 <property name="layout_style">end</property>
23 <child>
24 <object class="GtkButton" id="revert-button">
25 <property name="label">gtk-revert-to-saved</property>
26 <property name="visible">True</property>
27 <property name="can_focus">True</property>
28 <property name="receives_default">True</property>
29 <property name="use_action_appearance">False</property>
30 <property name="use_stock">True</property>
31 </object>
32 <packing>
33 <property name="expand">False</property>
34 <property name="fill">True</property>
35 <property name="position">0</property>
36 </packing>
37 </child>
38 <child>
39 <object class="GtkButton" id="close-button">
40 <property name="label">gtk-close</property>
41 <property name="visible">True</property>
42 <property name="can_focus">True</property>
43 <property name="receives_default">True</property>
44 <property name="use_action_appearance">False</property>
45 <property name="use_stock">True</property>
46 </object>
47 <packing>
48 <property name="expand">False</property>
49 <property name="fill">True</property>
50 <property name="position">1</property>
51 </packing>
52 </child>
53 </object>
54 <packing>
55 <property name="expand">False</property>
56 <property name="fill">True</property>
57 <property name="pack_type">end</property>
58 <property name="position">0</property>
59 </packing>
60 </child>
61 <child>
62 <object class="GtkBox" id="vbox">
63 <property name="visible">True</property>
64 <property name="can_focus">False</property>
65 <property name="orientation">vertical</property>
66 <property name="spacing">6</property>
67 <child>
68 <object class="GtkFrame" id="landscape-server-type-frame">
69 <property name="visible">True</property>
70 <property name="can_focus">False</property>
71 <property name="label_xalign">0</property>
72 <property name="shadow_type">none</property>
73 <child>
74 <object class="GtkAlignment" id="landscape-server-type-frame-alignment">
75 <property name="visible">True</property>
76 <property name="can_focus">False</property>
77 <property name="hexpand">True</property>
78 <property name="vexpand">True</property>
79 <property name="top_padding">6</property>
80 <property name="left_padding">12</property>
81 <child>
82 <object class="GtkBox" id="server-type-options-vbox">
83 <property name="visible">True</property>
84 <property name="can_focus">False</property>
85 <property name="hexpand">True</property>
86 <property name="vexpand">True</property>
87 <property name="orientation">vertical</property>
88 <property name="spacing">12</property>
89 <child>
90 <object class="GtkBox" id="hosted-hbox">
91 <property name="visible">True</property>
92 <property name="can_focus">False</property>
93 <property name="hexpand">True</property>
94 <property name="vexpand">True</property>
95 <child>
96 <object class="GtkAlignment" id="hosted-radiobutton-alignment">
97 <property name="visible">True</property>
98 <property name="can_focus">False</property>
99 <property name="yalign">0</property>
100 <child>
101 <object class="GtkRadioButton" id="hosted-radiobutton">
102 <property name="visible">True</property>
103 <property name="can_focus">True</property>
104 <property name="receives_default">False</property>
105 <property name="halign">start</property>
106 <property name="valign">start</property>
107 <property name="use_action_appearance">False</property>
108 <property name="xalign">0</property>
109 <property name="active">True</property>
110 <property name="draw_indicator">True</property>
111 <property name="group">dedicated-radiobutton</property>
112 <child>
113 <object class="GtkLabel" id="hosted-radiobutton-label">
114 <property name="visible">True</property>
115 <property name="can_focus">False</property>
116 <property name="xalign">0</property>
117 <attributes>
118 <attribute name="weight" value="bold"/>
119 </attributes>
120 </object>
121 </child>
122 </object>
123 </child>
124 </object>
125 <packing>
126 <property name="expand">False</property>
127 <property name="fill">True</property>
128 <property name="position">0</property>
129 </packing>
130 </child>
131 <child>
132 <object class="GtkFrame" id="hosted-frame">
133 <property name="visible">True</property>
134 <property name="can_focus">False</property>
135 <property name="hexpand">True</property>
136 <property name="label_xalign">0</property>
137 <property name="shadow_type">none</property>
138 <child>
139 <object class="GtkAlignment" id="hosted-frame-alignment">
140 <property name="visible">True</property>
141 <property name="can_focus">False</property>
142 <property name="left_padding">12</property>
143 <child>
144 <object class="GtkBox" id="hosted-vbox">
145 <property name="visible">True</property>
146 <property name="can_focus">False</property>
147 <property name="hexpand">True</property>
148 <property name="vexpand">True</property>
149 <property name="orientation">vertical</property>
150 <property name="spacing">6</property>
151 <child>
152 <object class="GtkBox" id="account-hbox">
153 <property name="visible">True</property>
154 <property name="can_focus">False</property>
155 <property name="hexpand">True</property>
156 <property name="spacing">12</property>
157 <child>
158 <object class="GtkLabel" id="account-label">
159 <property name="visible">True</property>
160 <property name="can_focus">False</property>
161 <property name="xalign">0</property>
162 <property name="label" translatable="yes">Account name: </property>
163 </object>
164 <packing>
165 <property name="expand">False</property>
166 <property name="fill">True</property>
167 <property name="position">0</property>
168 </packing>
169 </child>
170 <child>
171 <object class="GtkEntry" id="account-name-entry">
172 <property name="visible">True</property>
173 <property name="can_focus">True</property>
174 <property name="hexpand">True</property>
175 <property name="invisible_char">•</property>
176 <property name="invisible_char_set">True</property>
177 </object>
178 <packing>
179 <property name="expand">True</property>
180 <property name="fill">True</property>
181 <property name="position">1</property>
182 </packing>
183 </child>
184 </object>
185 <packing>
186 <property name="expand">True</property>
187 <property name="fill">True</property>
188 <property name="position">0</property>
189 </packing>
190 </child>
191 <child>
192 <object class="GtkBox" id="password-hbox">
193 <property name="visible">True</property>
194 <property name="can_focus">False</property>
195 <property name="spacing">12</property>
196 <child>
197 <object class="GtkLabel" id="password-label">
198 <property name="visible">True</property>
199 <property name="can_focus">False</property>
200 <property name="xalign">0</property>
201 <property name="label" translatable="yes">Password:</property>
202 </object>
203 <packing>
204 <property name="expand">False</property>
205 <property name="fill">True</property>
206 <property name="position">0</property>
207 </packing>
208 </child>
209 <child>
210 <object class="GtkEntry" id="reigstered-password-entry">
211 <property name="visible">True</property>
212 <property name="can_focus">True</property>
213 <property name="hexpand">True</property>
214 <property name="visibility">False</property>
215 <property name="invisible_char">•</property>
216 <property name="invisible_char_set">True</property>
217 </object>
218 <packing>
219 <property name="expand">True</property>
220 <property name="fill">True</property>
221 <property name="position">1</property>
222 </packing>
223 </child>
224 </object>
225 <packing>
226 <property name="expand">True</property>
227 <property name="fill">True</property>
228 <property name="position">1</property>
229 </packing>
230 </child>
231 </object>
232 </child>
233 </object>
234 </child>
235 <child type="label">
236 <object class="GtkLabel" id="hosted-frame-label">
237 <property name="visible">True</property>
238 <property name="can_focus">False</property>
239 <property name="margin_top">4</property>
240 <property name="label" translatable="yes">&lt;b&gt;Hosted&lt;/b&gt;</property>
241 <property name="use_markup">True</property>
242 </object>
243 </child>
244 </object>
245 <packing>
246 <property name="expand">False</property>
247 <property name="fill">True</property>
248 <property name="position">1</property>
249 </packing>
250 </child>
251 </object>
252 <packing>
253 <property name="expand">True</property>
254 <property name="fill">True</property>
255 <property name="position">0</property>
256 </packing>
257 </child>
258 <child>
259 <object class="GtkBox" id="dedicated-hbox">
260 <property name="visible">True</property>
261 <property name="can_focus">False</property>
262 <child>
263 <object class="GtkRadioButton" id="dedicated-radiobutton">
264 <property name="visible">True</property>
265 <property name="can_focus">True</property>
266 <property name="receives_default">False</property>
267 <property name="valign">start</property>
268 <property name="use_action_appearance">False</property>
269 <property name="xalign">0</property>
270 <property name="active">True</property>
271 <property name="draw_indicator">True</property>
272 <child>
273 <object class="GtkAlignment" id="alignment3">
274 <property name="visible">True</property>
275 <property name="can_focus">False</property>
276 <child>
277 <object class="GtkLabel" id="dedicated-radiobutton-label">
278 <property name="visible">True</property>
279 <property name="can_focus">False</property>
280 <property name="xalign">0</property>
281 <attributes>
282 <attribute name="weight" value="bold"/>
283 </attributes>
284 </object>
285 </child>
286 </object>
287 </child>
288 </object>
289 <packing>
290 <property name="expand">False</property>
291 <property name="fill">True</property>
292 <property name="position">0</property>
293 </packing>
294 </child>
295 <child>
296 <object class="GtkFrame" id="dedicated-frame">
297 <property name="visible">True</property>
298 <property name="can_focus">False</property>
299 <property name="margin_top">4</property>
300 <property name="hexpand">True</property>
301 <property name="label_xalign">0</property>
302 <property name="shadow_type">none</property>
303 <child>
304 <object class="GtkAlignment" id="dedicated-frame-alignment">
305 <property name="visible">True</property>
306 <property name="can_focus">False</property>
307 <property name="left_padding">12</property>
308 <child>
309 <object class="GtkBox" id="host-name-hbox">
310 <property name="visible">True</property>
311 <property name="can_focus">False</property>
312 <property name="hexpand">True</property>
313 <property name="spacing">12</property>
314 <child>
315 <object class="GtkLabel" id="server-host-name-label">
316 <property name="visible">True</property>
317 <property name="can_focus">False</property>
318 <property name="xalign">0</property>
319 <property name="label" translatable="yes">Server host name:</property>
320 </object>
321 <packing>
322 <property name="expand">False</property>
323 <property name="fill">True</property>
324 <property name="position">0</property>
325 </packing>
326 </child>
327 <child>
328 <object class="GtkEntry" id="server-host-name-entry">
329 <property name="visible">True</property>
330 <property name="can_focus">True</property>
331 <property name="hexpand">True</property>
332 <property name="invisible_char">•</property>
333 <property name="invisible_char_set">True</property>
334 </object>
335 <packing>
336 <property name="expand">True</property>
337 <property name="fill">True</property>
338 <property name="position">1</property>
339 </packing>
340 </child>
341 </object>
342 </child>
343 </object>
344 </child>
345 <child type="label">
346 <object class="GtkLabel" id="dedicated-frame-label">
347 <property name="visible">True</property>
348 <property name="can_focus">False</property>
349 <property name="label" translatable="yes">&lt;b&gt;Dedicated server&lt;/b&gt;</property>
350 <property name="use_markup">True</property>
351 </object>
352 </child>
353 </object>
354 <packing>
355 <property name="expand">False</property>
356 <property name="fill">True</property>
357 <property name="position">1</property>
358 </packing>
359 </child>
360 </object>
361 <packing>
362 <property name="expand">True</property>
363 <property name="fill">True</property>
364 <property name="position">1</property>
365 </packing>
366 </child>
367 </object>
368 </child>
369 </object>
370 </child>
371 <child type="label">
372 <object class="GtkLabel" id="landscape-server-type-label">
373 <property name="visible">True</property>
374 <property name="can_focus">False</property>
375 <property name="label" translatable="yes">&lt;b&gt;Landscape Server Type:&lt;/b&gt;</property>
376 <property name="use_markup">True</property>
377 </object>
378 </child>
379 </object>
380 <packing>
381 <property name="expand">False</property>
382 <property name="fill">True</property>
383 <property name="position">0</property>
384 </packing>
385 </child>
386 </object>
387 <packing>
388 <property name="expand">False</property>
389 <property name="fill">True</property>
390 <property name="position">1</property>
391 </packing>
392 </child>
393 </object>
394 </child>
395 <action-widgets>
396 <action-widget response="1">revert-button</action-widget>
397 <action-widget response="0">close-button</action-widget>
398 </action-widgets>
399 </object>
400 <object class="GtkSizeGroup" id="property-label-sizegroup">
401 <widgets>
402 <widget name="server-host-name-label"/>
403 <widget name="password-label"/>
404 <widget name="account-label"/>
405 </widgets>
406 </object>
407</interface>
0408
=== added file 'scripts/landscape-client-settings-ui'
--- scripts/landscape-client-settings-ui 1970-01-01 00:00:00 +0000
+++ scripts/landscape-client-settings-ui 2012-01-09 11:38:25 +0000
@@ -0,0 +1,17 @@
1#!/usr/bin/python
2import os
3import sys
4
5script_dir = os.path.abspath("scripts")
6if os.path.dirname(os.path.abspath(sys.argv[0])) == script_dir:
7 sys.path.insert(0, "./")
8else:
9 from landscape.lib.warning import hide_warnings
10 hide_warnings()
11
12from landscape.ui.controller.app import SettingsApplicationController
13
14
15if __name__ == "__main__":
16 app = SettingsApplicationController(args=sys.argv[1:])
17 app.run(None)

Subscribers

People subscribed via source and target branches

to all changes: