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
1=== modified file 'landscape/broker/config.py'
2--- landscape/broker/config.py 2011-11-11 16:45:32 +0000
3+++ landscape/broker/config.py 2012-01-09 11:38:25 +0000
4@@ -11,7 +11,7 @@
5 @cvar required_options: C{["url"]}
6 """
7
8- required_options = ["url"]
9+ DEFAULT_URL = "https://landscape.canonical.com/message-system"
10
11 def __init__(self):
12 super(BrokerConfiguration, self).__init__()
13@@ -115,3 +115,6 @@
14 os.environ["https_proxy"] = self.https_proxy
15 elif self._original_https_proxy:
16 os.environ["https_proxy"] = self._original_https_proxy
17+
18+ if self.url is None:
19+ self.url = self.DEFAULT_URL
20
21=== modified file 'landscape/broker/tests/test_config.py'
22--- landscape/broker/tests/test_config.py 2011-08-19 15:47:27 +0000
23+++ landscape/broker/tests/test_config.py 2012-01-09 11:38:25 +0000
24@@ -78,3 +78,16 @@
25 self.assertEqual(configuration.urgent_exchange_interval, 12)
26 self.assertEqual(configuration.exchange_interval, 34)
27 self.assertEqual(configuration.ping_interval, 6)
28+
29+ def test_missing_url_is_defaulted(self):
30+ """
31+ Test that is we don't explicitly pass a URL that this value is
32+ defaulted.
33+ """
34+ filename = self.makeFile("[client]\n")
35+
36+ configuration = BrokerConfiguration()
37+ configuration.load(["--config", filename])
38+
39+ self.assertEqual(configuration.url,
40+ "https://landscape.canonical.com/message-system")
41
42=== modified file 'landscape/configuration.py'
43--- landscape/configuration.py 2011-12-01 14:01:29 +0000
44+++ landscape/configuration.py 2012-01-09 11:38:25 +0000
45@@ -68,9 +68,8 @@
46 unsaved_options = ("no_start", "disable", "silent", "ok_no_register",
47 "import_from")
48
49- def __init__(self, fetch_import_url):
50+ def __init__(self):
51 super(LandscapeSetupConfiguration, self).__init__()
52- self._fetch_import_url = fetch_import_url
53
54 def _load_external_options(self):
55 """Handle the --import parameter.
56@@ -89,7 +88,7 @@
57 os.environ["http_proxy"] = self.http_proxy
58 if self.https_proxy:
59 os.environ["https_proxy"] = self.https_proxy
60- content = self._fetch_import_url(self.import_from)
61+ content = self.fetch_import_url(self.import_from)
62 parser.readfp(StringIO(content))
63 elif not os.path.isfile(self.import_from):
64 raise ImportOptionError("File %s doesn't exist." %
65@@ -109,6 +108,21 @@
66 options.update(self._command_line_options)
67 self._command_line_options = options
68
69+ def fetch_import_url(self, url):
70+ """Handle fetching of URLs passed to --url."""
71+
72+ print_text("Fetching configuration from %s..." % url)
73+ error_message = None
74+ try:
75+ content = fetch(url)
76+ except FetchError, error:
77+ error_message = str(error)
78+ if error_message is not None:
79+ raise ImportOptionError(
80+ "Couldn't download configuration from %s: %s" %
81+ (url, error_message))
82+ return content
83+
84 def make_parser(self):
85 """
86 Specialize the parser, adding configure-specific options.
87@@ -584,28 +598,8 @@
88 return result
89
90
91-def fetch_import_url(url):
92- """Handle fetching of URLs passed to --url.
93-
94- This is done out of LandscapeSetupConfiguration since it has to deal
95- with interaction with the user and downloading of files.
96- """
97-
98- print_text("Fetching configuration from %s..." % url)
99- error_message = None
100- try:
101- content = fetch(url)
102- except FetchError, error:
103- error_message = str(error)
104- if error_message is not None:
105- raise ImportOptionError(
106- "Couldn't download configuration from %s: %s" %
107- (url, error_message))
108- return content
109-
110-
111 def main(args):
112- config = LandscapeSetupConfiguration(fetch_import_url)
113+ config = LandscapeSetupConfiguration()
114 if args in (["-h"], ["--help"]):
115 # We let landscape-config --help to be run as normal user
116 config.load(args)
117
118=== modified file 'landscape/tests/test_configuration.py'
119--- landscape/tests/test_configuration.py 2011-12-01 14:01:29 +0000
120+++ landscape/tests/test_configuration.py 2012-01-09 11:38:25 +0000
121@@ -11,7 +11,7 @@
122 print_text, LandscapeSetupScript, LandscapeSetupConfiguration,
123 register, setup, main, setup_init_script_and_start_client,
124 stop_client_and_disable_init_script, ConfigurationError,
125- fetch_import_url, ImportOptionError, store_public_key_data)
126+ ImportOptionError, store_public_key_data)
127 from landscape.broker.registration import InvalidCredentialsError
128 from landscape.sysvconfig import SysVConfig, ProcessError
129 from landscape.tests.helpers import (
130@@ -27,7 +27,7 @@
131 url = https://landscape.canonical.com/message-system
132 """)
133 args.extend(["--config", filename, "--data-path", self.makeDir()])
134- config = LandscapeSetupConfiguration(fetch_import_url)
135+ config = LandscapeSetupConfiguration()
136 config.load(args)
137 return config
138
139@@ -91,7 +91,7 @@
140
141 class MyLandscapeSetupConfiguration(LandscapeSetupConfiguration):
142 default_config_filenames = [self.config_filename]
143- self.config = MyLandscapeSetupConfiguration(None)
144+ self.config = MyLandscapeSetupConfiguration()
145 self.script = LandscapeSetupScript(self.config)
146
147 def test_show_help(self):
148@@ -1889,7 +1889,7 @@
149 When registration fails because of an unknown error, a message is
150 printed and the program exits.
151 """
152- configuration = LandscapeSetupConfiguration(None)
153+ configuration = LandscapeSetupConfiguration()
154
155 # We'll just mock the remote here to have it raise an exception.
156 connector_factory = self.mocker.replace(
157
158=== added directory 'landscape/ui'
159=== added file 'landscape/ui/__init__.py'
160=== added directory 'landscape/ui/controller'
161=== added file 'landscape/ui/controller/__init__.py'
162=== added file 'landscape/ui/controller/app.py'
163--- landscape/ui/controller/app.py 1970-01-01 00:00:00 +0000
164+++ landscape/ui/controller/app.py 2012-01-09 11:38:25 +0000
165@@ -0,0 +1,32 @@
166+from gi.repository import Gtk
167+
168+from landscape.configuration import LandscapeSetupConfiguration
169+from landscape.ui.view.configuration import ClientSettingsDialog
170+from landscape.ui.controller.configuration import ConfigController
171+
172+
173+APPLICATION_ID = "com.canonical.landscape-client.settings.ui"
174+
175+
176+class SettingsApplicationController(Gtk.Application):
177+ """
178+ Core application controller for the landscape settings application.
179+ """
180+
181+ def __init__(self, args=[], data_path=None):
182+ super(SettingsApplicationController, self).__init__(
183+ application_id=APPLICATION_ID)
184+ self._args = args
185+ self.data_path = data_path
186+ self.connect("activate", self.setup_ui)
187+
188+ def get_config(self):
189+ return LandscapeSetupConfiguration()
190+
191+ def setup_ui(self, data=None):
192+ config = self.get_config()
193+ controller = ConfigController(config, args=self._args)
194+ controller.load()
195+ self.settings_dialog = ClientSettingsDialog(controller,
196+ data_path=self.data_path)
197+ self.settings_dialog.run()
198
199=== added file 'landscape/ui/controller/configuration.py'
200--- landscape/ui/controller/configuration.py 1970-01-01 00:00:00 +0000
201+++ landscape/ui/controller/configuration.py 2012-01-09 11:38:25 +0000
202@@ -0,0 +1,232 @@
203+import threading
204+
205+
206+class ConfigControllerLockError(Exception):
207+ pass
208+
209+
210+class ConfigController(object):
211+ """
212+ L{ConfigContoller} defines actions to take against a configuration object,
213+ providing starting values from the file, allowing them to be changed
214+ transiently, reverted or committed.
215+ """
216+
217+ HOSTED_HOST_NAME = "landscape.canonical.com"
218+ DEFAULT_SERVER_HOST_NAME = "landscape.localdomain"
219+
220+ def __init__(self, configuration, args=[]):
221+ self._initial_server_host_name = self.DEFAULT_SERVER_HOST_NAME
222+ self._configuration = configuration
223+ self._args = args
224+ self._lock_out = False
225+ self._lock = threading.Lock()
226+
227+ def load(self):
228+ "Load the initial data from the configuration"
229+ self.lock()
230+ self._configuration.load(self._args)
231+ self._pull_data_from_config()
232+ self._modified = False
233+ self.unlock()
234+
235+ def default_dedicated(self):
236+ """
237+ Set L{server_host_name} to something sane when switching from hosted to
238+ dedicated.
239+ """
240+ if self._initial_server_host_name != self.HOSTED_HOST_NAME:
241+ self._server_host_name = self._initial_server_host_name
242+ else:
243+ self._server_host_name = self.DEFAULT_SERVER_HOST_NAME
244+ self._url = self._derive_url_from_host_name(
245+ self._server_host_name)
246+ self._ping_url = self._derive_ping_url_from_host_name(
247+ self._server_host_name)
248+ self._modified = True
249+
250+ def default_hosted(self):
251+ """
252+ Set L{server_host_name} in a recoverable fashion when switching from
253+ dedicated to hosted.
254+ """
255+ if self._server_host_name != self.HOSTED_HOST_NAME:
256+ self._server_host_name = self.HOSTED_HOST_NAME
257+ self._url = self._derive_url_from_host_name(
258+ self._server_host_name)
259+ self._ping_url = self._derive_ping_url_from_host_name(
260+ self._server_host_name)
261+ self._modified = True
262+
263+ def _pull_data_from_config(self):
264+ """
265+ Pull in data set from configuration class.
266+ """
267+ self._lock.acquire()
268+ self._data_path = self._configuration.data_path
269+ self._http_proxy = self._configuration.http_proxy
270+ self._tags = self._configuration.tags
271+ self._url = self._configuration.url
272+ self._ping_url = self._configuration.ping_url
273+ self._account_name = self._configuration.account_name
274+ self._registration_password = \
275+ self._configuration.registration_password
276+ self._computer_title = self._configuration.computer_title
277+ self._https_proxy = self._configuration.https_proxy
278+ self._ping_url = self._configuration.ping_url
279+ if self._url:
280+ self._server_host_name = \
281+ self._derive_server_host_name_from_url(self._url)
282+ else:
283+ self._server_host_name = self.HOSTED_HOST_NAME
284+ self._initial_server_host_name = self._server_host_name
285+ self._modified = False
286+ self._lock.release()
287+
288+ def lock(self):
289+ "Block updates to the data set."
290+ self._lock.acquire()
291+ self._lock_out = True
292+ self._lock.release()
293+
294+ def unlock(self):
295+ "Allow updates to the data set."
296+ self._lock.acquire()
297+ self._lock_out = False
298+ self._lock.release()
299+
300+ def is_locked(self):
301+ "Check if updates are locked out."
302+ self._lock.acquire()
303+ lock_state = self._lock_out
304+ self._lock.release()
305+ return lock_state
306+
307+ def _derive_server_host_name_from_url(self, url):
308+ "Extract the hostname part from a URL."
309+ try:
310+ without_protocol = url[url.index("://") + 3:]
311+ except ValueError:
312+ without_protocol = url
313+ try:
314+ return without_protocol[:without_protocol.index("/")]
315+ except ValueError:
316+ return without_protocol
317+
318+ def _derive_url_from_host_name(self, host_name):
319+ "Extrapolate a url from a host name."
320+ #Reuse this code to make sure it's a proper host name
321+ host_name = self._derive_server_host_name_from_url(host_name)
322+ return "https://" + host_name + "/message-system"
323+
324+ def _derive_ping_url_from_host_name(self, host_name):
325+ "Extrapolate a ping_url from a host name."
326+ #Reuse this code to make sure it's a proper host name
327+ host_name = self._derive_server_host_name_from_url(host_name)
328+ return "http://" + host_name + "/ping"
329+
330+ def _get_server_host_name(self):
331+ return self._server_host_name
332+
333+ def _set_server_host_name(self, value):
334+ self._lock.acquire()
335+ if self._lock_out:
336+ self._lock.release()
337+ raise ConfigControllerLockError
338+ else:
339+ if value != self.HOSTED_HOST_NAME:
340+ self._initial_server_host_name = value
341+ self._server_host_name = value
342+ self._url = self._derive_url_from_host_name(
343+ self._server_host_name)
344+ self._ping_url = self._derive_ping_url_from_host_name(
345+ self._server_host_name)
346+ self._modified = True
347+ self._lock.release()
348+ server_host_name = property(_get_server_host_name, _set_server_host_name)
349+
350+ @property
351+ def data_path(self):
352+ return self._data_path
353+
354+ @property
355+ def url(self):
356+ return self._url
357+
358+ @property
359+ def http_proxy(self):
360+ return self._http_proxy
361+
362+ @property
363+ def tags(self):
364+ return self._tags
365+
366+ def _get_account_name(self):
367+ return self._account_name
368+
369+ def _set_account_name(self, value):
370+ self._lock.acquire()
371+ if self._lock_out:
372+ self._lock.release()
373+ raise ConfigControllerLockError
374+ else:
375+ self._account_name = value
376+ self._modified = True
377+ self._lock.release()
378+ account_name = property(_get_account_name, _set_account_name)
379+
380+ def _get_registration_password(self):
381+ return self._registration_password
382+
383+ def _set_registration_password(self, value):
384+ self._lock.acquire()
385+ if self._lock_out:
386+ self._lock.release()
387+ raise ConfigControllerLockError
388+ else:
389+ self._registration_password = value
390+ self._modified = True
391+ self._lock.release()
392+ registration_password = property(_get_registration_password,
393+ _set_registration_password)
394+
395+ @property
396+ def computer_title(self):
397+ return self._computer_title
398+
399+ @property
400+ def https_proxy(self):
401+ return self._https_proxy
402+
403+ @property
404+ def ping_url(self):
405+ return self._ping_url
406+
407+ @property
408+ def hosted(self):
409+ return self.server_host_name == self.HOSTED_HOST_NAME
410+
411+ @property
412+ def is_modified(self):
413+ return self._modified
414+
415+ def revert(self):
416+ "Revert settings to those the configuration object originally found."
417+ self._configuration.reload()
418+ self._pull_data_from_config()
419+
420+ def commit(self):
421+ "Persist settings via the configuration object."
422+ self._configuration.data_path = self._data_path
423+ self._configuration.http_proxy = self._http_proxy
424+ self._configuration.tags = self._tags
425+ self._configuration.url = self._url
426+ self._configuration.ping_url = self._ping_url
427+ self._configuration.account_name = self._account_name
428+ self._configuration.registration_password = \
429+ self._registration_password
430+ self._configuration.computer_title = self._computer_title
431+ self._configuration.https_proxy = self._https_proxy
432+ self._configuration.ping_url = self._ping_url
433+ self._configuration.write()
434+ self._modified = False
435
436=== added directory 'landscape/ui/controller/tests'
437=== added file 'landscape/ui/controller/tests/__init__.py'
438=== added file 'landscape/ui/controller/tests/test_app.py'
439--- landscape/ui/controller/tests/test_app.py 1970-01-01 00:00:00 +0000
440+++ landscape/ui/controller/tests/test_app.py 2012-01-09 11:38:25 +0000
441@@ -0,0 +1,124 @@
442+import sys
443+
444+try:
445+ from gi.repository import Gtk
446+ got_gobject_introspection = True
447+except ImportError:
448+ got_gobject_introspection = False
449+ gobject_skip_message = "GObject Introspection module unavailable"
450+
451+
452+from landscape.tests.helpers import LandscapeTest
453+from landscape.ui.controller.app import SettingsApplicationController
454+from landscape.ui.controller.configuration import ConfigController
455+from landscape.ui.view.configuration import ClientSettingsDialog
456+from landscape.configuration import LandscapeSetupConfiguration
457+
458+
459+class ConnectionRecordingSettingsApplicationController(
460+ SettingsApplicationController):
461+
462+ _connections = set()
463+ _connection_args = {}
464+ _connection_kwargs = {}
465+
466+ def __init__(self, get_config_f=None):
467+ super(ConnectionRecordingSettingsApplicationController,
468+ self).__init__()
469+ if get_config_f:
470+ self.get_config = get_config_f
471+
472+ def _make_connection_name(self, signal, func):
473+ return signal + ">" + func.__name__
474+
475+ def _record_connection(self, signal, func, *args, **kwargs):
476+ connection_name = self._make_connection_name(signal, func)
477+ self._connections.add(connection_name)
478+ signal_connection_args = self._connection_args.get(
479+ connection_name, [])
480+ signal_connection_args.append(repr(args))
481+ self._connection_args = signal_connection_args
482+ signal_connection_kwargs = self._connection_kwargs.get(
483+ connection_name, [])
484+ signal_connection_kwargs.append(repr(kwargs))
485+ self._connection_kwargs = signal_connection_kwargs
486+
487+ def is_connected(self, signal, func):
488+ connection_name = self._make_connection_name(signal, func)
489+ return self._connections.issuperset(set([connection_name]))
490+
491+ def connect(self, signal, func, *args, **kwargs):
492+ self._record_connection(signal, func)
493+
494+
495+class SettingsApplicationControllerInitTest(LandscapeTest):
496+
497+ def setUp(self):
498+ super(SettingsApplicationControllerInitTest, self).setUp()
499+
500+ def test_init(self):
501+ """
502+ Test we connect activate to something useful on application
503+ initialisation.
504+ """
505+ app = ConnectionRecordingSettingsApplicationController()
506+ self.assertTrue(app.is_connected("activate", app.setup_ui))
507+
508+ if not got_gobject_introspection:
509+ test_init.skip = gobject_skip_message
510+
511+
512+class SettingsApplicationControllerUISetupTest(LandscapeTest):
513+
514+ def setUp(self):
515+ super(SettingsApplicationControllerUISetupTest, self).setUp()
516+
517+ def fake_run(obj):
518+ """
519+ Retard X11 mapping.
520+ """
521+ pass
522+
523+ self._real_run = Gtk.Dialog.run
524+ Gtk.Dialog.run = fake_run
525+
526+ def get_config():
527+ configdata = "[client]\n"
528+ configdata += "data_path = %s\n" % sys.path[0]
529+ configdata += "http_proxy = http://proxy.localdomain:3192\n"
530+ configdata += "tags = a_tag\n"
531+ configdata += \
532+ "url = https://landscape.canonical.com/message-system\n"
533+ configdata += "account_name = foo\n"
534+ configdata += "registration_password = bar\n"
535+ configdata += "computer_title = baz\n"
536+ configdata += "https_proxy = https://proxy.localdomain:6192\n"
537+ configdata += "ping_url = http://landscape.canonical.com/ping\n"
538+ config_filename = self.makeFile(configdata)
539+
540+ class MyLandscapeSetupConfiguration(LandscapeSetupConfiguration):
541+ default_config_filenames = [config_filename]
542+
543+ config = MyLandscapeSetupConfiguration()
544+ return config
545+
546+ self.app = ConnectionRecordingSettingsApplicationController(
547+ get_config_f=get_config)
548+
549+ def tearDown(self):
550+ Gtk.Dialog.run = self._real_run
551+ super(
552+ SettingsApplicationControllerUISetupTest, self).tearDown()
553+
554+ def test_setup_ui(self):
555+ """
556+ Test that we correctly setup the L{ClientSettingsDialog} with
557+ the config object and correct data
558+ """
559+ self.app.setup_ui(data=None)
560+ self.assertIsInstance(self.app.settings_dialog, ClientSettingsDialog)
561+ self.assertIsInstance(self.app.settings_dialog.controller,
562+ ConfigController)
563+
564+ if not got_gobject_introspection:
565+ test_setup_ui.skip = gobject_skip_message
566
567=== added file 'landscape/ui/controller/tests/test_configuration.py'
568--- landscape/ui/controller/tests/test_configuration.py 1970-01-01 00:00:00 +0000
569+++ landscape/ui/controller/tests/test_configuration.py 2012-01-09 11:38:25 +0000
570@@ -0,0 +1,223 @@
571+from landscape.tests.helpers import LandscapeTest
572+from landscape.ui.controller.configuration import (
573+ ConfigController, ConfigControllerLockError)
574+from landscape.configuration import LandscapeSetupConfiguration
575+
576+
577+class ConfigControllerTest(LandscapeTest):
578+
579+ def setUp(self):
580+ super(ConfigControllerTest, self).setUp()
581+ config = "[client]"
582+ config += "data_path = /var/lib/landscape/client\n"
583+ config += "http_proxy = http://proxy.localdomain:3192\n"
584+ config += "tags = a_tag\n"
585+ config += "url = https://landscape.canonical.com/message-system\n"
586+ config += "account_name = foo\n"
587+ config += "registration_password = bar\n"
588+ config += "computer_title = baz\n"
589+ config += "https_proxy = https://proxy.localdomain:6192\n"
590+ config += "ping_url = http://landscape.canonical.com/ping\n"
591+ self.config_filename = self.makeFile(config)
592+
593+ class MyLandscapeSetupConfiguration(LandscapeSetupConfiguration):
594+ default_config_filenames = [self.config_filename]
595+
596+ self.config = MyLandscapeSetupConfiguration()
597+ self.controller = ConfigController(self.config)
598+
599+ def test_init(self):
600+ """
601+ Test that when we create a controller it has initial state read in
602+ directly from the configuration file.
603+ """
604+ self.controller.load()
605+ self.assertEqual(self.controller.data_path,
606+ "/var/lib/landscape/client/")
607+ self.assertEqual(self.controller.http_proxy,
608+ "http://proxy.localdomain:3192")
609+ self.assertEqual(self.controller.tags, "a_tag")
610+ self.assertEqual(self.controller.url,
611+ "https://landscape.canonical.com/message-system")
612+ self.assertEqual(self.controller.account_name, "foo")
613+ self.assertEqual(self.controller.registration_password, "bar")
614+ self.assertEqual(self.controller.computer_title, "baz")
615+ self.assertEqual(self.controller.https_proxy,
616+ "https://proxy.localdomain:6192")
617+ self.assertEqual(self.controller.ping_url,
618+ "http://landscape.canonical.com/ping")
619+ self.assertEqual(self.controller.server_host_name,
620+ "landscape.canonical.com")
621+
622+ def test_set_server_hostname(self):
623+ """
624+ Test we can set the server_hostname correctly, and derive L{url} and
625+ L{ping_url} from it.
626+ """
627+ self.controller.load()
628+ self.assertEqual(self.controller.url,
629+ "https://landscape.canonical.com/message-system")
630+ self.assertEqual(self.controller.ping_url,
631+ "http://landscape.canonical.com/ping")
632+ self.assertEqual(self.controller.server_host_name,
633+ "landscape.canonical.com")
634+ new_server_host_name = "landscape.localdomain"
635+ self.controller.server_host_name = new_server_host_name
636+ self.assertEqual(self.controller.server_host_name,
637+ new_server_host_name)
638+ self.assertEqual(self.controller.url,
639+ "https://landscape.localdomain/message-system")
640+ self.assertEqual(self.controller.ping_url,
641+ "http://landscape.localdomain/ping")
642+
643+ def test_setting_server_host_name_also_sets_hosted(self):
644+ """
645+ Test that when we set the L{server_host_name} the L{hosted} value is
646+ also derived.
647+ """
648+ self.controller.load()
649+ self.assertTrue(self.controller.hosted)
650+ self.controller.server_host_name = "landscape.localdomain"
651+ self.assertFalse(self.controller.hosted)
652+ self.controller.server_host_name = "landscape.canonical.com"
653+ self.assertTrue(self.controller.hosted)
654+
655+ def test_set_account_name(self):
656+ """
657+ Test that we can set the L{account_name} property.
658+ """
659+ self.controller.load()
660+ self.assertEqual(self.controller.account_name, "foo")
661+ self.controller.account_name = "shoe"
662+ self.assertEqual(self.controller.account_name, "shoe")
663+
664+ def test_set_registration_password(self):
665+ """
666+ Test that we can set the L{registration_password} property.
667+ """
668+ self.controller.load()
669+ self.assertEquals(self.controller.registration_password, "bar")
670+ self.controller.registration_password = "nucker"
671+ self.assertEquals(self.controller.registration_password, "nucker")
672+
673+ def test_revert(self):
674+ """
675+ Test that we can revert the controller to it's initial state.
676+ """
677+ self.controller.load()
678+ self.assertEqual(self.controller.server_host_name,
679+ "landscape.canonical.com")
680+ self.controller.server_host_name = "landscape.localdomain"
681+ self.assertEqual(self.controller.server_host_name,
682+ "landscape.localdomain")
683+ self.controller.revert()
684+ self.assertEqual(self.controller.server_host_name,
685+ "landscape.canonical.com")
686+
687+ def test_is_modified(self):
688+ """
689+ Test that we can determine when something has been modified.
690+ """
691+ self.controller.load()
692+ self.assertFalse(self.controller.is_modified)
693+ self.controller.server_host_name = "bing.bang.a.bang"
694+ self.assertTrue(self.controller.is_modified)
695+ self.controller.revert()
696+ self.assertFalse(self.controller.is_modified)
697+ self.controller.account_name = "soldierBlue"
698+ self.assertTrue(self.controller.is_modified)
699+ self.controller.revert()
700+ self.assertFalse(self.controller.is_modified)
701+ self.controller.registration_password = "HesAnIndianCowboyInTheRodeo"
702+ self.assertTrue(self.controller.is_modified)
703+
704+ def test_commit(self):
705+ """
706+ Test that we can write configuration settings back to the config file.
707+ """
708+ self.controller.load()
709+ self.assertEqual(self.controller.server_host_name,
710+ "landscape.canonical.com")
711+ self.controller.server_host_name = "landscape.localdomain"
712+ self.assertEqual(self.controller.server_host_name,
713+ "landscape.localdomain")
714+ self.controller.commit()
715+ self.assertEqual(self.controller.server_host_name,
716+ "landscape.localdomain")
717+ self.controller.revert()
718+ self.assertEqual(self.controller.server_host_name,
719+ "landscape.localdomain")
720+
721+ def test_lock(self):
722+ """
723+ Test that we can lock out updates.
724+ """
725+ self.controller.load()
726+ self.controller.lock()
727+ self.assertRaises(ConfigControllerLockError, setattr, self.controller,
728+ "server_host_name", "faily.fail.com")
729+ self.assertFalse(self.controller.is_modified)
730+ self.controller.unlock()
731+ self.controller.server_host_name = "successy.success.org"
732+ self.assertTrue(self.controller.is_modified)
733+
734+ self.controller.revert()
735+ self.assertFalse(self.controller.is_modified)
736+
737+ self.controller.lock()
738+ self.assertRaises(ConfigControllerLockError,
739+ setattr,
740+ self.controller,
741+ "account_name",
742+ "Failbert")
743+ self.assertFalse(self.controller.is_modified)
744+ self.controller.unlock()
745+ self.assertFalse(self.controller.is_modified)
746+ self.controller.account_name = "Winbob"
747+ self.assertTrue(self.controller.is_modified)
748+
749+ self.controller.revert()
750+ self.assertFalse(self.controller.is_modified)
751+
752+ self.controller.lock()
753+ self.assertRaises(ConfigControllerLockError,
754+ setattr,
755+ self.controller,
756+ "registration_password",
757+ "I Fail")
758+ self.assertFalse(self.controller.is_modified)
759+ self.controller.unlock()
760+ self.assertFalse(self.controller.is_modified)
761+ self.controller.registration_password = "I Win"
762+ self.assertTrue(self.controller.is_modified)
763+
764+ def test_defaulting(self):
765+ """
766+ Test we set the correct values when switching between hosted and
767+ dedicated.
768+ """
769+ self.makeFile("", path=self.config_filename) # empty the config file
770+ self.controller.load()
771+ self.assertEqual(self.controller.account_name, None)
772+ self.assertEqual(self.controller.registration_password, None)
773+ self.assertEqual(self.controller.server_host_name,
774+ "landscape.canonical.com")
775+ self.controller.default_dedicated()
776+ self.assertEqual(self.controller.account_name, None)
777+ self.assertEqual(self.controller.registration_password, None)
778+ self.assertEqual(self.controller.server_host_name,
779+ "landscape.localdomain")
780+ self.controller.default_hosted()
781+ self.assertEqual(self.controller.account_name, None)
782+ self.assertEqual(self.controller.registration_password, None)
783+ self.assertEqual(self.controller.server_host_name,
784+ "landscape.canonical.com")
785+ self.controller.default_dedicated()
786+ self.controller.server_host_name = "test.machine"
787+ self.controller.default_dedicated()
788+ self.assertEqual(self.controller.server_host_name, "test.machine")
789+ self.controller.default_hosted()
790+ self.assertEqual(self.controller.server_host_name,
791+ "landscape.canonical.com")
792+ self.controller.default_dedicated()
793+ self.assertEqual(self.controller.server_host_name, "test.machine")
794
795=== added directory 'landscape/ui/model'
796=== added file 'landscape/ui/model/__init__.py'
797=== added directory 'landscape/ui/view'
798=== added file 'landscape/ui/view/__init__.py'
799=== added file 'landscape/ui/view/configuration.py'
800--- landscape/ui/view/configuration.py 1970-01-01 00:00:00 +0000
801+++ landscape/ui/view/configuration.py 2012-01-09 11:38:25 +0000
802@@ -0,0 +1,152 @@
803+import os
804+
805+from gi.repository import Gtk
806+
807+
808+class ClientSettingsDialog(Gtk.Dialog):
809+
810+ GLADE_FILE = "landscape-client-settings.glade"
811+
812+ def __init__(self, controller, data_path=None, *args, **kwargs):
813+ super(ClientSettingsDialog, self).__init__(*args, **kwargs)
814+ self.controller = controller
815+ self._ui_path = os.path.join(
816+ os.path.dirname(__file__), "ui",
817+ ClientSettingsDialog.GLADE_FILE)
818+ self._builder = Gtk.Builder()
819+ self._builder.add_from_file(self._ui_path)
820+ self._setup_ui()
821+ self._hosted_toggle = None
822+ self._dedicated_toggle = None
823+ self.revert(self._revert_button)
824+
825+ def _setup_window(self):
826+ """
827+ Configure the dialog window and pack content from the Glade UI file
828+ into the main content area.
829+ """
830+ self.set_title("Client Settings")
831+ content_area = self.get_content_area()
832+ vbox = self._builder.get_object(
833+ "landscape-client-settings-dialog-vbox")
834+ vbox.unparent()
835+ content_area.pack_start(vbox, expand=True, fill=True, padding=0)
836+
837+ def _setup_action_buttons(self):
838+ """
839+ Obtain handles for action buttons and connect them to handlers.
840+ """
841+ self._close_button = self._builder.get_object("close-button")
842+ self._revert_button = self._builder.get_object("revert-button")
843+ self._revert_button.connect("clicked", self.revert)
844+ self._close_button.connect("clicked", self._possibly_save_and_exit)
845+
846+ def _setup_entries(self):
847+ """
848+ Obtain handles for entry widgets, set initial state and connect them to
849+ handlers.
850+ """
851+ self._account_entry = self._builder.get_object("account-name-entry")
852+ self._password_entry = self._builder.get_object(
853+ "reigstered-password-entry")
854+ self._server_host_name_entry = self._builder.get_object(
855+ "server-host-name-entry")
856+ self._account_entry.set_sensitive(False)
857+ self._password_entry.set_sensitive(False)
858+ self._server_host_name_entry.set_sensitive(False)
859+ self._account_entry.connect("changed", self._update_account)
860+ self._password_entry.connect("changed", self._update_password)
861+ self._server_host_name_entry.connect("changed",
862+ self._update_server_host_name)
863+
864+ def _setup_radiobuttons(self):
865+ """
866+ Obtain handles on radiobuttons and connect them to handler.
867+ """
868+ self._hosted_radiobutton = self._builder.get_object(
869+ "hosted-radiobutton")
870+ self._dedicated_radiobutton = self._builder.get_object(
871+ "dedicated-radiobutton")
872+
873+ def _setup_ui(self):
874+ self._setup_window()
875+ self._setup_radiobuttons()
876+ self._setup_entries()
877+ self._setup_action_buttons()
878+
879+ def _load_data(self):
880+ self.controller.lock()
881+ if not self.controller.account_name is None:
882+ self._account_entry.set_text(self.controller.account_name)
883+ if not self.controller.registration_password is None:
884+ self._password_entry.set_text(
885+ self.controller.registration_password)
886+ if not self.controller.server_host_name is None:
887+ self._server_host_name_entry.set_text(
888+ self.controller.server_host_name)
889+ self.controller.unlock()
890+
891+ def _update_account(self, event):
892+ if not self.controller.is_locked():
893+ self.controller.account_name = self._account_entry.get_text()
894+
895+ def _update_password(self, event):
896+ if not self.controller.is_locked():
897+ self.controller.registration_password = \
898+ self._password_entry.get_text()
899+
900+ def _update_server_host_name(self, event):
901+ if not self.controller.is_locked():
902+ self.controller.server_host_name = \
903+ self._server_host_name_entry.get_text()
904+
905+ def _set_entry_sensitivity(self, hosted):
906+ if hosted:
907+ self._account_entry.set_sensitive(True)
908+ self._password_entry.set_sensitive(True)
909+ self._server_host_name_entry.set_sensitive(False)
910+ else:
911+ self._account_entry.set_sensitive(False)
912+ self._password_entry.set_sensitive(False)
913+ self._server_host_name_entry.set_sensitive(True)
914+
915+ def select_landscape_hosting(self):
916+ hosted = self._hosted_radiobutton.get_active()
917+ self._set_entry_sensitivity(hosted)
918+ if hosted:
919+ self.controller.default_hosted()
920+ self._load_data()
921+ else:
922+ self.controller.default_dedicated()
923+ self._load_data()
924+
925+ def _on_toggle_server_type_radiobutton(self, radiobutton):
926+ self.select_landscape_hosting()
927+ return True
928+
929+ def revert(self, button):
930+ self.controller.revert()
931+ if self._hosted_toggle:
932+ self._hosted_radiobutton.disconnect(self._hosted_toggle)
933+ if self._dedicated_toggle:
934+ self._dedicated_radiobutton.disconnect(self._dedicated_toggle)
935+ if self.controller.hosted:
936+ self._hosted_radiobutton.set_active(True)
937+ else:
938+ self._dedicated_radiobutton.set_active(True)
939+ self._set_entry_sensitivity(self.controller.hosted)
940+ self._load_data()
941+ self._hosted_toggle = self._hosted_radiobutton.connect(
942+ "toggled",
943+ self._on_toggle_server_type_radiobutton)
944+ self._dedicated_toggle = self._dedicated_radiobutton.connect(
945+ "toggled",
946+ self._on_toggle_server_type_radiobutton)
947+
948+ def _write_back(self):
949+ self.controller.commit()
950+
951+ def _possibly_save_and_exit(self, button):
952+ if self.controller.is_modified:
953+ self._write_back()
954+ self.destroy()
955
956=== added directory 'landscape/ui/view/tests'
957=== added file 'landscape/ui/view/tests/__init__.py'
958=== added file 'landscape/ui/view/tests/test_configuration.py'
959--- landscape/ui/view/tests/test_configuration.py 1970-01-01 00:00:00 +0000
960+++ landscape/ui/view/tests/test_configuration.py 2012-01-09 11:38:25 +0000
961@@ -0,0 +1,270 @@
962+import sys
963+
964+try:
965+ from gi.repository import Gtk
966+ got_gobject_introspection = True
967+except ImportError:
968+ got_gobject_introspection = False
969+ gobject_skip_message = "GObject Introspection module unavailable"
970+
971+from landscape.tests.helpers import LandscapeTest
972+from landscape.configuration import LandscapeSetupConfiguration
973+from landscape.ui.view.configuration import ClientSettingsDialog
974+from landscape.ui.controller.configuration import ConfigController
975+
976+
977+class ConfigurationViewTest(LandscapeTest):
978+
979+ def setUp(self):
980+ super(ConfigurationViewTest, self).setUp()
981+ config = "[client]\n"
982+ config += "data_path = %s\n" % sys.path[0]
983+ config += "http_proxy = http://proxy.localdomain:3192\n"
984+ config += "tags = a_tag\n"
985+ config += "url = https://landscape.canonical.com/message-system\n"
986+ config += "account_name = foo\n"
987+ config += "registration_password = bar\n"
988+ config += "computer_title = baz\n"
989+ config += "https_proxy = https://proxy.localdomain:6192\n"
990+ config += "ping_url = http://landscape.canonical.com/ping\n"
991+
992+ self.config_filename = self.makeFile(config)
993+
994+ class MySetupConfiguration(LandscapeSetupConfiguration):
995+ default_config_filenames = [self.config_filename]
996+
997+ self.config = MySetupConfiguration()
998+
999+ def test_init(self):
1000+ """
1001+ Test that we correctly initialise the L{ConfigurationView} correctly
1002+ from the controller.
1003+ """
1004+ controller = ConfigController(self.config)
1005+ dialog = ClientSettingsDialog(controller)
1006+ content_area = dialog.get_content_area()
1007+ children = content_area.get_children()
1008+ self.assertEqual(len(children), 2)
1009+ box = children[0]
1010+ self.assertIsInstance(box, Gtk.Box)
1011+ self.assertTrue(dialog._hosted_radiobutton.get_active())
1012+ self.assertFalse(dialog._dedicated_radiobutton.get_active())
1013+ self.assertTrue(dialog._account_entry.get_sensitive())
1014+ self.assertTrue(dialog._password_entry.get_sensitive())
1015+ self.assertFalse(dialog._server_host_name_entry.get_sensitive())
1016+
1017+ def test_toggle_radio_button(self):
1018+ """
1019+ Test that we disable and enable the correct entries when we toggle the
1020+ dialog radiobuttons.
1021+ """
1022+ controller = ConfigController(self.config)
1023+ dialog = ClientSettingsDialog(controller)
1024+ self.assertTrue(dialog._hosted_radiobutton.get_active())
1025+ self.assertFalse(dialog._dedicated_radiobutton.get_active())
1026+ self.assertTrue(dialog._account_entry.get_sensitive())
1027+ self.assertTrue(dialog._password_entry.get_sensitive())
1028+ self.assertFalse(dialog._server_host_name_entry.get_sensitive())
1029+ dialog._dedicated_radiobutton.set_active(True)
1030+ self.assertFalse(dialog._hosted_radiobutton.get_active())
1031+ self.assertTrue(dialog._dedicated_radiobutton.get_active())
1032+ self.assertFalse(dialog._account_entry.get_sensitive())
1033+ self.assertFalse(dialog._password_entry.get_sensitive())
1034+ self.assertTrue(dialog._server_host_name_entry.get_sensitive())
1035+ dialog._hosted_radiobutton.set_active(True)
1036+ self.assertTrue(dialog._hosted_radiobutton.get_active())
1037+ self.assertFalse(dialog._dedicated_radiobutton.get_active())
1038+ self.assertTrue(dialog._account_entry.get_sensitive())
1039+ self.assertTrue(dialog._password_entry.get_sensitive())
1040+ self.assertFalse(dialog._server_host_name_entry.get_sensitive())
1041+
1042+ def test_load_data_from_config(self):
1043+ """
1044+ Test that we load data into the appropriate entries from the
1045+ configuration file.
1046+ """
1047+ controller = ConfigController(self.config)
1048+ dialog = ClientSettingsDialog(controller)
1049+ self.assertEqual(dialog._account_entry.get_text(), "foo")
1050+ self.assertEqual(dialog._password_entry.get_text(), "bar")
1051+ self.assertEqual(dialog._server_host_name_entry.get_text(),
1052+ "landscape.canonical.com")
1053+
1054+ def test_revert(self):
1055+ """
1056+ Test that we can revert the UI values using the controller.
1057+ """
1058+ controller = ConfigController(self.config)
1059+ dialog = ClientSettingsDialog(controller)
1060+ self.assertEqual(dialog._account_entry.get_text(), "foo")
1061+ self.assertEqual(dialog._password_entry.get_text(), "bar")
1062+ self.assertEqual(dialog._server_host_name_entry.get_text(),
1063+ "landscape.canonical.com")
1064+ dialog._dedicated_radiobutton.set_active(True)
1065+ dialog._server_host_name_entry.set_text("more.barn")
1066+ self.assertEqual(dialog._account_entry.get_text(), "foo")
1067+ self.assertEqual(dialog._password_entry.get_text(), "bar")
1068+ self.assertEqual(dialog._server_host_name_entry.get_text(),
1069+ "more.barn")
1070+ self.assertTrue(dialog._dedicated_radiobutton.get_active())
1071+ self.assertFalse(dialog._hosted_radiobutton.get_active())
1072+ dialog.revert(None)
1073+ self.assertEqual(dialog._account_entry.get_text(), "foo")
1074+ self.assertEqual(dialog._password_entry.get_text(), "bar")
1075+ self.assertEqual(dialog._server_host_name_entry.get_text(),
1076+ "landscape.canonical.com")
1077+ self.assertFalse(dialog._dedicated_radiobutton.get_active())
1078+ self.assertTrue(dialog._hosted_radiobutton.get_active())
1079+
1080+ if not got_gobject_introspection:
1081+ test_revert.skip = gobject_skip_message
1082+ test_load_data_from_config.skip = gobject_skip_message
1083+ test_toggle_radio_button.skip = gobject_skip_message
1084+ test_init.skip = gobject_skip_message
1085+
1086+
1087+class ConfigurationViewCommitTest(LandscapeTest):
1088+
1089+ def setUp(self):
1090+ super(ConfigurationViewCommitTest, self).setUp()
1091+ config = "[client]\n"
1092+ config += "data_path = %s\n" % sys.path[0]
1093+ config += "http_proxy = http://proxy.localdomain:3192\n"
1094+ config += "tags = a_tag\n"
1095+ config += "url = https://landscape.canonical.com/message-system\n"
1096+ config += "account_name = foo\n"
1097+ config += "registration_password = bar\n"
1098+ config += "computer_title = baz\n"
1099+ config += "https_proxy = https://proxy.localdomain:6192\n"
1100+ config += "ping_url = http://landscape.canonical.com/ping\n"
1101+ self.config_filename = self.makeFile(config)
1102+
1103+ class MySetupConfiguration(LandscapeSetupConfiguration):
1104+ default_config_filenames = [self.config_filename]
1105+
1106+ self.config = MySetupConfiguration()
1107+ self.real_write_back = ClientSettingsDialog._write_back
1108+ self.write_back_called = False
1109+
1110+ def fake_write_back(obj):
1111+ self.write_back_called = True
1112+
1113+ ClientSettingsDialog._write_back = fake_write_back
1114+ self.controller = ConfigController(self.config)
1115+ self.dialog = ClientSettingsDialog(self.controller)
1116+
1117+ def tearDown(self):
1118+ self.controller = None
1119+ self.dialog.destroy()
1120+ self.dialog = None
1121+ while Gtk.events_pending():
1122+ Gtk.main_iteration()
1123+ self.write_back_called = False
1124+ ClientSettingsDialog._write_back = self.real_write_back
1125+ super(ConfigurationViewCommitTest, self).tearDown()
1126+
1127+ def test_commit_fresh_dialog(self):
1128+ """
1129+ Test that we don't save anything from an untouched dialog on exit
1130+ """
1131+ while Gtk.events_pending():
1132+ Gtk.main_iteration()
1133+ self.dialog._possibly_save_and_exit(None)
1134+ self.assertFalse(self.write_back_called)
1135+
1136+ def test_commit_hosted_account_name_change(self):
1137+ """
1138+ Test that we do save changes when we set a new account name for hosted
1139+ accounts.
1140+ """
1141+ self.dialog._hosted_radiobutton.set_active(True)
1142+ while Gtk.events_pending():
1143+ Gtk.main_iteration()
1144+ self.dialog._account_entry.set_text("glow")
1145+ while Gtk.events_pending():
1146+ Gtk.main_iteration()
1147+ self.dialog._possibly_save_and_exit(None)
1148+ self.assertTrue(self.write_back_called)
1149+
1150+ def test_commit_hosted_password_change(self):
1151+ """
1152+ Test that we do save changes when we set a new account name for hosted
1153+ accounts.
1154+ """
1155+ self.dialog._hosted_radiobutton.set_active(True)
1156+ self.dialog._password_entry.set_text("sticks")
1157+ while Gtk.events_pending():
1158+ Gtk.main_iteration()
1159+ self.dialog._possibly_save_and_exit(None)
1160+ self.assertTrue(self.write_back_called)
1161+
1162+ def test_commit_dedicated_server_host_name_change(self):
1163+ """
1164+ Test that we do save changes when we set a new server host name for
1165+ a dedicated server.
1166+ """
1167+ self.dialog._dedicated_radiobutton.set_active(True)
1168+ self.dialog._server_host_name_entry.set_text(
1169+ "that.isolated.geographic")
1170+ while Gtk.events_pending():
1171+ Gtk.main_iteration()
1172+ self.dialog._possibly_save_and_exit(None)
1173+ self.assertTrue(self.write_back_called)
1174+
1175+ if not got_gobject_introspection:
1176+ test_commit_dedicated_server_host_name_change.skip = \
1177+ gobject_skip_message
1178+ test_commit_hosted_password_change.skip = gobject_skip_message
1179+ test_commit_hosted_account_name_change.skip = gobject_skip_message
1180+ test_commit_fresh_dialog.skip = gobject_skip_message
1181+
1182+
1183+class DedicatedConfigurationViewTest(LandscapeTest):
1184+
1185+ def setUp(self):
1186+ super(DedicatedConfigurationViewTest, self).setUp()
1187+ config = "[client]\n"
1188+ config += "data_path = %s\n" % sys.path[0]
1189+ config += "url = https://landscape.localdomain/message-system\n"
1190+ config += "computer_title = baz\n"
1191+ config += "ping_url = http://landscape.localdomain/ping\n"
1192+ self.config_filename = self.makeFile(config)
1193+
1194+ class MySetupConfiguration(LandscapeSetupConfiguration):
1195+ default_config_filenames = [self.config_filename]
1196+
1197+ self.config = MySetupConfiguration()
1198+
1199+ def test_init(self):
1200+ """
1201+ Test that we correctly initialise the L{ConfigurationView} correctly
1202+ from the controller.
1203+ """
1204+ controller = ConfigController(self.config)
1205+ dialog = ClientSettingsDialog(controller)
1206+ content_area = dialog.get_content_area()
1207+ children = content_area.get_children()
1208+ self.assertEqual(len(children), 2)
1209+ box = children[0]
1210+ self.assertIsInstance(box, Gtk.Box)
1211+ self.assertFalse(dialog._hosted_radiobutton.get_active())
1212+ self.assertTrue(dialog._dedicated_radiobutton.get_active())
1213+ self.assertFalse(dialog._account_entry.get_sensitive())
1214+ self.assertFalse(dialog._password_entry.get_sensitive())
1215+ self.assertTrue(dialog._server_host_name_entry.get_sensitive())
1216+
1217+ def test_load_data_from_config(self):
1218+ """
1219+ Test that we load data into the appropriate entries from the
1220+ configuration file.
1221+ """
1222+ controller = ConfigController(self.config)
1223+ dialog = ClientSettingsDialog(controller)
1224+ self.assertEqual(dialog._account_entry.get_text(), "")
1225+ self.assertEqual(dialog._password_entry.get_text(), "")
1226+ self.assertEqual(dialog._server_host_name_entry.get_text(),
1227+ "landscape.localdomain")
1228+
1229+ if not got_gobject_introspection:
1230+ test_load_data_from_config.skip = gobject_skip_message
1231+ test_init.skip = gobject_skip_message
1232
1233=== added directory 'landscape/ui/view/ui'
1234=== added file 'landscape/ui/view/ui/landscape-client-settings.glade'
1235--- landscape/ui/view/ui/landscape-client-settings.glade 1970-01-01 00:00:00 +0000
1236+++ landscape/ui/view/ui/landscape-client-settings.glade 2012-01-09 11:38:25 +0000
1237@@ -0,0 +1,407 @@
1238+<?xml version="1.0" encoding="UTF-8"?>
1239+<interface>
1240+ <!-- interface-requires gtk+ 3.0 -->
1241+ <object class="GtkDialog" id="landscape-client-settings-dialog">
1242+ <property name="can_focus">False</property>
1243+ <property name="border_width">5</property>
1244+ <property name="title" translatable="yes">Landscape Client Settings</property>
1245+ <property name="resizable">False</property>
1246+ <property name="modal">True</property>
1247+ <property name="window_position">center</property>
1248+ <property name="icon_name">landscape-client-settings</property>
1249+ <property name="type_hint">dialog</property>
1250+ <property name="has_resize_grip">False</property>
1251+ <child internal-child="vbox">
1252+ <object class="GtkBox" id="landscape-client-settings-dialog-vbox">
1253+ <property name="can_focus">False</property>
1254+ <property name="orientation">vertical</property>
1255+ <property name="spacing">2</property>
1256+ <child internal-child="action_area">
1257+ <object class="GtkButtonBox" id="dialog-action_area1">
1258+ <property name="can_focus">False</property>
1259+ <property name="layout_style">end</property>
1260+ <child>
1261+ <object class="GtkButton" id="revert-button">
1262+ <property name="label">gtk-revert-to-saved</property>
1263+ <property name="visible">True</property>
1264+ <property name="can_focus">True</property>
1265+ <property name="receives_default">True</property>
1266+ <property name="use_action_appearance">False</property>
1267+ <property name="use_stock">True</property>
1268+ </object>
1269+ <packing>
1270+ <property name="expand">False</property>
1271+ <property name="fill">True</property>
1272+ <property name="position">0</property>
1273+ </packing>
1274+ </child>
1275+ <child>
1276+ <object class="GtkButton" id="close-button">
1277+ <property name="label">gtk-close</property>
1278+ <property name="visible">True</property>
1279+ <property name="can_focus">True</property>
1280+ <property name="receives_default">True</property>
1281+ <property name="use_action_appearance">False</property>
1282+ <property name="use_stock">True</property>
1283+ </object>
1284+ <packing>
1285+ <property name="expand">False</property>
1286+ <property name="fill">True</property>
1287+ <property name="position">1</property>
1288+ </packing>
1289+ </child>
1290+ </object>
1291+ <packing>
1292+ <property name="expand">False</property>
1293+ <property name="fill">True</property>
1294+ <property name="pack_type">end</property>
1295+ <property name="position">0</property>
1296+ </packing>
1297+ </child>
1298+ <child>
1299+ <object class="GtkBox" id="vbox">
1300+ <property name="visible">True</property>
1301+ <property name="can_focus">False</property>
1302+ <property name="orientation">vertical</property>
1303+ <property name="spacing">6</property>
1304+ <child>
1305+ <object class="GtkFrame" id="landscape-server-type-frame">
1306+ <property name="visible">True</property>
1307+ <property name="can_focus">False</property>
1308+ <property name="label_xalign">0</property>
1309+ <property name="shadow_type">none</property>
1310+ <child>
1311+ <object class="GtkAlignment" id="landscape-server-type-frame-alignment">
1312+ <property name="visible">True</property>
1313+ <property name="can_focus">False</property>
1314+ <property name="hexpand">True</property>
1315+ <property name="vexpand">True</property>
1316+ <property name="top_padding">6</property>
1317+ <property name="left_padding">12</property>
1318+ <child>
1319+ <object class="GtkBox" id="server-type-options-vbox">
1320+ <property name="visible">True</property>
1321+ <property name="can_focus">False</property>
1322+ <property name="hexpand">True</property>
1323+ <property name="vexpand">True</property>
1324+ <property name="orientation">vertical</property>
1325+ <property name="spacing">12</property>
1326+ <child>
1327+ <object class="GtkBox" id="hosted-hbox">
1328+ <property name="visible">True</property>
1329+ <property name="can_focus">False</property>
1330+ <property name="hexpand">True</property>
1331+ <property name="vexpand">True</property>
1332+ <child>
1333+ <object class="GtkAlignment" id="hosted-radiobutton-alignment">
1334+ <property name="visible">True</property>
1335+ <property name="can_focus">False</property>
1336+ <property name="yalign">0</property>
1337+ <child>
1338+ <object class="GtkRadioButton" id="hosted-radiobutton">
1339+ <property name="visible">True</property>
1340+ <property name="can_focus">True</property>
1341+ <property name="receives_default">False</property>
1342+ <property name="halign">start</property>
1343+ <property name="valign">start</property>
1344+ <property name="use_action_appearance">False</property>
1345+ <property name="xalign">0</property>
1346+ <property name="active">True</property>
1347+ <property name="draw_indicator">True</property>
1348+ <property name="group">dedicated-radiobutton</property>
1349+ <child>
1350+ <object class="GtkLabel" id="hosted-radiobutton-label">
1351+ <property name="visible">True</property>
1352+ <property name="can_focus">False</property>
1353+ <property name="xalign">0</property>
1354+ <attributes>
1355+ <attribute name="weight" value="bold"/>
1356+ </attributes>
1357+ </object>
1358+ </child>
1359+ </object>
1360+ </child>
1361+ </object>
1362+ <packing>
1363+ <property name="expand">False</property>
1364+ <property name="fill">True</property>
1365+ <property name="position">0</property>
1366+ </packing>
1367+ </child>
1368+ <child>
1369+ <object class="GtkFrame" id="hosted-frame">
1370+ <property name="visible">True</property>
1371+ <property name="can_focus">False</property>
1372+ <property name="hexpand">True</property>
1373+ <property name="label_xalign">0</property>
1374+ <property name="shadow_type">none</property>
1375+ <child>
1376+ <object class="GtkAlignment" id="hosted-frame-alignment">
1377+ <property name="visible">True</property>
1378+ <property name="can_focus">False</property>
1379+ <property name="left_padding">12</property>
1380+ <child>
1381+ <object class="GtkBox" id="hosted-vbox">
1382+ <property name="visible">True</property>
1383+ <property name="can_focus">False</property>
1384+ <property name="hexpand">True</property>
1385+ <property name="vexpand">True</property>
1386+ <property name="orientation">vertical</property>
1387+ <property name="spacing">6</property>
1388+ <child>
1389+ <object class="GtkBox" id="account-hbox">
1390+ <property name="visible">True</property>
1391+ <property name="can_focus">False</property>
1392+ <property name="hexpand">True</property>
1393+ <property name="spacing">12</property>
1394+ <child>
1395+ <object class="GtkLabel" id="account-label">
1396+ <property name="visible">True</property>
1397+ <property name="can_focus">False</property>
1398+ <property name="xalign">0</property>
1399+ <property name="label" translatable="yes">Account name: </property>
1400+ </object>
1401+ <packing>
1402+ <property name="expand">False</property>
1403+ <property name="fill">True</property>
1404+ <property name="position">0</property>
1405+ </packing>
1406+ </child>
1407+ <child>
1408+ <object class="GtkEntry" id="account-name-entry">
1409+ <property name="visible">True</property>
1410+ <property name="can_focus">True</property>
1411+ <property name="hexpand">True</property>
1412+ <property name="invisible_char">•</property>
1413+ <property name="invisible_char_set">True</property>
1414+ </object>
1415+ <packing>
1416+ <property name="expand">True</property>
1417+ <property name="fill">True</property>
1418+ <property name="position">1</property>
1419+ </packing>
1420+ </child>
1421+ </object>
1422+ <packing>
1423+ <property name="expand">True</property>
1424+ <property name="fill">True</property>
1425+ <property name="position">0</property>
1426+ </packing>
1427+ </child>
1428+ <child>
1429+ <object class="GtkBox" id="password-hbox">
1430+ <property name="visible">True</property>
1431+ <property name="can_focus">False</property>
1432+ <property name="spacing">12</property>
1433+ <child>
1434+ <object class="GtkLabel" id="password-label">
1435+ <property name="visible">True</property>
1436+ <property name="can_focus">False</property>
1437+ <property name="xalign">0</property>
1438+ <property name="label" translatable="yes">Password:</property>
1439+ </object>
1440+ <packing>
1441+ <property name="expand">False</property>
1442+ <property name="fill">True</property>
1443+ <property name="position">0</property>
1444+ </packing>
1445+ </child>
1446+ <child>
1447+ <object class="GtkEntry" id="reigstered-password-entry">
1448+ <property name="visible">True</property>
1449+ <property name="can_focus">True</property>
1450+ <property name="hexpand">True</property>
1451+ <property name="visibility">False</property>
1452+ <property name="invisible_char">•</property>
1453+ <property name="invisible_char_set">True</property>
1454+ </object>
1455+ <packing>
1456+ <property name="expand">True</property>
1457+ <property name="fill">True</property>
1458+ <property name="position">1</property>
1459+ </packing>
1460+ </child>
1461+ </object>
1462+ <packing>
1463+ <property name="expand">True</property>
1464+ <property name="fill">True</property>
1465+ <property name="position">1</property>
1466+ </packing>
1467+ </child>
1468+ </object>
1469+ </child>
1470+ </object>
1471+ </child>
1472+ <child type="label">
1473+ <object class="GtkLabel" id="hosted-frame-label">
1474+ <property name="visible">True</property>
1475+ <property name="can_focus">False</property>
1476+ <property name="margin_top">4</property>
1477+ <property name="label" translatable="yes">&lt;b&gt;Hosted&lt;/b&gt;</property>
1478+ <property name="use_markup">True</property>
1479+ </object>
1480+ </child>
1481+ </object>
1482+ <packing>
1483+ <property name="expand">False</property>
1484+ <property name="fill">True</property>
1485+ <property name="position">1</property>
1486+ </packing>
1487+ </child>
1488+ </object>
1489+ <packing>
1490+ <property name="expand">True</property>
1491+ <property name="fill">True</property>
1492+ <property name="position">0</property>
1493+ </packing>
1494+ </child>
1495+ <child>
1496+ <object class="GtkBox" id="dedicated-hbox">
1497+ <property name="visible">True</property>
1498+ <property name="can_focus">False</property>
1499+ <child>
1500+ <object class="GtkRadioButton" id="dedicated-radiobutton">
1501+ <property name="visible">True</property>
1502+ <property name="can_focus">True</property>
1503+ <property name="receives_default">False</property>
1504+ <property name="valign">start</property>
1505+ <property name="use_action_appearance">False</property>
1506+ <property name="xalign">0</property>
1507+ <property name="active">True</property>
1508+ <property name="draw_indicator">True</property>
1509+ <child>
1510+ <object class="GtkAlignment" id="alignment3">
1511+ <property name="visible">True</property>
1512+ <property name="can_focus">False</property>
1513+ <child>
1514+ <object class="GtkLabel" id="dedicated-radiobutton-label">
1515+ <property name="visible">True</property>
1516+ <property name="can_focus">False</property>
1517+ <property name="xalign">0</property>
1518+ <attributes>
1519+ <attribute name="weight" value="bold"/>
1520+ </attributes>
1521+ </object>
1522+ </child>
1523+ </object>
1524+ </child>
1525+ </object>
1526+ <packing>
1527+ <property name="expand">False</property>
1528+ <property name="fill">True</property>
1529+ <property name="position">0</property>
1530+ </packing>
1531+ </child>
1532+ <child>
1533+ <object class="GtkFrame" id="dedicated-frame">
1534+ <property name="visible">True</property>
1535+ <property name="can_focus">False</property>
1536+ <property name="margin_top">4</property>
1537+ <property name="hexpand">True</property>
1538+ <property name="label_xalign">0</property>
1539+ <property name="shadow_type">none</property>
1540+ <child>
1541+ <object class="GtkAlignment" id="dedicated-frame-alignment">
1542+ <property name="visible">True</property>
1543+ <property name="can_focus">False</property>
1544+ <property name="left_padding">12</property>
1545+ <child>
1546+ <object class="GtkBox" id="host-name-hbox">
1547+ <property name="visible">True</property>
1548+ <property name="can_focus">False</property>
1549+ <property name="hexpand">True</property>
1550+ <property name="spacing">12</property>
1551+ <child>
1552+ <object class="GtkLabel" id="server-host-name-label">
1553+ <property name="visible">True</property>
1554+ <property name="can_focus">False</property>
1555+ <property name="xalign">0</property>
1556+ <property name="label" translatable="yes">Server host name:</property>
1557+ </object>
1558+ <packing>
1559+ <property name="expand">False</property>
1560+ <property name="fill">True</property>
1561+ <property name="position">0</property>
1562+ </packing>
1563+ </child>
1564+ <child>
1565+ <object class="GtkEntry" id="server-host-name-entry">
1566+ <property name="visible">True</property>
1567+ <property name="can_focus">True</property>
1568+ <property name="hexpand">True</property>
1569+ <property name="invisible_char">•</property>
1570+ <property name="invisible_char_set">True</property>
1571+ </object>
1572+ <packing>
1573+ <property name="expand">True</property>
1574+ <property name="fill">True</property>
1575+ <property name="position">1</property>
1576+ </packing>
1577+ </child>
1578+ </object>
1579+ </child>
1580+ </object>
1581+ </child>
1582+ <child type="label">
1583+ <object class="GtkLabel" id="dedicated-frame-label">
1584+ <property name="visible">True</property>
1585+ <property name="can_focus">False</property>
1586+ <property name="label" translatable="yes">&lt;b&gt;Dedicated server&lt;/b&gt;</property>
1587+ <property name="use_markup">True</property>
1588+ </object>
1589+ </child>
1590+ </object>
1591+ <packing>
1592+ <property name="expand">False</property>
1593+ <property name="fill">True</property>
1594+ <property name="position">1</property>
1595+ </packing>
1596+ </child>
1597+ </object>
1598+ <packing>
1599+ <property name="expand">True</property>
1600+ <property name="fill">True</property>
1601+ <property name="position">1</property>
1602+ </packing>
1603+ </child>
1604+ </object>
1605+ </child>
1606+ </object>
1607+ </child>
1608+ <child type="label">
1609+ <object class="GtkLabel" id="landscape-server-type-label">
1610+ <property name="visible">True</property>
1611+ <property name="can_focus">False</property>
1612+ <property name="label" translatable="yes">&lt;b&gt;Landscape Server Type:&lt;/b&gt;</property>
1613+ <property name="use_markup">True</property>
1614+ </object>
1615+ </child>
1616+ </object>
1617+ <packing>
1618+ <property name="expand">False</property>
1619+ <property name="fill">True</property>
1620+ <property name="position">0</property>
1621+ </packing>
1622+ </child>
1623+ </object>
1624+ <packing>
1625+ <property name="expand">False</property>
1626+ <property name="fill">True</property>
1627+ <property name="position">1</property>
1628+ </packing>
1629+ </child>
1630+ </object>
1631+ </child>
1632+ <action-widgets>
1633+ <action-widget response="1">revert-button</action-widget>
1634+ <action-widget response="0">close-button</action-widget>
1635+ </action-widgets>
1636+ </object>
1637+ <object class="GtkSizeGroup" id="property-label-sizegroup">
1638+ <widgets>
1639+ <widget name="server-host-name-label"/>
1640+ <widget name="password-label"/>
1641+ <widget name="account-label"/>
1642+ </widgets>
1643+ </object>
1644+</interface>
1645
1646=== added file 'scripts/landscape-client-settings-ui'
1647--- scripts/landscape-client-settings-ui 1970-01-01 00:00:00 +0000
1648+++ scripts/landscape-client-settings-ui 2012-01-09 11:38:25 +0000
1649@@ -0,0 +1,17 @@
1650+#!/usr/bin/python
1651+import os
1652+import sys
1653+
1654+script_dir = os.path.abspath("scripts")
1655+if os.path.dirname(os.path.abspath(sys.argv[0])) == script_dir:
1656+ sys.path.insert(0, "./")
1657+else:
1658+ from landscape.lib.warning import hide_warnings
1659+ hide_warnings()
1660+
1661+from landscape.ui.controller.app import SettingsApplicationController
1662+
1663+
1664+if __name__ == "__main__":
1665+ app = SettingsApplicationController(args=sys.argv[1:])
1666+ app.run(None)

Subscribers

People subscribed via source and target branches

to all changes: