Merge lp:~tealeg/landscape-client/settings-ui into lp:~landscape/landscape-client/trunk
- settings-ui
- Merge into trunk
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 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Free Ekanayaka (community) | Approve | ||
Fernando Correa Neto (community) | Approve | ||
Review via email: mp+87393@code.launchpad.net |
Commit message
Description of the change
Firstly see: https:/
This branch adds a script "landscape-
* 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/
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.
- 452. By Geoff Teale
-
Resolved divergance and conflicts
- 453. By Geoff Teale
-
Fixed typo: configfuration -> configuration
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://
+ping_url = http://
Why this? This configuration is supposed to work locally against ./dev/advicedog, I believe this change breaks it.
[2]
landscape/
landscape/
landscape/
landscape/
[3]
+ # register(config)
Is this a leftover?
[4]
+ self.__
+ self.__
+ self.__
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 LandscapeSettin
and
+class LandscapeSettin
As discussed on IRC, passing [] to the constructor is probably broken, and it'd be good to use LandscapeSetupC
class LandscapeSetupC
fetch_
and LandscapeSetupC
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 LandscapeSettin
gsConfiguration 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 LandscapeSetupC
onfiguration
- 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
Fernando Correa Neto (fcorrea) wrote : | # |
Looks great, +1!
- 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 SettingsConfigu
ration
- Remove landscape.ui.model. configuration
- Replace references to SettingsConfiguration with LandscapeSetupC onfiguration
Free Ekanayaka (free.ekanayaka) wrote : | # |
Hi Geoff, some more comments.
[7]
+if os.path.
+ sys.path.insert(0, "./")
+ data_path = os.path.
+ print data_path
+else:
+ from landscape.
+ hide_warnings()
+ data_path = None
+ print "B"
I suspect the print statements here are a leftover.
[8]
+ # self.select_
This line is commented.
[9]
+ if data_path is None:
+ self._ui_path = os.path.join(
+ controller.
+ ClientSettingsD
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 SettingsConfigu
+ 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._configura
As mentioned, this should be passed argv, so we can have:
./scripts/
(root-client.conf is the default file that you would use for testing a branch against a local server, like ./scripts/
[13]
+ @server_
+ def server_
I'd rename it to _set_server_
+ @account_
+ def account_name(self, value):
[14]
+ self._configura
+ self._load_
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 ConfigControlle
def setUp(self):
config = "[client]"
config += "data_path = /var/lib/
config += "http_proxy = http://
config += "tags = a_tag\n"
config += "url = https:/
config += "account_name = foo\n"
config += "registration_
config += "computer_title = baz\n"
config += "https_proxy = https:/
config += "ping_url = http://
class MySettingsConfi
self.config = MySettingsConfi
...
- 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
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.
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 ClientSettingsD
[18]
+ def __init__(self, controller, data_path=None, *args, **kwargs):
Are *args and **kwargs necessary? If not, I'd drop them.
Preview Diff
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"><b>Hosted</b></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"><b>Dedicated server</b></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"><b>Landscape Server Type:</b></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) |
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): initial_ server_ host_name = self.DEFAULT_ SERVER_ HOST_NAME configuration = configuration configuration configuration. load([] ) load_data_ from_config( )
83 + self.__lock_out = True
84 + self.__lock = threading.Lock()
85 + self.__
86 + self.__
87 + self.__
88 + self.__
89 + self.__
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): index(" ://") + 3:] protocol[ :without_ protocol. index(" /")]
161 + "Extract the hostname part from a url"
162 + try:
163 + without_protocol = url[url.
164 + except ValueError:
165 + without_protocol = url
166 + try:
167 + return without_
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): SettingsApplica tionControllerU ISetupTest, self).setUp()
355 + super(Landscape
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(): proxy.localdoma in:3192 /landscape. canonical. com/message- system password = bar /proxy. localdomain: 6192 landscape. canonical. com/ping
366 + configdata = """
367 +[client]
368 +data_path = %s
369 +http_proxy = http://
370 +tags = a_tag
371 +url = https:/
372 +account_name = foo
373 +registration_
374 +computer_title = baz
375 +https_proxy = https:/
376 +ping_url = http://
377 +
378 +""" % sys.path[0]
Maybe we could make it less unindented:
def get_config():
"data_path = %s"
"http_proxy = http:// proxy.localdoma in: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]
configdata = ("[client]"
[6]
381 + class MyLandscapeSett ingsConfigurati on( gsConfiguration ): config_ filenames = [config_filena...
382 + LandscapeSettin
383 + default_