Merge lp:~frankban/juju-quickstart/local-osx into lp:juju-quickstart
- local-osx
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 82 |
Proposed branch: | lp:~frankban/juju-quickstart/local-osx |
Merge into: | lp:juju-quickstart |
Diff against target: |
301 lines (+114/-22) 6 files modified
quickstart/cli/views.py (+35/-11) quickstart/manage.py (+1/-1) quickstart/models/envs.py (+6/-2) quickstart/tests/cli/test_views.py (+55/-5) quickstart/tests/models/test_envs.py (+16/-2) quickstart/tests/test_manage.py (+1/-1) |
To merge this branch: | bzr merge lp:~frankban/juju-quickstart/local-osx |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Juju GUI Hackers | Pending | ||
Review via email:
|
Commit message
Description of the change
Avoid proposing LXC envs as an option on MacOS.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Francesco Banconi (frankban) wrote : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Brad Crittenden (bac) wrote : | # |
LGTM with some typo fixes and QA-ok. Thanks Francesco.
https:/
File quickstart/
https:/
quickstart/
representing whether or not local
typo: s/arguments/
https:/
quickstart/
local environments is not present if they
typo: s/environments/
- 90. By Francesco Banconi
-
Fix typos.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Francesco Banconi (frankban) wrote : | # |
*** Submitted:
Avoid proposing LXC envs as an option on MacOS.
R=bac
CC=
https:/
https:/
File quickstart/
https:/
quickstart/
representing whether or not local
On 2014/06/16 12:27:11, bac wrote:
> typo: s/arguments/
Done.
https:/
quickstart/
local environments is not present if they
On 2014/06/16 12:27:11, bac wrote:
> typo: s/environments/
Done.
Preview Diff
1 | === modified file 'quickstart/cli/views.py' | |||
2 | --- quickstart/cli/views.py 2014-04-23 15:53:32 +0000 | |||
3 | +++ quickstart/cli/views.py 2014-06-16 12:33:53 +0000 | |||
4 | @@ -102,7 +102,10 @@ | |||
5 | 102 | 102 | ||
6 | 103 | import urwid | 103 | import urwid |
7 | 104 | 104 | ||
9 | 105 | from quickstart import settings | 105 | from quickstart import ( |
10 | 106 | platform_support, | ||
11 | 107 | settings, | ||
12 | 108 | ) | ||
13 | 106 | from quickstart.cli import ( | 109 | from quickstart.cli import ( |
14 | 107 | base, | 110 | base, |
15 | 108 | forms, | 111 | forms, |
16 | @@ -170,6 +173,9 @@ | |||
17 | 170 | # Use the newly created environment. | 173 | # Use the newly created environment. |
18 | 171 | raise ui.AppExit((env_db, env_data)) | 174 | raise ui.AppExit((env_db, env_data)) |
19 | 172 | 175 | ||
20 | 176 | platform = platform_support.get_platform() | ||
21 | 177 | supports_local = platform_support.supports_local(platform) | ||
22 | 178 | |||
23 | 173 | if environments: | 179 | if environments: |
24 | 174 | title = 'Select an existing Juju environment or create a new one' | 180 | title = 'Select an existing Juju environment or create a new one' |
25 | 175 | widgets = [ | 181 | widgets = [ |
26 | @@ -188,16 +194,24 @@ | |||
27 | 188 | 'Quickstart in interactive mode again by passing the -i flag, ' | 194 | 'Quickstart in interactive mode again by passing the -i flag, ' |
28 | 189 | 'e.g.:\n', | 195 | 'e.g.:\n', |
29 | 190 | ('highlight', '$ juju quickstart -i'), | 196 | ('highlight', '$ juju quickstart -i'), |
30 | 191 | '\n\nAt the bottom of the page you can find links to manually ' | ||
31 | 192 | 'create new environments. If you instead prefer to quickly ' | ||
32 | 193 | 'start your Juju experience in a local environment (LXC), ' | ||
33 | 194 | 'just click the link below:' | ||
34 | 195 | ]), | 197 | ]), |
35 | 196 | urwid.Divider(), | ||
36 | 197 | ui.MenuButton( | ||
37 | 198 | '\N{BULLET} automatically create and bootstrap a local ' | ||
38 | 199 | 'environment', ui.thunk(create_and_start_local_env)), | ||
39 | 200 | ] | 198 | ] |
40 | 199 | # If the current platform supports local Juju environments, add an | ||
41 | 200 | # option to automatically create and bootstrap one. | ||
42 | 201 | if supports_local: | ||
43 | 202 | widgets.extend([ | ||
44 | 203 | urwid.Text([ | ||
45 | 204 | '\nAt the bottom of the page you can find links to ' | ||
46 | 205 | 'manually create new environments. If you instead prefer ' | ||
47 | 206 | 'to quickly start your Juju experience in a local ' | ||
48 | 207 | 'environment (LXC), just click the link below:' | ||
49 | 208 | ]), | ||
50 | 209 | urwid.Divider(), | ||
51 | 210 | ui.MenuButton( | ||
52 | 211 | '\N{BULLET} automatically create and bootstrap a local ' | ||
53 | 212 | 'environment', ui.thunk(create_and_start_local_env)), | ||
54 | 213 | ]) | ||
55 | 214 | |||
56 | 201 | app.set_title(title) | 215 | app.set_title(title) |
57 | 202 | # Start creating the page contents: a list of selectable environments. | 216 | # Start creating the page contents: a list of selectable environments. |
58 | 203 | # Wouldn't it be nice if we were able to highlight in some way the | 217 | # Wouldn't it be nice if we were able to highlight in some way the |
59 | @@ -225,7 +239,8 @@ | |||
60 | 225 | env_short_description = envs.get_env_short_description(env_data) | 239 | env_short_description = envs.get_env_short_description(env_data) |
61 | 226 | text = [bullet, ' {}'.format(env_short_description)] | 240 | text = [bullet, ' {}'.format(env_short_description)] |
62 | 227 | widgets.append(ui.MenuButton(text, ui.thunk(detail_view, env_data))) | 241 | widgets.append(ui.MenuButton(text, ui.thunk(detail_view, env_data))) |
64 | 228 | # Add the buttons used to create new environments. | 242 | |
65 | 243 | # Set up the "create a new environment" section. | ||
66 | 229 | widgets.extend([ | 244 | widgets.extend([ |
67 | 230 | urwid.Divider(), | 245 | urwid.Divider(), |
68 | 231 | urwid.Text(( | 246 | urwid.Text(( |
69 | @@ -236,14 +251,23 @@ | |||
70 | 236 | # series matches one of the series supported by the GUI. | 251 | # series matches one of the series supported by the GUI. |
71 | 237 | # Suggest the most recent supported series by pre-filling the value. | 252 | # Suggest the most recent supported series by pre-filling the value. |
72 | 238 | preferred_series = settings.JUJU_GUI_SUPPORTED_SERIES[-1] | 253 | preferred_series = settings.JUJU_GUI_SUPPORTED_SERIES[-1] |
73 | 254 | # Retrieve the list of supported environment types: exclude the local | ||
74 | 255 | # environment if not supported by the current OS platform. | ||
75 | 256 | filter_function = None | ||
76 | 257 | if not supports_local: | ||
77 | 258 | filter_function = lambda env_type, _: env_type != 'local' | ||
78 | 259 | supported_env_types = envs.get_supported_env_types( | ||
79 | 260 | env_type_db, filter_function=filter_function) | ||
80 | 261 | # Add the buttons used to create new environments. | ||
81 | 239 | widgets.extend([ | 262 | widgets.extend([ |
82 | 240 | ui.MenuButton( | 263 | ui.MenuButton( |
83 | 241 | ['\N{BULLET} new ', ('highlight', label), ' environment'], | 264 | ['\N{BULLET} new ', ('highlight', label), ' environment'], |
84 | 242 | ui.thunk(edit_view, { | 265 | ui.thunk(edit_view, { |
85 | 243 | 'type': env_type, 'default-series': preferred_series}) | 266 | 'type': env_type, 'default-series': preferred_series}) |
86 | 244 | ) | 267 | ) |
88 | 245 | for env_type, label in envs.get_supported_env_types(env_type_db) | 268 | for env_type, label in supported_env_types |
89 | 246 | ]) | 269 | ]) |
90 | 270 | |||
91 | 247 | # Set up the application status messages. | 271 | # Set up the application status messages. |
92 | 248 | status = [' \N{UPWARDS ARROW LEFTWARDS OF DOWNWARDS ARROW} navigate '] | 272 | status = [' \N{UPWARDS ARROW LEFTWARDS OF DOWNWARDS ARROW} navigate '] |
93 | 249 | if default_found: | 273 | if default_found: |
94 | 250 | 274 | ||
95 | === modified file 'quickstart/manage.py' | |||
96 | --- quickstart/manage.py 2014-06-13 14:36:30 +0000 | |||
97 | +++ quickstart/manage.py 2014-06-16 12:33:53 +0000 | |||
98 | @@ -290,7 +290,7 @@ | |||
99 | 290 | no_local_support = not platform_support.supports_local(options.platform) | 290 | no_local_support = not platform_support.supports_local(options.platform) |
100 | 291 | if options.env_type == 'local' and no_local_support: | 291 | if options.env_type == 'local' and no_local_support: |
101 | 292 | return parser.error( | 292 | return parser.error( |
103 | 293 | 'This host platform does not support local environments.' | 293 | 'this host platform does not support local environments' |
104 | 294 | ) | 294 | ) |
105 | 295 | # Update the options namespace with the new values. | 295 | # Update the options namespace with the new values. |
106 | 296 | options.admin_secret = env_data.get('admin-secret') | 296 | options.admin_secret = env_data.get('admin-secret') |
107 | 297 | 297 | ||
108 | === modified file 'quickstart/models/envs.py' | |||
109 | --- quickstart/models/envs.py 2014-04-23 11:22:44 +0000 | |||
110 | +++ quickstart/models/envs.py 2014-06-16 12:33:53 +0000 | |||
111 | @@ -685,15 +685,19 @@ | |||
112 | 685 | return env_type_db | 685 | return env_type_db |
113 | 686 | 686 | ||
114 | 687 | 687 | ||
116 | 688 | def get_supported_env_types(env_type_db): | 688 | def get_supported_env_types(env_type_db, filter_function=None): |
117 | 689 | """Return a list of supported (provider type, label) tuples. | 689 | """Return a list of supported (provider type, label) tuples. |
118 | 690 | 690 | ||
119 | 691 | Each tuple represents an environment type supported by Quickstart. | 691 | Each tuple represents an environment type supported by Quickstart. |
120 | 692 | It is possible to filter results by providing a filter_function callable | ||
121 | 693 | which receives the environment type and metadata. | ||
122 | 692 | """ | 694 | """ |
123 | 695 | if filter_function is None: | ||
124 | 696 | filter_function = lambda env_type, metadata: True | ||
125 | 693 | return [ | 697 | return [ |
126 | 694 | (env_type, metadata['label']) | 698 | (env_type, metadata['label']) |
127 | 695 | for env_type, metadata in env_type_db.items() | 699 | for env_type, metadata in env_type_db.items() |
129 | 696 | if env_type != '__fallback__' | 700 | if (env_type != '__fallback__') and filter_function(env_type, metadata) |
130 | 697 | ] | 701 | ] |
131 | 698 | 702 | ||
132 | 699 | 703 | ||
133 | 700 | 704 | ||
134 | === modified file 'quickstart/tests/cli/test_views.py' | |||
135 | --- quickstart/tests/cli/test_views.py 2014-04-23 12:20:35 +0000 | |||
136 | +++ quickstart/tests/cli/test_views.py 2014-06-16 12:33:53 +0000 | |||
137 | @@ -36,6 +36,18 @@ | |||
138 | 36 | from quickstart.tests.cli import helpers as cli_helpers | 36 | from quickstart.tests.cli import helpers as cli_helpers |
139 | 37 | 37 | ||
140 | 38 | 38 | ||
141 | 39 | def local_envs_supported(value): | ||
142 | 40 | """Simulate local environments support in the current platform. | ||
143 | 41 | |||
144 | 42 | The value argument is a boolean representing whether or not local | ||
145 | 43 | environments are supported. | ||
146 | 44 | Return a context manager that can be used when calling views. | ||
147 | 45 | """ | ||
148 | 46 | return mock.patch( | ||
149 | 47 | 'quickstart.cli.views.platform_support.supports_local', | ||
150 | 48 | mock.Mock(return_value=value)) | ||
151 | 49 | |||
152 | 50 | |||
153 | 39 | class TestShow(unittest.TestCase): | 51 | class TestShow(unittest.TestCase): |
154 | 40 | 52 | ||
155 | 41 | @contextmanager | 53 | @contextmanager |
156 | @@ -125,6 +137,8 @@ | |||
157 | 125 | class TestEnvIndex(EnvViewTestsMixin, unittest.TestCase): | 137 | class TestEnvIndex(EnvViewTestsMixin, unittest.TestCase): |
158 | 126 | 138 | ||
159 | 127 | base_status = ' \N{UPWARDS ARROW LEFTWARDS OF DOWNWARDS ARROW} navigate ' | 139 | base_status = ' \N{UPWARDS ARROW LEFTWARDS OF DOWNWARDS ARROW} navigate ' |
160 | 140 | create_local_caption = ( | ||
161 | 141 | '\N{BULLET} automatically create and bootstrap a local environment') | ||
162 | 128 | 142 | ||
163 | 129 | def test_view_default_return_value_on_exit(self): | 143 | def test_view_default_return_value_on_exit(self): |
164 | 130 | # The view configures the app so that the return value on user exit is | 144 | # The view configures the app so that the return value on user exit is |
165 | @@ -157,7 +171,9 @@ | |||
166 | 157 | # The view displays a list of the environments in env_db, and buttons | 171 | # The view displays a list of the environments in env_db, and buttons |
167 | 158 | # to create new environments. | 172 | # to create new environments. |
168 | 159 | env_db = helpers.make_env_db() | 173 | env_db = helpers.make_env_db() |
170 | 160 | views.env_index(self.app, self.env_type_db, env_db, self.save_callable) | 174 | with local_envs_supported(True): |
171 | 175 | views.env_index( | ||
172 | 176 | self.app, self.env_type_db, env_db, self.save_callable) | ||
173 | 161 | buttons = self.get_widgets_in_contents( | 177 | buttons = self.get_widgets_in_contents( |
174 | 162 | filter_function=self.is_a(ui.MenuButton)) | 178 | filter_function=self.is_a(ui.MenuButton)) |
175 | 163 | # A button is created for each existing environment (see details) and | 179 | # A button is created for each existing environment (see details) and |
176 | @@ -166,6 +182,22 @@ | |||
177 | 166 | expected_buttons_number = len(env_db['environments']) + len(env_types) | 182 | expected_buttons_number = len(env_db['environments']) + len(env_types) |
178 | 167 | self.assertEqual(expected_buttons_number, len(buttons)) | 183 | self.assertEqual(expected_buttons_number, len(buttons)) |
179 | 168 | 184 | ||
180 | 185 | def test_new_local_environment_disabled(self): | ||
181 | 186 | # The option to create a new local environment is not present if they | ||
182 | 187 | # are not supported in the current platform. | ||
183 | 188 | env_db = helpers.make_env_db() | ||
184 | 189 | with local_envs_supported(False): | ||
185 | 190 | views.env_index( | ||
186 | 191 | self.app, self.env_type_db, env_db, self.save_callable) | ||
187 | 192 | buttons = self.get_widgets_in_contents( | ||
188 | 193 | filter_function=self.is_a(ui.MenuButton)) | ||
189 | 194 | captions = map(cli_helpers.get_button_caption, buttons) | ||
190 | 195 | create_local_captions = [ | ||
191 | 196 | caption for caption in captions | ||
192 | 197 | if caption.startswith('\N{BULLET} new local') | ||
193 | 198 | ] | ||
194 | 199 | self.assertEqual([], create_local_captions) | ||
195 | 200 | |||
196 | 169 | @mock.patch('quickstart.cli.views.env_detail') | 201 | @mock.patch('quickstart.cli.views.env_detail') |
197 | 170 | def test_environment_clicked(self, mock_env_detail): | 202 | def test_environment_clicked(self, mock_env_detail): |
198 | 171 | # The environment detail view is called when clicking an environment. | 203 | # The environment detail view is called when clicking an environment. |
199 | @@ -196,7 +228,9 @@ | |||
200 | 196 | # The environment edit view is called when clicking to create a new | 228 | # The environment edit view is called when clicking to create a new |
201 | 197 | # environment. | 229 | # environment. |
202 | 198 | env_db = helpers.make_env_db() | 230 | env_db = helpers.make_env_db() |
204 | 199 | views.env_index(self.app, self.env_type_db, env_db, self.save_callable) | 231 | with local_envs_supported(True): |
205 | 232 | views.env_index( | ||
206 | 233 | self.app, self.env_type_db, env_db, self.save_callable) | ||
207 | 200 | buttons = self.get_widgets_in_contents( | 234 | buttons = self.get_widgets_in_contents( |
208 | 201 | filter_function=self.is_a(ui.MenuButton)) | 235 | filter_function=self.is_a(ui.MenuButton)) |
209 | 202 | env_types = envs.get_supported_env_types(self.env_type_db) | 236 | env_types = envs.get_supported_env_types(self.env_type_db) |
210 | @@ -223,14 +257,16 @@ | |||
211 | 223 | # If that option is clicked, the view quits the application returning | 257 | # If that option is clicked, the view quits the application returning |
212 | 224 | # the newly created env_data. | 258 | # the newly created env_data. |
213 | 225 | env_db = envs.create_empty_env_db() | 259 | env_db = envs.create_empty_env_db() |
215 | 226 | views.env_index(self.app, self.env_type_db, env_db, self.save_callable) | 260 | with local_envs_supported(True): |
216 | 261 | views.env_index( | ||
217 | 262 | self.app, self.env_type_db, env_db, self.save_callable) | ||
218 | 227 | buttons = self.get_widgets_in_contents( | 263 | buttons = self.get_widgets_in_contents( |
219 | 228 | filter_function=self.is_a(ui.MenuButton)) | 264 | filter_function=self.is_a(ui.MenuButton)) |
220 | 229 | # The "create and bootstrap" button is the first one in the contents. | 265 | # The "create and bootstrap" button is the first one in the contents. |
221 | 230 | create_button = buttons[0] | 266 | create_button = buttons[0] |
222 | 231 | self.assertEqual( | 267 | self.assertEqual( |
225 | 232 | '\N{BULLET} automatically create and bootstrap a local ' | 268 | self.create_local_caption, |
226 | 233 | 'environment', cli_helpers.get_button_caption(create_button)) | 269 | cli_helpers.get_button_caption(create_button)) |
227 | 234 | # An AppExit is raised clicking the button. | 270 | # An AppExit is raised clicking the button. |
228 | 235 | with self.assertRaises(ui.AppExit) as context_manager: | 271 | with self.assertRaises(ui.AppExit) as context_manager: |
229 | 236 | cli_helpers.emit(create_button) | 272 | cli_helpers.emit(create_button) |
230 | @@ -239,6 +275,20 @@ | |||
231 | 239 | self.assertIn('local', new_env_db['environments']) | 275 | self.assertIn('local', new_env_db['environments']) |
232 | 240 | self.assertEqual(envs.get_env_data(new_env_db, 'local'), env_data) | 276 | self.assertEqual(envs.get_env_data(new_env_db, 'local'), env_data) |
233 | 241 | 277 | ||
234 | 278 | def test_create_and_bootstrap_local_environment_missing(self): | ||
235 | 279 | # The option to automatically create and bootstrap a new local | ||
236 | 280 | # environment is not displayed if the current platform does not support | ||
237 | 281 | # local environments. | ||
238 | 282 | env_db = envs.create_empty_env_db() | ||
239 | 283 | with local_envs_supported(False): | ||
240 | 284 | views.env_index( | ||
241 | 285 | self.app, self.env_type_db, env_db, self.save_callable) | ||
242 | 286 | buttons = self.get_widgets_in_contents( | ||
243 | 287 | filter_function=self.is_a(ui.MenuButton)) | ||
244 | 288 | # No "create and bootstrap" buttons are present. | ||
245 | 289 | captions = map(cli_helpers.get_button_caption, buttons) | ||
246 | 290 | self.assertNotIn(self.create_local_caption, captions) | ||
247 | 291 | |||
248 | 242 | def test_selected_environment(self): | 292 | def test_selected_environment(self): |
249 | 243 | # The default environment is already selected in the list. | 293 | # The default environment is already selected in the list. |
250 | 244 | env_db = helpers.make_env_db(default='lxc') | 294 | env_db = helpers.make_env_db(default='lxc') |
251 | 245 | 295 | ||
252 | === modified file 'quickstart/tests/models/test_envs.py' | |||
253 | --- quickstart/tests/models/test_envs.py 2014-04-23 12:20:35 +0000 | |||
254 | +++ quickstart/tests/models/test_envs.py 2014-06-16 12:33:53 +0000 | |||
255 | @@ -808,9 +808,12 @@ | |||
256 | 808 | 808 | ||
257 | 809 | class TestGetSupportedEnvTypes(unittest.TestCase): | 809 | class TestGetSupportedEnvTypes(unittest.TestCase): |
258 | 810 | 810 | ||
259 | 811 | def setUp(self): | ||
260 | 812 | # Store the environments database. | ||
261 | 813 | self.env_type_db = envs.get_env_type_db() | ||
262 | 814 | |||
263 | 811 | def test_env_types(self): | 815 | def test_env_types(self): |
264 | 812 | # All the supported env_types but the fallback one are returned. | 816 | # All the supported env_types but the fallback one are returned. |
265 | 813 | env_type_db = envs.get_env_type_db() | ||
266 | 814 | expected_env_types = [ | 817 | expected_env_types = [ |
267 | 815 | ('ec2', 'Amazon EC2'), | 818 | ('ec2', 'Amazon EC2'), |
268 | 816 | ('openstack', 'OpenStack (or HP Public Cloud)'), | 819 | ('openstack', 'OpenStack (or HP Public Cloud)'), |
269 | @@ -818,7 +821,18 @@ | |||
270 | 818 | ('joyent', 'Joyent'), | 821 | ('joyent', 'Joyent'), |
271 | 819 | ('local', 'local (LXC)'), | 822 | ('local', 'local (LXC)'), |
272 | 820 | ] | 823 | ] |
274 | 821 | obtained_env_types = envs.get_supported_env_types(env_type_db) | 824 | obtained_env_types = envs.get_supported_env_types(self.env_type_db) |
275 | 825 | self.assertEqual(expected_env_types, obtained_env_types) | ||
276 | 826 | |||
277 | 827 | def test_filter_function(self): | ||
278 | 828 | # Results can be filtered by providing a filter function. | ||
279 | 829 | expected_env_types = [ | ||
280 | 830 | ('ec2', 'Amazon EC2'), | ||
281 | 831 | ('joyent', 'Joyent'), | ||
282 | 832 | ] | ||
283 | 833 | func = lambda env_type, metadata: env_type in ('ec2', 'joyent') | ||
284 | 834 | obtained_env_types = envs.get_supported_env_types( | ||
285 | 835 | self.env_type_db, filter_function=func) | ||
286 | 822 | self.assertEqual(expected_env_types, obtained_env_types) | 836 | self.assertEqual(expected_env_types, obtained_env_types) |
287 | 823 | 837 | ||
288 | 824 | 838 | ||
289 | 825 | 839 | ||
290 | === modified file 'quickstart/tests/test_manage.py' | |||
291 | --- quickstart/tests/test_manage.py 2014-06-13 13:47:27 +0000 | |||
292 | +++ quickstart/tests/test_manage.py 2014-06-16 12:33:53 +0000 | |||
293 | @@ -572,7 +572,7 @@ | |||
294 | 572 | self.assertTrue(self.parser.error.called) | 572 | self.assertTrue(self.parser.error.called) |
295 | 573 | message = self.parser.error.call_args[0][0] | 573 | message = self.parser.error.call_args[0][0] |
296 | 574 | self.assertEqual( | 574 | self.assertEqual( |
298 | 575 | 'This host platform does not support local environments.', | 575 | 'this host platform does not support local environments', |
299 | 576 | message) | 576 | message) |
300 | 577 | 577 | ||
301 | 578 | def test_interactive_mode(self): | 578 | def test_interactive_mode(self): |
Reviewers: mp+223220_ code.launchpad. net,
Message:
Please take a look.
Description:
Avoid proposing LXC envs as an option on MacOS.
https:/ /code.launchpad .net/~frankban/ juju-quickstart /local- osx/+merge/ 223220
(do not edit description out of merge proposal)
Please review this at https:/ /codereview. appspot. com/105250043/
Affected files (+116, -22 lines): cli/views. py manage. py models/ envs.py tests/cli/ test_views. py tests/models/ test_envs. py tests/test_ manage. py
A [revision details]
M quickstart/
M quickstart/
M quickstart/
M quickstart/
M quickstart/
M quickstart/