stackamole-xblock 9.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. fake/common/djangoapps/student/__init__.py +0 -0
  2. fake/common/djangoapps/student/models.py +15 -0
  3. fake/course/__init__.py +0 -0
  4. requirements/base.txt +25 -0
  5. requirements/flake8.txt +1 -0
  6. requirements/setup.txt +3 -0
  7. requirements/test.txt +19 -0
  8. samples/hot/sample-template.yaml +427 -0
  9. src/pip-delete-this-directory.txt +5 -0
  10. stackamole/__init__.py +8 -0
  11. stackamole/admin.py +130 -0
  12. stackamole/common.py +423 -0
  13. stackamole/jobs.py +211 -0
  14. stackamole/locale/config.yaml +14 -0
  15. stackamole/locale/de_DE/LC_MESSAGES/django.po +109 -0
  16. stackamole/locale/de_DE/LC_MESSAGES/djangojs.po +68 -0
  17. stackamole/locale/el/LC_MESSAGES/django.po +113 -0
  18. stackamole/locale/el/LC_MESSAGES/djangojs.po +77 -0
  19. stackamole/locale/en/LC_MESSAGES/django.po +107 -0
  20. stackamole/locale/en/LC_MESSAGES/djangojs.po +68 -0
  21. stackamole/locale/es_419/LC_MESSAGES/django.po +109 -0
  22. stackamole/locale/es_419/LC_MESSAGES/djangojs.po +68 -0
  23. stackamole/locale/et_EE/LC_MESSAGES/django.po +110 -0
  24. stackamole/locale/et_EE/LC_MESSAGES/djangojs.po +68 -0
  25. stackamole/locale/hi/LC_MESSAGES/django.po +111 -0
  26. stackamole/locale/hi/LC_MESSAGES/djangojs.po +72 -0
  27. stackamole/management/__init__.py +0 -0
  28. stackamole/management/commands/__init__.py +0 -0
  29. stackamole/management/commands/reaper.py +21 -0
  30. stackamole/management/commands/suspender.py +20 -0
  31. stackamole/migrations/0001_initial.py +43 -0
  32. stackamole/migrations/0002_stacklog.py +47 -0
  33. stackamole/migrations/0003_blanks.py +84 -0
  34. stackamole/migrations/0004_auto_20190715_1053.py +24 -0
  35. stackamole/migrations/0005_auto_20190811_1555.py +56 -0
  36. stackamole/migrations/0006_auto_20200107_1332.py +44 -0
  37. stackamole/migrations/0007_add_delete_by_and_delete_age.py +34 -0
  38. stackamole/migrations/0008_add_database_defaults_for_stack_key_and_password.py +23 -0
  39. stackamole/migrations/0009_add_null_true_for_key_and_password.py +23 -0
  40. stackamole/migrations/0010_add_user_foreign_key.py +52 -0
  41. stackamole/migrations/0011_allow_null_for_learner.py +29 -0
  42. stackamole/migrations/0012_add_suspend_by.py +21 -0
  43. stackamole/migrations/0013_migrate_app_label.py +71 -0
  44. stackamole/migrations/__init__.py +0 -0
  45. stackamole/models.py +106 -0
  46. stackamole/openstack.py +132 -0
  47. stackamole/provider.py +437 -0
  48. stackamole/public/css/main.css +203 -0
  49. stackamole/public/js/client.js +269 -0
  50. stackamole/public/js/guacamole-common-js/0.9.13-incubating-all.min.js +1 -0
  51. stackamole/public/js/guacamole-common-js/1.4.0-all.min.js +155 -0
  52. stackamole/public/js/guacamole-common-js/1.5.2-all.min.js +161 -0
  53. stackamole/public/js/guacamole-common-js/1.5.4-all.min.js +161 -0
  54. stackamole/public/js/guacamole-common-js/1.5.5-all.min.js +161 -0
  55. stackamole/public/js/main.js +492 -0
  56. stackamole/public/js/plugins.js +95 -0
  57. stackamole/stackamole.py +1226 -0
  58. stackamole/static/html/lab.html +88 -0
  59. stackamole/static/html/main.html +93 -0
  60. stackamole/tasks.py +947 -0
  61. stackamole_guacamole_client/__init__.py +0 -0
  62. stackamole_guacamole_client/asgi.py +26 -0
  63. stackamole_guacamole_client/consumers.py +95 -0
  64. stackamole_guacamole_client/settings.py +87 -0
  65. stackamole_xblock-9.4.0.dist-info/METADATA +647 -0
  66. stackamole_xblock-9.4.0.dist-info/RECORD +89 -0
  67. stackamole_xblock-9.4.0.dist-info/WHEEL +5 -0
  68. stackamole_xblock-9.4.0.dist-info/entry_points.txt +3 -0
  69. stackamole_xblock-9.4.0.dist-info/licenses/LICENSE +661 -0
  70. stackamole_xblock-9.4.0.dist-info/top_level.txt +7 -0
  71. tests/__init__.py +0 -0
  72. tests/resources/course/stackamole/fake_lab_1.xml +9 -0
  73. tests/resources/course/stackamole/fake_lab_2.xml +9 -0
  74. tests/resources/course/stackamole/fake_lab_hook_events_1.xml +4 -0
  75. tests/resources/course/stackamole/fake_lab_hook_events_2.xml +3 -0
  76. tests/resources/course/stackamole/fake_lab_no_port_name.xml +3 -0
  77. tests/resources/course/stackamole/fake_lab_no_port_number.xml +3 -0
  78. tests/resources/course/stackamole/fake_lab_no_provider_capacity.xml +5 -0
  79. tests/resources/course/stackamole/fake_lab_no_provider_name.xml +3 -0
  80. tests/unit/__init__.py +0 -0
  81. tests/unit/test_admin.py +170 -0
  82. tests/unit/test_commands.py +31 -0
  83. tests/unit/test_common.py +307 -0
  84. tests/unit/test_jobs.py +649 -0
  85. tests/unit/test_models.py +72 -0
  86. tests/unit/test_openstack.py +149 -0
  87. tests/unit/test_provider.py +847 -0
  88. tests/unit/test_stackamole.py +1664 -0
  89. tests/unit/test_tasks.py +2324 -0
