X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=planetstack%2Fcore%2Fadmin.py;h=9f8fbb0f7acd1f42ac1d3b1b1047efc753d106fd;hb=c3439c3b5bf57e550115a6c2bf5be60d841585b4;hp=196324e4386cacafe473d66732ba58d1f9a06bbc;hpb=f875eba8d36c7d03964a933e44d1cfe4d0752c4d;p=plstackapi.git diff --git a/planetstack/core/admin.py b/planetstack/core/admin.py index 196324e..9f8fbb0 100644 --- a/planetstack/core/admin.py +++ b/planetstack/core/admin.py @@ -48,12 +48,15 @@ class ReadOnlyAwareAdmin(admin.ModelAdmin): # save the original readonly fields self.readonly_save = self.readonly_fields self.inlines_save = self.inlines - self.readonly_fields=self.user_readonly_fields - self.inlines = self.user_readonly_inlines + if hasattr(self, "user_readonly_fields"): + self.readonly_fields=self.user_readonly_fields + if hasattr(self, "user_readonly_inlines"): + self.inlines = self.user_readonly_inlines else: if hasattr(self, "readonly_save"): # restore the original readonly fields self.readonly_fields = self.readonly_save + if hasattr(self, "inlines_save"): self.inlines = self.inlines_save try: @@ -68,8 +71,11 @@ class ReadOnlyAwareAdmin(admin.ModelAdmin): def __user_is_readonly(self, request): return request.user.isReadOnlyUser() -class SingletonAdmin (admin.ModelAdmin): +class SingletonAdmin (ReadOnlyAwareAdmin): def has_add_permission(self, request): + if not super(SingletonAdmin, self).has_add_permission(request): + return False + num_objects = self.model.objects.count() if num_objects >= 1: return False @@ -212,12 +218,12 @@ class NetworkLookerUpper: class SliverROInline(ReadOnlyTabularInline): model = Sliver - fields = ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'node', 'deploymentNetwork'] + fields = ['ip', 'instance_name', 'slice', 'numberCores', 'deploymentNetwork', 'image', 'node'] suit_classes = 'suit-tab suit-tab-slivers' class SliverInline(PlStackTabularInline): model = Sliver - fields = ['ip', 'instance_name', 'slice', 'numberCores', 'image', 'node', 'deploymentNetwork'] + fields = ['ip', 'instance_name', 'slice', 'numberCores', 'deploymentNetwork', 'image', 'node'] extra = 0 readonly_fields = ['ip', 'instance_name'] suit_classes = 'suit-tab suit-tab-slivers' @@ -225,6 +231,21 @@ class SliverInline(PlStackTabularInline): def queryset(self, request): return Sliver.select_by_user(request.user) + def formfield_for_foreignkey(self, db_field, request=None, **kwargs): + if db_field.name == 'deploymentNetwork': + kwargs['queryset'] = Deployment.select_by_acl(request.user) + # the inscrutable jquery selector below says: + # find the closest parent "tr" to the current element + # then find the child with class "field-node" + # then find the child with that is a select + # then return its id + kwargs['widget'] = forms.Select(attrs={'onChange': "update_nodes(this, $($(this).closest('tr')[0]).find('.field-node select')[0].id)"}) + #kwargs['widget'] = forms.Select(attrs={'onChange': "console.log($($($(this).closest('tr')[0]).children('.field-node')[0]).children('select')[0].id);"}) + + field = super(SliverInline, self).formfield_for_foreignkey(db_field, request, **kwargs) + + return field + # Note this is breaking in the admin.py when trying to use an inline to add a node/image # def _declared_fieldsets(self): # # Return None so django will call get_fieldsets and we can insert our @@ -305,13 +326,13 @@ class NodeROInline(ReadOnlyTabularInline): model = Node extra = 0 suit_classes = 'suit-tab suit-tab-nodes' - fields = ['name','deployment'] + fields = ['name','deployment','site'] class NodeInline(PlStackTabularInline): model = Node extra = 0 suit_classes = 'suit-tab suit-tab-nodes' - fields = ['name','deployment'] + fields = ['name','deployment','site'] class DeploymentPrivilegeROInline(ReadOnlyTabularInline): model = DeploymentPrivilege @@ -416,6 +437,24 @@ class SliceNetworkInline(PlStackTabularInline): verbose_name = "Network Connection" verbose_name_plural = "Network Connections" suit_classes = 'suit-tab suit-tab-slicenetworks' + fields = ['network'] + +class ImageDeploymentsROInline(ReadOnlyTabularInline): + model = ImageDeployments + extra = 0 + verbose_name = "Image Deployments" + verbose_name_plural = "Image Deployments" + suit_classes = 'suit-tab suit-tab-imagedeployments' + fields = ['image', 'deployment', 'glance_image_id'] + +class ImageDeploymentsInline(PlStackTabularInline): + model = ImageDeployments + extra = 0 + verbose_name = "Image Deployments" + verbose_name_plural = "Image Deployments" + suit_classes = 'suit-tab suit-tab-imagedeployments' + fields = ['image', 'deployment', 'glance_image_id'] + readonly_fields = ['glance_image_id'] class PlainTextWidget(forms.HiddenInput): input_type = 'hidden' @@ -454,18 +493,66 @@ class DeploymentAdminForm(forms.ModelForm): sites = forms.ModelMultipleChoiceField( queryset=Site.objects.all(), required=False, + help_text="Select which sites are allowed to host nodes in this deployment", widget=FilteredSelectMultiple( verbose_name=('Sites'), is_stacked=False ) ) + images = forms.ModelMultipleChoiceField( + queryset=Image.objects.all(), + required=False, + help_text="Select which images should be deployed on this deployment", + widget=FilteredSelectMultiple( + verbose_name=('Images'), is_stacked=False + ) + ) class Meta: model = Deployment def __init__(self, *args, **kwargs): + request = kwargs.pop('request', None) super(DeploymentAdminForm, self).__init__(*args, **kwargs) + self.fields['accessControl'].initial = "allow site " + request.user.site.name + if self.instance and self.instance.pk: self.fields['sites'].initial = [x.site for x in self.instance.sitedeployments_set.all()] + self.fields['images'].initial = [x.image for x in self.instance.imagedeployments_set.all()] + + def manipulate_m2m_objs(self, this_obj, selected_objs, all_relations, relation_class, local_attrname, foreign_attrname): + """ helper function for handling m2m relations from the MultipleChoiceField + + this_obj: the source object we want to link from + + selected_objs: a list of destination objects we want to link to + + all_relations: the full set of relations involving this_obj, including ones we don't want + + relation_class: the class that implements the relation from source to dest + + local_attrname: field name representing this_obj in relation_class + + foreign_attrname: field name representing selected_objs in relation_class + + This function will remove all newobjclass relations from this_obj + that are not contained in selected_objs, and add any relations that + are in selected_objs but don't exist in the data model yet. + """ + + existing_dest_objs = [] + for relation in list(all_relations): + if getattr(relation, foreign_attrname) not in selected_objs: + #print "deleting site", sdp.site + relation.delete() + else: + existing_dest_objs.append(getattr(relation, foreign_attrname)) + + for dest_obj in selected_objs: + if dest_obj not in existing_dest_objs: + #print "adding site", site + kwargs = {foreign_attrname: dest_obj, local_attrname: this_obj} + relation = relation_class(**kwargs) + relation.save() def save(self, commit=True): deployment = super(DeploymentAdminForm, self).save(commit=False) @@ -478,21 +565,8 @@ class DeploymentAdminForm(forms.ModelForm): # create/destroy the through models ourselves. There has to be # a better way... - sites = self.cleaned_data['sites'] - - existing_sites = [] - for sdp in list(deployment.sitedeployments_set.all()): - if sdp.site not in sites: - #print "deleting site", sdp.site - sdp.delete() - else: - existing_sites.append(sdp.site) - - for site in sites: - if site not in existing_sites: - #print "adding site", site - sdp = SiteDeployments(site=site, deployment=deployment) - sdp.save() + self.manipulate_m2m_objs(deployment, self.cleaned_data['sites'], deployment.sitedeployments_set.all(), SiteDeployments, "deployment", "site") + self.manipulate_m2m_objs(deployment, self.cleaned_data['images'], deployment.imagedeployments_set.all(), ImageDeployments, "deployment", "image") self.save_m2m() @@ -508,24 +582,32 @@ class SiteAssocInline(PlStackTabularInline): suit_classes = 'suit-tab suit-tab-sites' class DeploymentAdmin(PlanetStackBaseAdmin): - #form = DeploymentAdminForm model = Deployment - fieldList = ['name','sites'] + fieldList = ['name','sites', 'images', 'accessControl'] fieldsets = [(None, {'fields': fieldList, 'classes':['suit-tab suit-tab-sites']})] - inlines = [DeploymentPrivilegeInline,NodeInline,TagInline] + inlines = [DeploymentPrivilegeInline,NodeInline,TagInline] # ,ImageDeploymentsInline] - user_readonly_inlines = [DeploymentPrivilegeROInline,NodeROInline,TagROInline] + user_readonly_inlines = [DeploymentPrivilegeROInline,NodeROInline,TagROInline] # ,ImageDeploymentsROInline] user_readonly_fields = ['name'] - suit_form_tabs =(('sites','Deployment Details'),('nodes','Nodes'),('deploymentprivileges','Privileges'),('tags','Tags')) + suit_form_tabs =(('sites','Deployment Details'),('nodes','Nodes'),('deploymentprivileges','Privileges'),('tags','Tags')) # ,('imagedeployments','Images')) def get_form(self, request, obj=None, **kwargs): if request.user.isReadOnlyUser(): kwargs["form"] = DeploymentAdminROForm else: kwargs["form"] = DeploymentAdminForm - return super(DeploymentAdmin,self).get_form(request, obj, **kwargs) - + adminForm = super(DeploymentAdmin,self).get_form(request, obj, **kwargs) + + # from stackexchange: pass the request object into the form + + class AdminFormMetaClass(adminForm): + def __new__(cls, *args, **kwargs): + kwargs['request'] = request + return adminForm(*args, **kwargs) + + return AdminFormMetaClass + class ServiceAttrAsTabROInline(ReadOnlyTabularInline): model = ServiceAttribute fields = ['name','value'] @@ -685,6 +767,19 @@ class SliceAdmin(PlanetStackBaseAdmin): ('reservations','Reservations'), ) + def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None): + #deployment_nodes = {} + #for node in Node.objects.all(): + # deployment_nodes[node.deployment.id] = get(deployment_nodes, node.deployment.id, []).append( (node.id, node.name) ) + + deployment_nodes = [] + for node in Node.objects.all(): + deployment_nodes.append( (node.deployment.id, node.id, node.name) ) + + context["deployment_nodes"] = deployment_nodes + + return super(SliceAdmin, self).render_change_form(request, context, add, change, form_url, obj) + def formfield_for_foreignkey(self, db_field, request, **kwargs): if db_field.name == 'site': kwargs['queryset'] = Site.select_by_user(request.user) @@ -750,12 +845,12 @@ class ImageAdmin(PlanetStackBaseAdmin): 'classes': ['suit-tab suit-tab-general']}) ] - suit_form_tabs =(('general','Image Details'),('slivers','Slivers')) + suit_form_tabs =(('general','Image Details'),('slivers','Slivers'),('imagedeployments','Deployments')) + + inlines = [SliverInline, ImageDeploymentsInline] - inlines = [SliverInline] - user_readonly_fields = ['name', 'disk_format', 'container_format'] - user_readonly_inlines = [SliverROInline] + user_readonly_inlines = [SliverROInline, ImageDeploymentsROInline] class NodeForm(forms.ModelForm): class Meta: