diff --git a/estate/assets/js/components/DashboardListView.jsx b/estate/assets/js/components/DashboardListView.jsx
index 8ed213a..86ee396 100644
--- a/estate/assets/js/components/DashboardListView.jsx
+++ b/estate/assets/js/components/DashboardListView.jsx
@@ -90,7 +90,9 @@ class DashboardListView extends React.Component {
let data = []
each(this.props.data, (item) => {
if (item) {
- item.link =
+ if (item.is_owner) {
+ item.link =
+ }
item.modified = new Date(Date.parse(item.modified)).toLocaleString()
data.push(item)
}
diff --git a/estate/assets/js/components/TerraformNamespaceItem.jsx b/estate/assets/js/components/TerraformNamespaceItem.jsx
index d82da1c..a9a30f1 100644
--- a/estate/assets/js/components/TerraformNamespaceItem.jsx
+++ b/estate/assets/js/components/TerraformNamespaceItem.jsx
@@ -234,7 +234,7 @@ class TerraformNamespaceItem extends React.Component {
this.props.unlockNamespace(this.props.namespace.pk)
}
createFilePane(props) {
- var locked = this.props.namespace.is_uneditable
+ var locked = this.props.namespace.is_readonly
var index = findIndex(this.state.files, {slug: props.match.params.file})
if (index == -1){
return null
@@ -259,7 +259,7 @@ class TerraformNamespaceItem extends React.Component {
)
}
createFileList() {
- var locked = this.props.namespace.is_uneditable
+ var locked = this.props.namespace.is_readonly
var url = this.props.match.url
var count = 0
var elements = []
@@ -307,7 +307,7 @@ class TerraformNamespaceItem extends React.Component {
)
}
createTemplatePane(props) {
- var locked = this.props.namespace.is_uneditable
+ var locked = this.props.namespace.is_readonly
var index = findIndex(this.state.templates, {slug: props.match.params.template})
if (index == -1){
return null
@@ -349,7 +349,7 @@ class TerraformNamespaceItem extends React.Component {
)
}
createTemplateList() {
- var locked = this.props.namespace.is_uneditable
+ var locked = this.props.namespace.is_readonly
var url = this.props.match.url
var count = 0
var elements = []
@@ -446,7 +446,7 @@ class TerraformNamespaceItem extends React.Component {
)
}
createExperimentPane() {
- var locked = this.props.namespace.is_uneditable
+ var locked = this.props.namespace.is_readonly
var data = this.props.experimentOutput
var output = join(data.output, "")
return (
@@ -501,9 +501,12 @@ class TerraformNamespaceItem extends React.Component {
if (this.props.namespace == null) {
return null
}
+ if (this.props.namespace.is_owner == false){
+ return null
+ }
const url = this.props.match.url
const namespace = this.props.namespace
- const locked = namespace.is_uneditable
+ const locked = namespace.is_readonly
return (
diff --git a/estate/assets/js/components/TerraformNamespaceList.jsx b/estate/assets/js/components/TerraformNamespaceList.jsx
index a0a4855..3320412 100644
--- a/estate/assets/js/components/TerraformNamespaceList.jsx
+++ b/estate/assets/js/components/TerraformNamespaceList.jsx
@@ -23,6 +23,18 @@ const TerraformNamespacesTableColumns = [
accessor: "description",
sortable: false,
},
+ {
+ Header: "Owning Group",
+ accessor: "owner",
+ maxWidth: 200,
+ sortable: false,
+ },
+ {
+ Header: "Locked",
+ accessor: "locking_user",
+ maxWidth: 200,
+ sortable: false,
+ },
{
Header: "Modified",
accessor: "modified",
diff --git a/estate/core/views/base.py b/estate/core/views/base.py
index 14857a2..bc7c719 100644
--- a/estate/core/views/base.py
+++ b/estate/core/views/base.py
@@ -1,6 +1,6 @@
from __future__ import absolute_import
from django.db.models.query import QuerySet
-from rest_framework import serializers, decorators, response
+from rest_framework import serializers, decorators, response, permissions
class HistoricalSerializer(serializers.ModelSerializer):
@@ -31,3 +31,21 @@ def history(self, request, *args, **kwargs):
instance = self.get_object()
serializer = self.get_serializer(instance.history.all(), many=True, is_history=True)
return response.Response(serializer.data)
+
+
+class IsOwner(permissions.BasePermission):
+
+ def has_object_permission(self, request, view, obj):
+ if obj.owner:
+ return request.user.groups.filter(name=obj.owner).count() == 1
+ else:
+ return True
+
+
+class OwnsNamespace(permissions.BasePermission):
+
+ def has_object_permission(self, request, view, obj):
+ if obj.namespace.owner:
+ return request.user.groups.filter(name=obj.namespace.owner).count() == 1
+ else:
+ return True
diff --git a/estate/settings/drf.py b/estate/settings/drf.py
index 4b3f6de..e4a0aec 100644
--- a/estate/settings/drf.py
+++ b/estate/settings/drf.py
@@ -31,7 +31,6 @@ def api_exception_handler(exc, context):
'PAGE_SIZE': 10,
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
- 'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
@@ -62,7 +61,7 @@ def api_exception_handler(exc, context):
},
'LOGIN_URL': 'rest_framework:login',
'LOGOUT_URL': 'rest_framework:logout',
- 'USE_SESSION_AUTH': True,
+ 'USE_SESSION_AUTH': False,
'APIS_SORTER': 'alpha',
'JSON_EDITOR': True,
'VALIDATOR_URL': None
diff --git a/estate/terraform/admin/namespace.py b/estate/terraform/admin/namespace.py
index 229664d..83e8aa2 100644
--- a/estate/terraform/admin/namespace.py
+++ b/estate/terraform/admin/namespace.py
@@ -6,8 +6,8 @@
class NamespaceAdmin(admin.ModelAdmin):
- list_display = ['pk', 'title', 'description', 'modified']
- list_editable = ['title', 'description']
+ list_display = ['pk', 'title', 'owner', 'locked', 'locking_user']
+ list_editable = ['title', 'owner']
list_filter = ['title']
search_fields = ['slug', 'title']
list_per_page = 10
diff --git a/estate/terraform/migrations/0009_auto_20170917_0156.py b/estate/terraform/migrations/0009_auto_20170917_0156.py
new file mode 100644
index 0000000..3ea2345
--- /dev/null
+++ b/estate/terraform/migrations/0009_auto_20170917_0156.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.7 on 2017-09-17 01:56
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('terraform', '0008_auto_20170905_2028'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='historicalnamespace',
+ name='locked',
+ field=models.BooleanField(default=False, verbose_name='locked'),
+ ),
+ migrations.AlterField(
+ model_name='historicalnamespace',
+ name='owner',
+ field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='auth.Group'),
+ ),
+ migrations.AlterField(
+ model_name='namespace',
+ name='locked',
+ field=models.BooleanField(default=False, verbose_name='locked'),
+ ),
+ migrations.AlterField(
+ model_name='namespace',
+ name='locking_user',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='locked_namespaces', to=settings.AUTH_USER_MODEL),
+ ),
+ migrations.AlterField(
+ model_name='namespace',
+ name='owner',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='namespaces', to='auth.Group'),
+ ),
+ ]
diff --git a/estate/terraform/models/namespace.py b/estate/terraform/models/namespace.py
index 7d9d594..a790b24 100644
--- a/estate/terraform/models/namespace.py
+++ b/estate/terraform/models/namespace.py
@@ -14,9 +14,9 @@
class Namespace(EstateAbstractBase):
- owner = models.CharField(_('owner'), max_length=80)
- locked = models.BooleanField(default=False)
- locking_user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, blank=True)
+ owner = models.ForeignKey("auth.Group", related_name="namespaces", null=True, blank=True)
+ locked = models.BooleanField(_('locked'), default=False)
+ locking_user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="locked_namespaces", null=True, blank=True)
# TODO: Add tags
history = HistoricalRecordsWithoutDelete(excluded_fields=['slug'])
diff --git a/estate/terraform/views/file.py b/estate/terraform/views/file.py
index 2c9bbc7..6228024 100644
--- a/estate/terraform/views/file.py
+++ b/estate/terraform/views/file.py
@@ -1,7 +1,7 @@
from __future__ import absolute_import
from django.apps import apps
-from rest_framework import serializers, viewsets
-from estate.core.views import HistoricalSerializer, HistoryMixin
+from rest_framework import serializers, viewsets, filters
+from estate.core.views import HistoricalSerializer, HistoryMixin, OwnsNamespace
Namespace = apps.get_model('terraform.Namespace')
File = apps.get_model('terraform.File')
@@ -20,9 +20,18 @@ class Meta:
historical_fields = ("pk", "slug", "title", "namespace", "description", "content", "disable")
+class FileFilter(filters.FilterSet):
+
+ class Meta:
+ model = File
+ fields = ["title", "namespace"]
+
+
class FileApiView(HistoryMixin, viewsets.ModelViewSet):
queryset = File.objects.all()
serializer_class = FileSerializer
+ filter_class = FileFilter
+ permission_classes = (OwnsNamespace, )
filter_fields = ('slug',)
search_fields = ('title',)
ordering_fields = ('title', 'created', 'modified')
diff --git a/estate/terraform/views/namespace.py b/estate/terraform/views/namespace.py
index 570a530..68ebaed 100644
--- a/estate/terraform/views/namespace.py
+++ b/estate/terraform/views/namespace.py
@@ -2,7 +2,7 @@
import django_filters
from django.apps import apps
from rest_framework import serializers, viewsets, filters, decorators, response
-from estate.core.views import HistoricalSerializer, HistoryMixin
+from estate.core.views import HistoricalSerializer, HistoryMixin, IsOwner
from .file import FileSerializer
from .template import TemplateInstanceSerializer
from ..terraform import Terraform
@@ -13,22 +13,30 @@
class NamespaceSerializer(HistoricalSerializer):
description = serializers.CharField(default="", allow_blank=True)
- owner = serializers.CharField(default="", allow_blank=True)
+ owner = serializers.SlugRelatedField(slug_field="name", read_only=True)
files = FileSerializer(many=True, read_only=True, is_history=True)
templates = TemplateInstanceSerializer(many=True, read_only=True, is_history=True)
locking_user = serializers.SlugRelatedField(slug_field="username", read_only=True)
+ is_owner = serializers.SerializerMethodField(read_only=True)
is_unlockable = serializers.SerializerMethodField(read_only=True)
- is_uneditable = serializers.SerializerMethodField(read_only=True)
+ is_readonly = serializers.SerializerMethodField(read_only=True)
class Meta:
model = Namespace
- fields = ("pk", "slug", "title", "description", "owner", "files", "templates", "locked", "locking_user", "is_unlockable", "is_uneditable", "created", "modified")
+ fields = ("pk", "slug", "title", "description", "owner", "files", "templates", "locked", "locking_user", "is_owner", "is_unlockable", "is_readonly", "created", "modified")
historical_fields = ("pk", "slug", "title", "description", "owner", "locked", "locking_user", "historical_files", "historical_templates")
+ def get_is_owner(self, instance):
+ if instance.owner:
+ return self.context["request"].user.groups.filter(name=instance.owner).count() == 1
+ else:
+ return True
+
def get_is_unlockable(self, instance):
- return instance.is_unlockable(self.context["request"].user)
+ is_owner = self.get_is_owner(instance)
+ return all([instance.is_unlockable(self.context["request"].user), is_owner])
- def get_is_uneditable(self, instance):
+ def get_is_readonly(self, instance):
result = False
if instance.locked is True:
if instance.is_unlockable(self.context["request"].user) is not True:
@@ -51,6 +59,7 @@ class NamespaceApiView(HistoryMixin, viewsets.ModelViewSet):
queryset = Namespace.objects.all()
serializer_class = NamespaceSerializer
filter_class = NamespaceFilter
+ permission_classes = (IsOwner, )
search_fields = ('title',)
ordering_fields = ('title', 'created', 'modified')
diff --git a/estate/terraform/views/template.py b/estate/terraform/views/template.py
index 9811df3..216547f 100644
--- a/estate/terraform/views/template.py
+++ b/estate/terraform/views/template.py
@@ -1,9 +1,9 @@
from __future__ import absolute_import
import json
from django.apps import apps
-from rest_framework import serializers, viewsets, decorators, exceptions, status, response
+from rest_framework import serializers, viewsets, decorators, exceptions, status, response, filters
from semantic_version import Version
-from estate.core.views import HistoricalSerializer, HistoryMixin
+from estate.core.views import HistoricalSerializer, HistoryMixin, OwnsNamespace
from estate.core import renderer
Namespace = apps.get_model("terraform.Namespace")
@@ -20,15 +20,19 @@ class TemplateSerializer(HistoricalSerializer):
version_increment = serializers.ChoiceField(choices=["major", "minor", "patch", "initial"], write_only=True)
body = serializers.CharField(default="", allow_blank=True, validators=[renderer.is_valid_template])
body_mode = serializers.SerializerMethodField()
+ is_owner = serializers.SerializerMethodField()
class Meta:
model = Template
- fields = ("pk", "slug", "title", "description", "version", "version_increment", "json_schema", "ui_schema", "body", "body_mode", "created", "modified")
+ fields = ("pk", "slug", "title", "description", "version", "version_increment", "json_schema", "ui_schema", "body", "body_mode", "is_owner", "created", "modified")
historical_fields = ("pk", "slug", "title", "description", "version", "json_schema", "ui_schema", "body")
def get_body_mode(self, instance):
return renderer.get_style(instance.body)
+ def get_is_owner(self, instance):
+ return True
+
def create(self, validated_data):
version_increment = validated_data.pop("version_increment", "initial")
if version_increment != "initial":
@@ -123,9 +127,18 @@ def update(self, instance, validated_data):
return super(TemplateInstanceSerializer, self).update(instance, validated_data)
+class TemplateInstanceFilter(filters.FilterSet):
+
+ class Meta:
+ model = TemplateInstance
+ fields = ["title", "namespace"]
+
+
class TemplateInstanceApiView(HistoryMixin, viewsets.ModelViewSet):
queryset = TemplateInstance.objects.all()
serializer_class = TemplateInstanceSerializer
+ filter_class = TemplateInstanceFilter
+ permission_classes = (OwnsNamespace, )
filter_fields = ("slug",)
search_fields = ("title",)
ordering_fields = ("title", "created", "modified")
diff --git a/package.json b/package.json
index dccf392..d9f1be0 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,7 @@
"rc-select": "6.8.6",
"react": "15.5.4",
"@skidding/react-codemirror": "^1.0.0",
+ "react-diff": "0.0.7",
"react-dom": "15.5.4",
"react-jsonschema-form": "0.48.2",
"react-hot-loader": "3.0.0-beta.6",