File without changes
@@ -0,0 +1,15 @@
1
+ from django.contrib.auth.models import User
2
+ from django.db import models
3
+
4
+
5
+ class AnonymousUserId(models.Model):
6
+ """
7
+ Pared down from:
8
+
9
+ edx-platform/common/djangoapps/student/models.py:AnonymousUserId
10
+
11
+ This is so we don't have to install all of edx-platform just to test it.
12
+
13
+ """
14
+ user = models.ForeignKey(User, db_index=True, on_delete=models.CASCADE)
15
+ anonymous_user_id = models.CharField(unique=True, max_length=32)
File without changes
requirements/base.txt ADDED
@@ -0,0 +1,25 @@
1
+ apscheduler<4
2
+ cliff<=4.7
3
+ dogpile.cache<=1.3
4
+ keystoneauth1<=5.8
5
+ openstacksdk<=4.1
6
+ osc-lib<=3.1
7
+ os-client-config<=2.1
8
+ oslo.config<=9.7.1
9
+ oslo.serialization<=5.5
10
+ oslo.utils<=7.4
11
+ oslo.i18n<6.6
12
+ paramiko>=4.0,<4.1
13
+ pymongo<=4.4.0 # keep in sync with edx-platform
14
+ python-heatclient<=4.0
15
+ python-keystoneclient<=5.5
16
+ python-novaclient<=18.7
17
+ tenacity<=9.0
18
+
19
+ # for stackamole_guacamole_client
20
+ django<=5.2.13
21
+ channels>=4.3,<4.4
22
+ daphne<=4.0.0
23
+ mysqlclient<=2.2.8 # keep in sync with edx-platform
24
+ jsonfield>=3.2.0,<4 # keep in sync with edx-platform
25
+ pyguacamole>=0.11
@@ -0,0 +1 @@
1
+ flake8
requirements/setup.txt ADDED
@@ -0,0 +1,3 @@
1
+ -r base.txt
2
+ setuptools-scm
3
+ bumpversion
requirements/test.txt ADDED
@@ -0,0 +1,19 @@
1
+ -r base.txt
2
+
3
+ # These package versions must be kept in sync with edx-platform as much as possible.
4
+ xblock-sdk<0.9.0;python_version<"3.9"
5
+ xblock-sdk;python_version>="3.9"
6
+ six==1.16.0
7
+ lazy<=1.5
8
+ openedx-django-pyfs>=3.8.0
9
+ sqlparse>=0.4.1,<0.5
10
+ web-fragments<=2.0.0
11
+ edx-opaque-keys<=3.0.0
12
+
13
+ # dependencies of supported nested XBlocks
14
+ path.py>=12.4
15
+
16
+ # Tooling
17
+ ddt
18
+ nose
19
+ coverage
@@ -0,0 +1,427 @@
1
+ heat_template_version: 2014-10-16
2
+
3
+ description: |
4
+ Sample lab environment.
5
+ - Consists of one deployment host (deploy) and three regular hosts
6
+ (daisy, eric, frank).
7
+ - Provides two separate networks.
8
+ - Installs an apt proxy on the deploy node for quicker updates
9
+ - Waits for cloud-init provisioning to finish before reports CREATE_COMPLETE
10
+ - Creates cron job to sync time with hardware clock, to work around stack
11
+ suspension desync
12
+ - Starts screen on connection
13
+
14
+ parameters:
15
+ flavor:
16
+ type: string
17
+ description: Flavor to use for servers
18
+ default: m1.medium.e
19
+ public_net_id:
20
+ type: string
21
+ description: Public network ID
22
+ default: 62954df1-05bb-42e5-9960-ca921cccaeeb
23
+ image:
24
+ type: string
25
+ description: Image ID or name
26
+ default: ubuntu-14.04.3-server-cloudimg
27
+ ubuntu_mirror:
28
+ type: string
29
+ description: Ubuntu package archive mirror
30
+ default: mirrors.se.eu.kernel.org
31
+ timeout:
32
+ type: number
33
+ description: Timeout for stack creation to finish
34
+ default: 900
35
+
36
+ resources:
37
+ all_done:
38
+ type: OS::Heat::WaitCondition
39
+ properties:
40
+ handle: {get_resource: all_done_handle}
41
+ count: 4
42
+ timeout: {get_param: timeout}
43
+
44
+ all_done_handle:
45
+ type: OS::Heat::WaitConditionHandle
46
+
47
+ proxy_ready:
48
+ type: OS::Heat::WaitCondition
49
+ properties:
50
+ handle: {get_resource: proxy_ready_handle}
51
+ count: 1
52
+ timeout: {get_param: timeout}
53
+
54
+ proxy_ready_handle:
55
+ type: OS::Heat::WaitConditionHandle
56
+
57
+ deploy_config:
58
+ type: OS::Heat::CloudConfig
59
+ properties:
60
+ cloud_config:
61
+ ### bootcmd: runs arbitrary commands early in the boot sequence
62
+ bootcmd:
63
+ - apt-get update
64
+ - apt-get -y install --no-install-recommends apt-cacher-ng
65
+ - { get_attr: ['proxy_ready_handle', 'curl_cli'] }
66
+ ## users: defines users, sets passwords and SSH authorized keys, and
67
+ ### enables sudo privileges.
68
+ users:
69
+ - default
70
+ - name: training
71
+ gecos: Training User
72
+ groups: users,adm
73
+ ssh-authorized-keys:
74
+ - { get_attr: [ training_key, public_key ] }
75
+ lock-passwd: false
76
+ shell: /bin/bash
77
+ sudo: ALL=(ALL) NOPASSWD:ALL
78
+ ### mounts: enables and disables mount points
79
+ #mounts:
80
+ # - [ 'vdb', null ]
81
+ ### write_files: writes arbitrary files
82
+ write_files:
83
+ - path: /etc/cron.d/hwclock
84
+ permissions: '0644'
85
+ content: |
86
+ # /etc/cron.d/hwclock: periodically sync the system time to the hardware clock
87
+ SHELL=/bin/sh
88
+ PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
89
+ */5 * * * * root hwclock -s
90
+ - path: /etc/hosts
91
+ permissions: '0644'
92
+ content: |
93
+ 127.0.0.1 localhost
94
+ 192.168.122.100 deploy.example.com deploy
95
+ 192.168.122.114 daisy.example.com daisy
96
+ 192.168.122.115 eric.example.com eric
97
+ 192.168.122.116 frank.example.com frank
98
+ - path: /etc/network/interfaces.d/eth1.cfg
99
+ permissions: '0644'
100
+ content: |
101
+ auto eth1
102
+ iface eth1 inet dhcp
103
+ - path: /etc/ntp.conf
104
+ permissions: '0644'
105
+ content: |
106
+ driftfile /var/lib/ntp/ntp.drift
107
+ statistics loopstats peerstats clockstats
108
+ filegen loopstats file loopstats type day enable
109
+ filegen peerstats file peerstats type day enable
110
+ filegen clockstats file clockstats type day enable
111
+ server 0.ubuntu.pool.ntp.org iburst
112
+ server 1.ubuntu.pool.ntp.org iburst
113
+ server 2.ubuntu.pool.ntp.org iburst
114
+ server 3.ubuntu.pool.ntp.org iburst
115
+ server ntp.ubuntu.com
116
+ restrict -4 default kod notrap nomodify nopeer noquery
117
+ restrict -6 default kod notrap nomodify nopeer noquery
118
+ restrict 127.0.0.1
119
+ restrict ::1
120
+ - path: /home/training/.ssh/id_rsa
121
+ permissions: '0600'
122
+ content: { get_attr: [ training_key, private_key ] }
123
+ - path: /home/training/.profile
124
+ permissions: '0644'
125
+ content: |
126
+ exec /usr/bin/screen -xRR
127
+ apt_proxy: "http://localhost:3142"
128
+ apt_mirror:
129
+ str_replace:
130
+ template: http://%mirror/ubuntu
131
+ params:
132
+ "%mirror": { get_param: ubuntu_mirror }
133
+ ### package_update: refresh package on first boot
134
+ package_update: false
135
+ ### package_upgrade: runs package update on first boot
136
+ package_upgrade: true
137
+ ### packages: installs packages
138
+ packages:
139
+ - attr
140
+ - ntp
141
+ - screen
142
+ runcmd:
143
+ - chown -R training:training /home/training
144
+ - chsh -s /bin/bash training
145
+ - { get_attr: ['all_done_handle', 'curl_cli'] }
146
+
147
+ host_config:
148
+ type: OS::Heat::CloudConfig
149
+ properties:
150
+ cloud_config:
151
+ ## users: defines users, sets passwords and SSH authorized keys, and
152
+ ### enables sudo privileges.
153
+ users:
154
+ - default
155
+ - name: training
156
+ gecos: Training User
157
+ groups: users,adm
158
+ ssh-authorized-keys:
159
+ - { get_attr: [ training_key, public_key ] }
160
+ lock-passwd: false
161
+ shell: /bin/bash
162
+ sudo: ALL=(ALL) NOPASSWD:ALL
163
+ ### bootcmd: runs arbitrary commands early in the boot sequence
164
+ # bootcmd:
165
+ ### mounts: enables and disables mount points
166
+ #mounts:
167
+ # - [ 'vdb', null ]
168
+ ### write_files: writes arbitrary files
169
+ write_files:
170
+ - path: /etc/cron.d/ntpdate
171
+ permissions: '0644'
172
+ content: |
173
+ # /etc/cron.d/ntpdate: periodically sync the system time to the NTP server
174
+ SHELL=/bin/sh
175
+ PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
176
+ */5 * * * * root ntpdate deploy
177
+ - path: /etc/hosts
178
+ permissions: '0644'
179
+ content: |
180
+ 127.0.0.1 localhost
181
+ 192.168.122.100 deploy.example.com deploy
182
+ 192.168.122.114 daisy.example.com daisy
183
+ 192.168.122.115 eric.example.com eric
184
+ 192.168.122.116 frank.example.com frank
185
+ - path: /etc/network/interfaces.d/eth1.cfg
186
+ permissions: '0644'
187
+ content: |
188
+ auto eth1
189
+ iface eth1 inet dhcp
190
+ - path: /home/training/.ssh/id_rsa
191
+ permissions: '0600'
192
+ content: { get_attr: [ training_key, private_key ] }
193
+ apt_proxy: "http://deploy:3142"
194
+ apt_mirror:
195
+ str_replace:
196
+ template: http://%mirror/ubuntu
197
+ params:
198
+ "%mirror": { get_param: ubuntu_mirror }
199
+ ### package_update: refresh package on first boot
200
+ package_update: true
201
+ ### package_upgrade: runs package update on first boot
202
+ package_upgrade: true
203
+ ### packages: installs packages
204
+ packages:
205
+ - screen
206
+ - attr
207
+ runcmd:
208
+ - chown -R training:training /home/training
209
+ - chsh -s /bin/bash training
210
+ - { get_attr: ['all_done_handle', 'curl_cli'] }
211
+
212
+ server_security_group:
213
+ type: OS::Neutron::SecurityGroup
214
+ properties:
215
+ description: Neutron security group rules
216
+ name: server_security_group
217
+ rules:
218
+ - remote_mode: 'remote_group_id'
219
+ remote_group_id:
220
+ direction: ingress
221
+ - remote_ip_prefix: 0.0.0.0/0
222
+ protocol: tcp
223
+ direction: ingress
224
+ port_range_min: 22
225
+ port_range_max: 22
226
+ - remote_ip_prefix: 0.0.0.0/0
227
+ protocol: icmp
228
+
229
+ training_key:
230
+ type: OS::Nova::KeyPair
231
+ properties:
232
+ name: { get_param: 'OS::stack_name' }
233
+ save_private_key: true
234
+
235
+ management_net:
236
+ type: OS::Neutron::Net
237
+ properties:
238
+ name: management-net
239
+
240
+ management_sub_net:
241
+ type: OS::Neutron::Subnet
242
+ properties:
243
+ name: management-sub-net
244
+ network_id: { get_resource: management_net }
245
+ cidr: 192.168.122.0/24
246
+ gateway_ip: 192.168.122.1
247
+ dns_nameservers:
248
+ - 8.8.8.8
249
+ - 8.8.4.4
250
+ enable_dhcp: true
251
+ allocation_pools:
252
+ - start: "192.168.122.2"
253
+ end: "192.168.122.50"
254
+
255
+ router:
256
+ type: OS::Neutron::Router
257
+
258
+ router_gateway:
259
+ type: OS::Neutron::RouterGateway
260
+ properties:
261
+ router_id: { get_resource: router }
262
+ network_id: { get_param: public_net_id }
263
+
264
+ router_interface:
265
+ type: OS::Neutron::RouterInterface
266
+ properties:
267
+ router_id: { get_resource: router }
268
+ subnet_id: { get_resource: management_sub_net }
269
+
270
+ data_net:
271
+ type: OS::Neutron::Net
272
+ properties:
273
+ name: data-net
274
+
275
+ data_sub_net:
276
+ type: OS::Neutron::Subnet
277
+ properties:
278
+ name: data-sub-net
279
+ network_id: { get_resource: data_net }
280
+ cidr: 192.168.133.0/24
281
+ gateway_ip: ""
282
+ dns_nameservers:
283
+ - 8.8.8.8
284
+ - 8.8.4.4
285
+ enable_dhcp: true
286
+ allocation_pools:
287
+ - start: "192.168.133.2"
288
+ end: "192.168.133.50"
289
+
290
+ deploy:
291
+ type: OS::Nova::Server
292
+ properties:
293
+ name: deploy
294
+ image: { get_param: image }
295
+ flavor: { get_param: flavor }
296
+ user_data: { get_resource: deploy_config }
297
+ user_data_format: RAW
298
+ networks:
299
+ - port: { get_resource: deploy_management_port }
300
+ - port: { get_resource: deploy_data_port }
301
+
302
+ deploy_management_port:
303
+ type: OS::Neutron::Port
304
+ properties:
305
+ network_id: { get_resource: management_net }
306
+ fixed_ips:
307
+ - ip_address: 192.168.122.100
308
+ security_groups:
309
+ - { get_resource: server_security_group }
310
+
311
+ deploy_data_port:
312
+ type: OS::Neutron::Port
313
+ properties:
314
+ network_id: { get_resource: data_net }
315
+ fixed_ips:
316
+ - ip_address: 192.168.133.100
317
+
318
+ deploy_floating_ip:
319
+ type: OS::Neutron::FloatingIP
320
+ properties:
321
+ floating_network_id: { get_param: public_net_id }
322
+ port_id: { get_resource: deploy_management_port }
323
+ fixed_ip_address: 192.168.122.100
324
+
325
+ daisy:
326
+ type: OS::Nova::Server
327
+ properties:
328
+ name: daisy
329
+ image: { get_param: image }
330
+ flavor: { get_param: flavor }
331
+ user_data: { get_resource: host_config }
332
+ user_data_format: RAW
333
+ metadata:
334
+ wait_condition_data: { get_attr: [ proxy_ready, data ] }
335
+ networks:
336
+ - port: { get_resource: daisy_management_port }
337
+ - port: { get_resource: daisy_data_port }
338
+
339
+ daisy_management_port:
340
+ type: OS::Neutron::Port
341
+ properties:
342
+ network_id: { get_resource: management_net }
343
+ fixed_ips:
344
+ - ip_address: 192.168.122.114
345
+ security_groups:
346
+ - { get_resource: server_security_group }
347
+
348
+ daisy_data_port:
349
+ type: OS::Neutron::Port
350
+ properties:
351
+ network_id: { get_resource: data_net }
352
+ fixed_ips:
353
+ - ip_address: 192.168.133.114
354
+
355
+ eric:
356
+ type: OS::Nova::Server
357
+ properties:
358
+ name: eric
359
+ image: { get_param: image }
360
+ flavor: { get_param: flavor }
361
+ user_data: { get_resource: host_config }
362
+ user_data_format: RAW
363
+ metadata:
364
+ wait_condition_data: { get_attr: [ proxy_ready, data ] }
365
+ networks:
366
+ - port: { get_resource: eric_management_port }
367
+ - port: { get_resource: eric_data_port }
368
+
369
+ eric_management_port:
370
+ type: OS::Neutron::Port
371
+ properties:
372
+ network_id: { get_resource: management_net }
373
+ fixed_ips:
374
+ - ip_address: 192.168.122.115
375
+ security_groups:
376
+ - { get_resource: server_security_group }
377
+
378
+ eric_data_port:
379
+ type: OS::Neutron::Port
380
+ properties:
381
+ network_id: { get_resource: data_net }
382
+ fixed_ips:
383
+ - ip_address: 192.168.133.115
384
+
385
+ frank:
386
+ type: OS::Nova::Server
387
+ properties:
388
+ name: frank
389
+ image: { get_param: image }
390
+ flavor: { get_param: flavor }
391
+ user_data: { get_resource: host_config }
392
+ user_data_format: RAW
393
+ metadata:
394
+ wait_condition_data: { get_attr: [ proxy_ready, data ] }
395
+ networks:
396
+ - port: { get_resource: frank_management_port }
397
+ - port: { get_resource: frank_data_port }
398
+
399
+ frank_management_port:
400
+ type: OS::Neutron::Port
401
+ properties:
402
+ network_id: { get_resource: management_net }
403
+ fixed_ips:
404
+ - ip_address: 192.168.122.116
405
+ security_groups:
406
+ - { get_resource: server_security_group }
407
+
408
+ frank_data_port:
409
+ type: OS::Neutron::Port
410
+ properties:
411
+ network_id: { get_resource: data_net }
412
+ fixed_ips:
413
+ - ip_address: 192.168.133.116
414
+
415
+ outputs:
416
+ public_ip:
417
+ description: Floating IP address of deploy in public network
418
+ value: { get_attr: [ deploy_floating_ip, floating_ip_address ] }
419
+ private_key:
420
+ description: Training private key
421
+ value: { get_attr: [ training_key, private_key ] }
422
+ reboot_on_resume:
423
+ description: Servers to reboot upon resume
424
+ value:
425
+ - { get_resource: daisy }
426
+ - { get_resource: eric }
427
+ - { get_resource: frank }
@@ -0,0 +1,5 @@
1
+ This file is placed here by pip to indicate the source was put
2
+ here by pip.
3
+
4
+ Once this package is successfully installed this source code will be
5
+ deleted (unless you remove this file).
stackamole/__init__.py ADDED
@@ -0,0 +1,8 @@
1
+ from importlib import metadata
2
+ # __version__ attribute as suggested by (deferred) PEP 396:
3
+ # https://www.python.org/dev/peps/pep-0396/
4
+ #
5
+ # Single-source package definition as suggested (among several
6
+ # options) by:
7
+ # https://packaging.python.org/guides/single-sourcing-package-version/
8
+ __version__ = metadata.version('stackamole-xblock')
stackamole/admin.py ADDED
@@ -0,0 +1,130 @@
1
+ """
2
+ Admin registration for stackamole Xblock models
3
+ """
4
+ from django import forms
5
+ from django.contrib import admin
6
+
7
+ from .common import (
8
+ DELETE_COMPLETE,
9
+ SUSPEND_COMPLETE,
10
+ VALID_STATES,
11
+ get_xblock_settings
12
+ )
13
+ from .models import Stack, StackLog
14
+
15
+
16
+ @admin.action(description="Mark selected stacks as SUSPEND_COMPLETE")
17
+ def mark_suspended(modeladmin, request, queryset):
18
+ """
19
+ Mark selected stacks as successfully suspended.
20
+
21
+ """
22
+ queryset.update(status=SUSPEND_COMPLETE)
23
+
24
+
25
+ @admin.action(description="Mark selected stacks as DELETE_COMPLETE")
26
+ def mark_deleted(modeladmin, request, queryset):
27
+ """
28
+ Mark selected stacks as deleted, and reset the provider.
29
+
30
+ """
31
+ queryset.update(status=DELETE_COMPLETE, provider="")
32
+
33
+
34
+ @admin.action(description="Clear stacklog for selected stacks")
35
+ def clear_stacklog(self, request, queryset):
36
+ logs = StackLog.objects.filter(stack_id__in=queryset.values('id'))
37
+ logs.delete()
38
+ self.message_user(
39
+ request,
40
+ f'Stacklog cleared for {queryset.count()} stacks!')
41
+
42
+
43
+ def student_email(stack):
44
+ """
45
+ Display the learner email for admin page.
46
+
47
+ """
48
+ if stack.learner:
49
+ return stack.learner.email
50
+ else:
51
+ return ""
52
+
53
+
54
+ student_email.short_description = "Email"
55
+
56
+
57
+ class StackAdminForm(forms.ModelForm):
58
+ """
59
+ Custom form for constructing `state` and `provider` choices dynamically
60
+ without the need to modify the model.
61
+
62
+ """
63
+ class Meta:
64
+ model = Stack
65
+ exclude = ['id']
66
+
67
+ provider = forms.ChoiceField(required=False)
68
+ status = forms.ChoiceField()
69
+ delete_by = forms.DateTimeField(required=False)
70
+
71
+ def __init__(self, *args, **kwargs):
72
+ super(StackAdminForm, self).__init__(*args, **kwargs)
73
+
74
+ # Gather provider choices. If the record has `providers` set
75
+ # (signifying the course author limited which providers this course can
76
+ # use), only let the user pick from those. Otherwise, allow any
77
+ # providers configured in the system.
78
+ if (self.instance and
79
+ len(self.instance.providers)):
80
+ provider_choices = [""] + [p["name"] for p in
81
+ self.instance.providers]
82
+ else:
83
+ settings = get_xblock_settings()
84
+ providers = settings.get("providers", {})
85
+ provider_choices = [""] + list(providers)
86
+
87
+ self.fields["provider"].choices = [(i, i) for i in provider_choices]
88
+ self.fields["status"].choices = [("", "")] + \
89
+ [(i, i) for i in VALID_STATES]
90
+
91
+
92
+ class StackAdmin(admin.ModelAdmin):
93
+ """
94
+ Custom stack admin class. Uses a custom form, enables searching and
95
+ filtering, and totally excludes fields used by the XBlock solely to
96
+ communicate with tasks or jobs. Of the remaining fields, only `status` and
97
+ `provider` should be editable.
98
+
99
+ Adding stacks manually is forbidden, but deleting records is allowed. A
100
+ custom action allows marking stacks as `DELETE_COMPLETE` in bulk.
101
+
102
+ """
103
+ form = StackAdminForm
104
+ list_display = ("name", "course_id", student_email, "status", "provider",
105
+ "launch_timestamp",)
106
+ list_filter = ("course_id", "status", "provider",)
107
+ list_editable = ("status", "provider")
108
+ list_select_related = True
109
+ actions = [mark_suspended, mark_deleted, clear_stacklog]
110
+ search_fields = (
111
+ "name", "course_id", "learner__email", "status", "provider")
112
+ readonly_fields = ("name", student_email, "course_id", "run", "protocol",
113
+ "port", "ip", "launch_timestamp", "suspend_timestamp",
114
+ "suspend_by", "created_on", "error_msg", "delete_age",)
115
+ exclude = ("student_id", "providers", "hook_script", "hook_events",
116
+ "launch_task_id", "user", "key", "password", "learner")
117
+ ordering = ("-launch_timestamp",)
118
+
119
+ def get_changelist_form(self, request, **kwargs):
120
+ return StackAdminForm
121
+
122
+ def has_add_permission(self, request, obj=None):
123
+ """
124
+ Adding stacks manually is not supported.
125
+
126
+ """
127
+ return False
128
+
129
+
130
+ admin.site.register(Stack, StackAdmin)