OpenStack Dashboard (Horizon)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

tables.py 9.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  2. # not use this file except in compliance with the License. You may obtain
  3. # a copy of the License at
  4. #
  5. # http://www.apache.org/licenses/LICENSE-2.0
  6. #
  7. # Unless required by applicable law or agreed to in writing, software
  8. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  9. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  10. # License for the specific language governing permissions and limitations
  11. # under the License.
  12. from django.template import defaultfilters as filters
  13. from django.urls import reverse
  14. from django.utils.http import urlencode
  15. from django.utils.translation import ugettext_lazy as _
  16. from django.utils.translation import ungettext_lazy
  17. from horizon import forms
  18. from horizon import tables
  19. from openstack_dashboard import api
  20. from openstack_dashboard import policy
  21. from openstack_dashboard.usage import quotas
  22. class RescopeTokenToProject(tables.LinkAction):
  23. name = "rescope"
  24. verbose_name = _("Set as Active Project")
  25. url = "switch_tenants"
  26. def allowed(self, request, project):
  27. # allow rescoping token to any project the user has a role on,
  28. # authorized_tenants, and that they are not currently scoped to
  29. return next((True for proj in request.user.authorized_tenants
  30. if proj.id == project.id and
  31. project.id != request.user.project_id and
  32. project.enabled), False)
  33. def get_link_url(self, project):
  34. # redirects to the switch_tenants url which then will redirect
  35. # back to this page
  36. dash_url = reverse("horizon:identity:projects:index")
  37. base_url = reverse(self.url, args=[project.id])
  38. param = urlencode({"next": dash_url})
  39. return "?".join([base_url, param])
  40. class UpdateMembersLink(tables.LinkAction):
  41. name = "users"
  42. verbose_name = _("Manage Members")
  43. url = "horizon:identity:projects:update"
  44. classes = ("ajax-modal",)
  45. icon = "pencil"
  46. policy_rules = (("identity", "identity:list_users"),
  47. ("identity", "identity:list_roles"))
  48. def get_link_url(self, project):
  49. step = 'update_members'
  50. base_url = reverse(self.url, args=[project.id])
  51. param = urlencode({"step": step})
  52. return "?".join([base_url, param])
  53. def allowed(self, request, project):
  54. if api.keystone.is_multi_domain_enabled():
  55. # domain admin or cloud admin = True
  56. # project admin or member = False
  57. return api.keystone.is_domain_admin(request)
  58. else:
  59. return super(UpdateMembersLink, self).allowed(request, project)
  60. class UpdateGroupsLink(tables.LinkAction):
  61. name = "groups"
  62. verbose_name = _("Modify Groups")
  63. url = "horizon:identity:projects:update"
  64. classes = ("ajax-modal",)
  65. icon = "pencil"
  66. policy_rules = (("identity", "identity:list_groups"),)
  67. def allowed(self, request, project):
  68. if api.keystone.is_multi_domain_enabled():
  69. # domain admin or cloud admin = True
  70. # project admin or member = False
  71. return api.keystone.is_domain_admin(request)
  72. else:
  73. return super(UpdateGroupsLink, self).allowed(request, project)
  74. def get_link_url(self, project):
  75. step = 'update_group_members'
  76. base_url = reverse(self.url, args=[project.id])
  77. param = urlencode({"step": step})
  78. return "?".join([base_url, param])
  79. class UsageLink(tables.LinkAction):
  80. name = "usage"
  81. verbose_name = _("View Usage")
  82. url = "horizon:identity:projects:usage"
  83. icon = "stats"
  84. policy_rules = (("compute", "os_compute_api:os-simple-tenant-usage:show"),)
  85. def allowed(self, request, project):
  86. return (request.user.is_superuser and
  87. api.base.is_service_enabled(request, 'compute'))
  88. class CreateProject(tables.LinkAction):
  89. name = "create"
  90. verbose_name = _("Create Project")
  91. url = "horizon:identity:projects:create"
  92. classes = ("ajax-modal",)
  93. icon = "plus"
  94. policy_rules = (('identity', 'identity:create_project'),)
  95. def allowed(self, request, project):
  96. if api.keystone.is_multi_domain_enabled():
  97. # domain admin or cloud admin = True
  98. # project admin or member = False
  99. return api.keystone.is_domain_admin(request)
  100. else:
  101. return api.keystone.keystone_can_edit_project()
  102. class UpdateProject(policy.PolicyTargetMixin, tables.LinkAction):
  103. name = "update"
  104. verbose_name = _("Edit Project")
  105. url = "horizon:identity:projects:update"
  106. classes = ("ajax-modal",)
  107. icon = "pencil"
  108. policy_rules = (('identity', 'identity:update_project'),)
  109. policy_target_attrs = (("target.project.domain_id", "domain_id"),)
  110. def allowed(self, request, project):
  111. if api.keystone.is_multi_domain_enabled():
  112. # domain admin or cloud admin = True
  113. # project admin or member = False
  114. return api.keystone.is_domain_admin(request)
  115. else:
  116. return api.keystone.keystone_can_edit_project()
  117. class ModifyQuotas(tables.LinkAction):
  118. name = "quotas"
  119. verbose_name = _("Modify Quotas")
  120. url = "horizon:identity:projects:update_quotas"
  121. classes = ("ajax-modal",)
  122. icon = "pencil"
  123. policy_rules = (('compute', "os_compute_api:os-quota-sets:update"),)
  124. def allowed(self, request, datum):
  125. if api.keystone.VERSIONS.active < 3:
  126. return True
  127. else:
  128. return (api.keystone.is_cloud_admin(request) and
  129. quotas.enabled_quotas(request))
  130. def get_link_url(self, project):
  131. step = 'update_quotas'
  132. base_url = reverse(self.url, args=[project.id])
  133. param = urlencode({"step": step})
  134. return "?".join([base_url, param])
  135. class DeleteTenantsAction(policy.PolicyTargetMixin, tables.DeleteAction):
  136. @staticmethod
  137. def action_present(count):
  138. return ungettext_lazy(
  139. u"Delete Project",
  140. u"Delete Projects",
  141. count
  142. )
  143. @staticmethod
  144. def action_past(count):
  145. return ungettext_lazy(
  146. u"Deleted Project",
  147. u"Deleted Projects",
  148. count
  149. )
  150. policy_rules = (("identity", "identity:delete_project"),)
  151. policy_target_attrs = (("target.project.domain_id", "domain_id"),)
  152. def allowed(self, request, project):
  153. if api.keystone.is_multi_domain_enabled() \
  154. and not api.keystone.is_domain_admin(request):
  155. return False
  156. return api.keystone.keystone_can_edit_project()
  157. def delete(self, request, obj_id):
  158. api.keystone.tenant_delete(request, obj_id)
  159. def handle(self, table, request, obj_ids):
  160. response = \
  161. super(DeleteTenantsAction, self).handle(table, request, obj_ids)
  162. return response
  163. class TenantFilterAction(tables.FilterAction):
  164. if api.keystone.VERSIONS.active < 3:
  165. filter_type = "query"
  166. else:
  167. filter_type = "server"
  168. filter_choices = (('name', _("Project Name ="), True),
  169. ('id', _("Project ID ="), True),
  170. ('enabled', _("Enabled ="), True, _('e.g. Yes/No')))
  171. class UpdateRow(tables.Row):
  172. ajax = True
  173. def get_data(self, request, project_id):
  174. project_info = api.keystone.tenant_get(request, project_id,
  175. admin=True)
  176. return project_info
  177. class TenantsTable(tables.DataTable):
  178. name = tables.WrappingColumn('name', verbose_name=_('Name'),
  179. link=("horizon:identity:projects:detail"),
  180. form_field=forms.CharField(max_length=64))
  181. description = tables.Column(lambda obj: getattr(obj, 'description', None),
  182. verbose_name=_('Description'),
  183. form_field=forms.CharField(
  184. widget=forms.Textarea(attrs={'rows': 4}),
  185. required=False))
  186. id = tables.Column('id', verbose_name=_('Project ID'))
  187. if api.keystone.VERSIONS.active >= 3:
  188. domain_name = tables.Column(
  189. 'domain_name', verbose_name=_('Domain Name'))
  190. enabled = tables.Column('enabled', verbose_name=_('Enabled'), status=True,
  191. filters=(filters.yesno, filters.capfirst),
  192. form_field=forms.BooleanField(
  193. label=_('Enabled'),
  194. required=False))
  195. def get_project_detail_link(self, project):
  196. # this method is an ugly monkey patch, needed because
  197. # the column link method does not provide access to the request
  198. if policy.check((("identity", "identity:get_project"),),
  199. self.request, target={"project": project}):
  200. return reverse("horizon:identity:projects:detail",
  201. args=(project.id,))
  202. return None
  203. def __init__(self, request, data=None, needs_form_wrapper=None, **kwargs):
  204. super(TenantsTable,
  205. self).__init__(request, data=data,
  206. needs_form_wrapper=needs_form_wrapper,
  207. **kwargs)
  208. # see the comment above about ugly monkey patches
  209. self.columns['name'].get_link_url = self.get_project_detail_link
  210. class Meta(object):
  211. name = "tenants"
  212. verbose_name = _("Projects")
  213. row_class = UpdateRow
  214. row_actions = (UpdateMembersLink, UpdateGroupsLink, UpdateProject,
  215. UsageLink, ModifyQuotas, DeleteTenantsAction,
  216. RescopeTokenToProject)
  217. table_actions = (TenantFilterAction, CreateProject,
  218. DeleteTenantsAction)
  219. pagination_param = "tenant_marker"