diff --git a/apps/fixtures/apps_fixtures.json b/apps/fixtures/apps_fixtures.json
index 2068454cb..472485850 100644
--- a/apps/fixtures/apps_fixtures.json
+++ b/apps/fixtures/apps_fixtures.json
@@ -1,146 +1,194 @@
[
{
- "model": "apps.apps",
- "pk": 5,
- "fields": {
- "name": "Mongo Express",
- "slug": "mongo-express",
- "category": "store",
- "table_field": {
+ "model": "apps.apps",
+ "pk": 5,
+ "fields": {
+ "name": "Mongo Express",
+ "slug": "mongo-express",
+ "category": "store",
+ "table_field": {
"url": "https://{{ release }}.{{ global.domain }}"
- },
- "description": "",
- "settings": {
- "apps":{
- "MongoDB":"one"
},
- "default_values": {
+ "description": "",
+ "settings": {
+ "apps": {
+ "MongoDB": "one"
+ },
+ "default_values": {
"port": "80",
"targetport": "8081"
},
- "permissions": {
- "public": {"value":"false", "option": "false"},
- "project": {"value":"true", "option": "true"},
- "private": {"value":"false", "option": "true"}
- }
+ "permissions": {
+ "public": {
+ "value": "false",
+ "option": "false"
+ },
+ "project": {
+ "value": "true",
+ "option": "true"
+ },
+ "private": {
+ "value": "false",
+ "option": "true"
+ }
+ }
+ },
+ "chart": "apps/mongo-express/chart",
+ "logo": "mongo-express-logo.png",
+ "updated_on": "2021-03-10T19:45:03.927Z",
+ "created_on": "2021-02-19T21:34:37.815Z"
+ }
},
- "chart": "apps/mongo-express/chart",
- "logo": "mongo-express-logo.png",
- "updated_on": "2021-03-10T19:45:03.927Z",
- "created_on": "2021-02-19T21:34:37.815Z"
- }
-},
{
- "model": "apps.apps",
- "pk": 1,
- "fields": {
- "name": "MongoDB",
- "slug": "mongodb",
- "category": "store",
- "table_field": {
+ "model": "apps.apps",
+ "pk": 1,
+ "fields": {
+ "name": "MongoDB",
+ "slug": "mongodb",
+ "category": "store",
+ "table_field": {
"info": "mongodb://{{ release }}:27017"
},
- "description": "",
- "settings": {
- "apps":{
- "Persistent Volume":"one"
- },
- "default_values": {
+ "description": "",
+ "settings": {
+ "apps": {
+ "Persistent Volume": "one"
+ },
+ "default_values": {
"port": "27017",
"targetport": "27017"
},
- "credentials": {
- "username": {"type": "string", "default":"admin","title":"Username"},
- "password": {"type": "string", "default":"password","title":"Password"}
+ "credentials": {
+ "username": {
+ "type": "string",
+ "default": "admin",
+ "title": "Username"
+ },
+ "password": {
+ "type": "string",
+ "default": "password",
+ "title": "Password"
+ }
+ },
+ "permissions": {
+ "public": {
+ "value": "false",
+ "option": "false"
+ },
+ "project": {
+ "value": "true",
+ "option": "true"
+ },
+ "private": {
+ "value": "false",
+ "option": "true"
+ }
+ }
},
- "permissions": {
- "public": {"value":"false", "option": "false"},
- "project": {"value":"true", "option": "true"},
- "private": {"value":"false", "option": "true"}
- }
- },
- "chart": "apps/mongodb/chart",
- "logo": "mongodb-logo.png",
- "updated_on": "2021-03-10T19:45:03.927Z",
- "created_on": "2021-02-19T21:34:37.815Z"
- }
-},
- {
- "model": "apps.apps",
- "pk": 2,
- "fields": {
- "name": "FEDn Combiner",
- "slug": "combiner",
- "category": "compute",
- "table_field": {},
- "description": "",
- "settings": {
- "apps":{
- "FEDn Reducer":"one",
- "Persistent Volume": "one"
- },
- "default_values": {
- "port": "443",
- "targetport": "443"
- },
- "environment": {
- "name": "from",
- "title": "Image",
- "quantity": "one",
- "type": "match"
- },
- "logs": ["combiner"],
- "permissions": {
- "public": {"value":"false", "option": "false"},
- "project": {"value":"true", "option": "true"},
- "private": {"value":"false", "option": "true"}
- }
+ "chart": "apps/mongodb/chart",
+ "logo": "mongodb-logo.png",
+ "updated_on": "2021-03-10T19:45:03.927Z",
+ "created_on": "2021-02-19T21:34:37.815Z"
+ }
},
- "chart": "apps/fedn-combiner/chart",
- "logo": "fedn-combiner-logo.png",
- "updated_on": "2021-03-10T19:45:03.927Z",
- "created_on": "2021-02-19T21:34:37.815Z"
- }
-},
{
- "model": "apps.apps",
- "pk": 4,
- "fields": {
- "name": "FEDn Reducer",
- "slug": "reducer",
- "category": "compute",
- "table_field": {
- "url": "https://{{ release }}.{{ global.domain }}"
+ "model": "apps.apps",
+ "pk": 2,
+ "fields": {
+ "name": "FEDn Combiner",
+ "slug": "combiner",
+ "category": "compute",
+ "table_field": {},
+ "description": "",
+ "settings": {
+ "apps": {
+ "FEDn Reducer": "one",
+ "Persistent Volume": "one"
+ },
+ "default_values": {
+ "port": "443",
+ "targetport": "443"
+ },
+ "environment": {
+ "name": "from",
+ "title": "Image",
+ "quantity": "one",
+ "type": "match"
+ },
+ "logs": [
+ "combiner"
+ ],
+ "permissions": {
+ "public": {
+ "value": "false",
+ "option": "false"
+ },
+ "project": {
+ "value": "true",
+ "option": "true"
+ },
+ "private": {
+ "value": "false",
+ "option": "true"
+ }
+ }
},
- "description": "",
- "settings": {
- "S3": "one",
- "apps":{
- "MongoDB":"one"
+ "chart": "apps/fedn-combiner/chart",
+ "logo": "fedn-combiner-logo.png",
+ "updated_on": "2021-03-10T19:45:03.927Z",
+ "created_on": "2021-02-19T21:34:37.815Z"
+ }
+ },
+ {
+ "model": "apps.apps",
+ "pk": 4,
+ "fields": {
+ "name": "FEDn Reducer",
+ "slug": "reducer",
+ "category": "compute",
+ "table_field": {
+ "url": "https://{{ release }}.{{ global.domain }}"
},
- "default_values": {
- "port": "8090",
- "targetport": "8090"
- },
- "environment": {
- "name": "from",
- "title": "Image",
- "quantity": "one",
- "type": "match"
+ "description": "",
+ "settings": {
+ "S3": "one",
+ "apps": {
+ "MongoDB": "one"
+ },
+ "default_values": {
+ "port": "8090",
+ "targetport": "8090"
+ },
+ "environment": {
+ "name": "from",
+ "title": "Image",
+ "quantity": "one",
+ "type": "match"
+ },
+ "logs": [
+ "reducer"
+ ],
+ "permissions": {
+ "public": {
+ "value": "false",
+ "option": "false"
+ },
+ "project": {
+ "value": "true",
+ "option": "true"
+ },
+ "private": {
+ "value": "false",
+ "option": "true"
+ }
+ }
},
- "logs": ["reducer"],
- "permissions": {
- "public": {"value":"false", "option": "false"},
- "project": {"value":"true", "option": "true"},
- "private": {"value":"false", "option": "true"}
- }
- },
- "chart": "apps/fedn-reducer/chart",
- "logo": "fedn-reducer-logo.png",
- "updated_on": "2021-03-10T19:45:03.927Z",
- "created_on": "2021-02-19T21:34:37.815Z"
- }
-},
+ "chart": "apps/fedn-reducer/chart",
+ "logo": "fedn-reducer-logo.png",
+ "updated_on": "2021-03-10T19:45:03.927Z",
+ "created_on": "2021-02-19T21:34:37.815Z"
+ }
+ },
{
"model": "apps.apps",
"pk": 3,
@@ -210,7 +258,7 @@
"Persistent Volume": "many"
},
"flavor": "one",
- "default_values":{
+ "default_values": {
"port": "80",
"targetport": "8888"
},
@@ -264,12 +312,12 @@
},
"credentials": {
"access_key": {
- "type": "string",
+ "type": "password",
"default": "accesskey",
"title": "Access Key"
},
"secret_key": {
- "type": "string",
+ "type": "password",
"default": "secretkey123",
"title": "Secret Key"
}
@@ -378,7 +426,7 @@
"is_tar": "False",
"port": "5000",
"targetport": "8501"
- },
+ },
"permissions": {
"public": {
"value": "false",
@@ -533,9 +581,24 @@
"settings": {
"volume": {
"size": {
- "type": "string",
+ "type": "select",
+ "title": "Size",
"default": "1Gi",
- "title": "Size"
+ "user_can_edit": false,
+ "items": [
+ {
+ "name": "1Gi",
+ "value": "1Gi"
+ },
+ {
+ "name": "2Gi",
+ "value": "2Gi"
+ },
+ {
+ "name": "5Gi",
+ "value": "5Gi"
+ }
+ ]
},
"storageClass": {
"type": "string",
@@ -579,7 +642,7 @@
"fields": {
"name": "VS Code",
"slug": "vscode",
- "category": "develop",
+ "category": "compute",
"table_field": {
"url": "https://{{ release }}.{{ global.domain }}"
},
@@ -614,4 +677,4 @@
"created_on": "2021-02-19T21:34:37.815Z"
}
}
-]
+]
\ No newline at end of file
diff --git a/apps/generate_form.py b/apps/generate_form.py
index c9f3ab751..bf4a17bab 100644
--- a/apps/generate_form.py
+++ b/apps/generate_form.py
@@ -1,5 +1,5 @@
from django.conf import settings
-from django.db.models import Q
+from django.db.models import Case, IntegerField, Q, Value, When
from models.models import Model
from projects.models import S3, Environment, Flavor, ReleaseName
@@ -137,6 +137,8 @@ def get_form_primitives(app_settings, appinstance=[]):
if not is_meta_key:
parameters_of_key = appinstance.parameters[key]
+ print(f"_key: {_key}")
+
if _key in parameters_of_key.keys():
primitives[key][_key][
"default"
@@ -200,13 +202,38 @@ def get_form_environments(aset, project, app, appinstance=[]):
dep_environment = True
if aset["environment"]["type"] == "match":
environments["objs"] = Environment.objects.filter(
- project=project, app__slug=app.slug
+ Q(project=project) | Q(project__isnull=True, public=True),
+ app__slug=app.slug,
+ ).order_by(
+ Case(
+ When(name__contains="- public", then=Value(1)),
+ default=Value(0),
+ output_field=IntegerField(),
+ ),
+ "-name",
)
elif aset["environment"]["type"] == "any":
- environments["objs"] = Environment.objects.filter(project=project)
+ environments["objs"] = Environment.objects.filter(
+ Q(project=project) | Q(project__isnull=True, public=True)
+ ).order_by(
+ Case(
+ When(name__contains="- public", then=Value(1)),
+ default=Value(0),
+ output_field=IntegerField(),
+ ),
+ "-name",
+ )
elif "apps" in aset["environment"]:
environments["objs"] = Environment.objects.filter(
- project=project, app__slug__in=aset["environment"]["apps"]
+ Q(project=project) | Q(project__isnull=True, public=True),
+ app__slug__in=aset["environment"]["apps"],
+ ).order_by(
+ Case(
+ When(name__contains="- public", then=Value(1)),
+ default=Value(0),
+ output_field=IntegerField(),
+ ),
+ "-name",
)
environments["title"] = aset["environment"]["title"]
diff --git a/apps/models.py b/apps/models.py
index de13001ec..7c4313345 100644
--- a/apps/models.py
+++ b/apps/models.py
@@ -39,7 +39,7 @@ class Apps(models.Model):
logo = models.CharField(max_length=512, null=True, blank=True)
name = models.CharField(max_length=512)
priority = models.IntegerField(default=100)
- projects = models.ManyToManyField("projects.Project")
+ projects = models.ManyToManyField("projects.Project", blank=True)
revision = models.IntegerField(default=1)
settings = models.JSONField(blank=True, null=True)
slug = models.CharField(max_length=512, blank=True, null=True)
@@ -190,7 +190,7 @@ class AppInstance(models.Model):
)
state = models.CharField(max_length=50, null=True, blank=True)
table_field = models.JSONField(blank=True, null=True)
- tags = TagField()
+ tags = TagField(blank=True)
updated_on = models.DateTimeField(auto_now=True)
class Meta:
diff --git a/apps/setup.py b/apps/setup.py
index b78f9a38a..da5fdc98d 100644
--- a/apps/setup.py
+++ b/apps/setup.py
@@ -10,8 +10,8 @@
package_dir={"apps": "."},
python_requires=">=3.6,<4",
install_requires=[
- "django==4.1.7",
- "requests==2.28.1",
+ "django==4.2.1",
+ "requests==2.31.0",
"django-guardian==2.4.0",
"celery==5.2.7",
"Pillow==9.4.0",
diff --git a/apps/tasks.py b/apps/tasks.py
index 0ebb17b9f..bde8bb262 100644
--- a/apps/tasks.py
+++ b/apps/tasks.py
@@ -563,6 +563,9 @@ def sync_mlflow_models():
~Q(state="Deleted"), project__status="active", app__slug="mlflow"
)
for mlflow_app in mlflow_apps:
+ if mlflow_app.project is None or mlflow_app.project.mlflow is None:
+ continue
+
url = "http://{}/{}".format(
mlflow_app.project.mlflow.host,
"api/2.0/mlflow/model-versions/search",
diff --git a/apps/templates/app_table.html b/apps/templates/app_table.html
index 37a74931a..91566966e 100644
--- a/apps/templates/app_table.html
+++ b/apps/templates/app_table.html
@@ -6,7 +6,7 @@
App
Name
- State
+ Status
Created
Actions
@@ -24,7 +24,20 @@
{% else %}
{% endif %}
- {{ appinstance.status.latest.status_type }}
+
+
+ {{ appinstance.status.latest.status_type }}
+
+
+ {% if category == "serve" %}
+
+ {% if appinstance.access != "public" %}
+ unpublished
+ {% else %}
+ published
+ {% endif %}
+ {% endif %}
+
{{ appinstance.created_on }}
@@ -37,19 +50,7 @@
Logs (disabled)
- {% if appinstance.app.settings.publishable == "true" and appinstance.owner.id == request.user.id %}
- {% if appinstance.access == "public" %}
-
-
- Unpublish
-
- {% else %}
-
-
- Publish
-
- {% endif %}
- {% endif %}
+ {% include './app_table_publishable_link.html' %}
{% if appinstance.app.user_can_edit %}
diff --git a/apps/templates/app_table_publishable_link.html b/apps/templates/app_table_publishable_link.html
new file mode 100644
index 000000000..2b3ca5327
--- /dev/null
+++ b/apps/templates/app_table_publishable_link.html
@@ -0,0 +1,45 @@
+{% if appinstance.app.settings.publishable == "true" %}
+{% if appinstance.access == "public" %}
+
+{% if appinstance.owner.id == request.user.id %}
+
+
+ Unpublish
+
+{% else %}
+
+{% endif %}
+{% else %}
+
+{% if appinstance.owner.id == request.user.id %}
+
+
+ Publish
+
+{% else %}
+
+{% endif %}
+{% endif %}
+{% endif %}
\ No newline at end of file
diff --git a/apps/templates/create.html b/apps/templates/create.html
index 65ff0af56..b1d638858 100644
--- a/apps/templates/create.html
+++ b/apps/templates/create.html
@@ -135,6 +135,12 @@
{{ vals.meta_title }}
{% endif %}
+
+ {% if subval.type == "password" %}
+
{{ subval.title }}
+
+ {% endif %}
{% if subval.type == "textfield" %}
{{ subval.title }}
@@ -147,6 +153,24 @@
{{ vals.meta_title }}
{% endif %}
+
+ {% if subval.type == "select" %}
+
+
{{ subval.title }}
+
+
+ {% for item in subval.items %}
+
+ {% if subval.default == item.value %}
+ {{item.name}}
+ {% else %}
+ {{item.name}}
+ {% endif %}
+ {% endfor %}
+
+
+ {% endif %}
+
{% endfor %}
diff --git a/apps/templates/new.html b/apps/templates/new.html
index 60e2706e7..4bd16d48c 100644
--- a/apps/templates/new.html
+++ b/apps/templates/new.html
@@ -66,6 +66,7 @@ {{ app.name }}
{% endfor %}
+
{% endblock %}
\ No newline at end of file
diff --git a/apps/tests/test_generate_form.py b/apps/tests/test_generate_form.py
index 869d73729..ea1b7dde0 100644
--- a/apps/tests/test_generate_form.py
+++ b/apps/tests/test_generate_form.py
@@ -3,9 +3,9 @@
from django.contrib.auth import get_user_model
from django.test import TestCase, override_settings
-from projects.models import Project
+from projects.models import Environment, Project
-from ..generate_form import get_form_primitives
+from ..generate_form import get_form_environments, get_form_primitives
from ..models import AppInstance, Apps
User = get_user_model()
@@ -15,7 +15,17 @@ class GenerateFormTestCase(TestCase):
def setUp(self) -> None:
self.app_settings_pvc = {
"volume": {
- "size": {"type": "string", "title": "Size", "default": "1Gi"},
+ "size": {
+ "type": "select",
+ "title": "Size",
+ "default": "1Gi",
+ "user_can_edit": False,
+ "items": [
+ {"name": "1Gi", "value": "1Gi"},
+ {"name": "2Gi", "value": "2Gi"},
+ {"name": "5Gi", "value": "5Gi"},
+ ],
+ },
"accessModes": {
"type": "string",
"title": "AccessModes",
@@ -33,6 +43,12 @@ def setUp(self) -> None:
"project": {"value": "true", "option": "true"},
},
"default_values": {"port": "port", "targetport": "targetport"},
+ "environment": {
+ "name": "from",
+ "type": "match",
+ "title": "Image",
+ "quantity": "one",
+ },
}
self.user = User.objects.create_user("foo1", "foo@test.com", "bar")
@@ -47,6 +63,8 @@ def setUp(self) -> None:
)
return super().setUp()
+ # primatives
+
@override_settings(DISABLED_APP_INSTANCE_FIELDS=[])
def test_get_form_primitives_should_return_complete(self):
app_settings = deepcopy(self.app_settings_pvc)
@@ -151,10 +169,12 @@ def test_get_form_primitives_should_set_default(self):
result_items = result["volume"]
result_size = result_items["size"]["default"]
+ result_size_user_can_edit = result_items["size"]["user_can_edit"]
result_access_modes = result_items["accessModes"]["default"]
result_storage_class = result_items["storageClass"]["default"]
self.assertEqual(result_size, "5Gi")
+ self.assertFalse(result_size_user_can_edit)
self.assertEqual(result_access_modes, "ReadWriteMany")
self.assertEqual(result_storage_class, "microk8s-hostpath")
@@ -177,3 +197,133 @@ def test_get_form_primitives_should_set_default(self):
self.assertEqual(result_size, "1Gi")
self.assertEqual(result_access_modes, "ReadWriteMany")
self.assertEqual(result_storage_class, "")
+
+ # environments
+
+ def test_get_form_environments_single(self):
+ environment = Environment(
+ app=self.app,
+ project=self.project,
+ name="test",
+ slug="test",
+ repository="test-repo",
+ image="test-image",
+ )
+
+ environment.save()
+ app_settings = deepcopy(self.app_settings_pvc)
+
+ result = get_form_environments(
+ app_settings, self.project, self.app, None
+ )
+
+ dep_environment, environments = result
+
+ self.assertEqual(dep_environment, True)
+
+ objs = environments["objs"]
+
+ self.assertEqual(len(objs), 1)
+
+ result_item = objs[0]
+
+ self.assertEqual(result_item.name, "test")
+ self.assertEqual(result_item.slug, "test")
+
+ def test_get_form_environments_with_public(self):
+ environment = Environment(
+ app=self.app,
+ project=self.project,
+ name="test1",
+ slug="test1",
+ repository="test1-repo",
+ image="test1-image",
+ )
+
+ environment.save()
+
+ environment2 = Environment(
+ app=self.app,
+ name="test2",
+ slug="test2",
+ repository="test2-repo",
+ image="test2-image",
+ public=True,
+ )
+
+ environment2.save()
+
+ app_settings = deepcopy(self.app_settings_pvc)
+
+ result = get_form_environments(
+ app_settings, self.project, self.app, None
+ )
+
+ dep_environment, environments = result
+
+ self.assertEqual(dep_environment, True)
+
+ objs = environments["objs"]
+
+ self.assertEqual(len(objs), 2)
+
+ number_of_public = 0
+
+ for obj in objs:
+ self.assertIn(obj.name, ["test1", "test2"])
+ self.assertIn(obj.slug, ["test1", "test2"])
+
+ if obj.public:
+ number_of_public += 1
+
+ self.assertEqual(number_of_public, 1)
+
+ def test_get_form_environments_with_public_and_other_projects(self):
+ project = Project.objects.create_project(
+ name="test-perm-generate_form2",
+ owner=self.user,
+ description="",
+ repository="",
+ )
+
+ environment = Environment(
+ app=self.app,
+ project=project,
+ name="test1",
+ slug="test1",
+ repository="test1-repo",
+ image="test1-image",
+ )
+
+ environment.save()
+
+ environment2 = Environment(
+ app=self.app,
+ name="test2",
+ slug="test2",
+ repository="test2-repo",
+ image="test2-image",
+ public=True,
+ )
+
+ environment2.save()
+
+ app_settings = deepcopy(self.app_settings_pvc)
+
+ result = get_form_environments(
+ app_settings, self.project, self.app, None
+ )
+
+ dep_environment, environments = result
+
+ self.assertEqual(dep_environment, True)
+
+ objs = environments["objs"]
+
+ self.assertEqual(len(objs), 1)
+
+ result_item = objs[0]
+
+ self.assertEqual(result_item.name, "test2")
+ self.assertEqual(result_item.slug, "test2")
+ self.assertTrue(result_item.public)
diff --git a/apps/tests/test_get_status_view.py b/apps/tests/test_get_status_view.py
index 99e705ebd..7a5e46ef1 100644
--- a/apps/tests/test_get_status_view.py
+++ b/apps/tests/test_get_status_view.py
@@ -78,3 +78,19 @@ def test_user_has_no_access(self):
response = c.post(url, {"apps": [self.app_instance.id]})
self.assertEqual(response.status_code, 403)
+
+ def test_apps_empty(self):
+ c = Client()
+
+ response = c.post(
+ "/accounts/login/", {"username": "foo1", "password": "bar"}
+ )
+ response.status_code
+
+ self.assertEqual(response.status_code, 302)
+
+ url = f"/{self.user.username}/{self.project.slug}/apps/status"
+
+ response = c.post(url, {"apps": []})
+
+ self.assertEqual(response.status_code, 200)
diff --git a/apps/views.py b/apps/views.py
index 1ce257312..930edbea0 100644
--- a/apps/views.py
+++ b/apps/views.py
@@ -159,12 +159,12 @@ def filter_func():
)
class GetStatusView(View):
def post(self, request, user, project):
- body = request.POST["apps"] if request.POST["apps"] is not None else []
- result = {}
+ body = request.POST.get("apps", "")
- arr = body.split(",")
+ result = {}
- if len(arr) > 0:
+ if len(body) > 0:
+ arr = body.split(",")
status_success, status_warning = get_status_defs()
app_instances = AppInstance.objects.filter(pk__in=arr)
@@ -444,17 +444,18 @@ def post(self, request, user, project, app_slug, data=[], wait=False):
@permission_required_or_403("can_view_project", (Project, "slug", "project"))
def publish(request, user, project, category, ai_id):
- print("Publish app {}".format(ai_id))
- print(project)
try:
app = AppInstance.objects.get(pk=ai_id)
- print(app)
- # TODO: Check that user is allowed to publish this app.
- print("setting public")
app.access = "public"
- print("saving")
+
+ if app.parameters["permissions"] is not None:
+ app.parameters["permissions"] = {
+ "public": True,
+ "project": False,
+ "private": False,
+ }
+
app.save()
- print("done")
except Exception as err:
print(err)
@@ -475,6 +476,14 @@ def unpublish(request, user, project, category, ai_id):
try:
app = AppInstance.objects.get(pk=ai_id)
app.access = "project"
+
+ if app.parameters["permissions"] is not None:
+ app.parameters["permissions"] = {
+ "public": False,
+ "project": True,
+ "private": False,
+ }
+
app.save()
except Exception as err:
print(err)
diff --git a/models/admin.py b/models/admin.py
index 857425eb8..c7d00852f 100644
--- a/models/admin.py
+++ b/models/admin.py
@@ -8,10 +8,10 @@ class ModelAdmin(admin.ModelAdmin):
list_display = ("name", "version", "project_name", "object_type_name")
def project_name(self, obj):
- return obj.project.name
+ return obj.project.name if obj.project else None
def object_type_name(self, obj):
- return obj.object_type.name
+ return obj.object_type.name if obj.object_type else None
admin.site.register(Model, ModelAdmin)
diff --git a/models/models.py b/models/models.py
index 0668bac15..ee4ed3e7e 100644
--- a/models/models.py
+++ b/models/models.py
@@ -56,6 +56,8 @@ class ObjectType(models.Model):
)
app_slug = models.CharField(max_length=100, null=True, blank=True)
+ enabled = models.BooleanField(default=True)
+
def __str__(self):
return self.name
@@ -65,7 +67,6 @@ def upload_headline_path(instance, filename):
class Model(models.Model):
-
objects_version = ModelManager()
objects = models.Manager()
PRIVATE = "PR"
diff --git a/models/setup.py b/models/setup.py
index 672f9390f..d45fa4f15 100644
--- a/models/setup.py
+++ b/models/setup.py
@@ -10,8 +10,8 @@
package_dir={"models": "."},
python_requires=">=3.6,<4",
install_requires=[
- "django==4.1.7",
- "requests==2.28.1",
+ "django==4.2.1",
+ "requests==2.31.0",
"django-guardian==2.4.0",
"Pillow==9.4.0",
"Markdown==3.4.1",
diff --git a/models/templates/models/models_details_public.html b/models/templates/models/models_details_public.html
index d5cc9ef4e..8d61b3571 100644
--- a/models/templates/models/models_details_public.html
+++ b/models/templates/models/models_details_public.html
@@ -6,7 +6,6 @@
-
Model Details
@@ -42,7 +41,8 @@
{{ model.name }}
{{ model.uploaded_at }}
- {% if request.user and request.user.is_authenticated and request.user == model.project.owner or request.user.is_superuser %}
+ {% if request.user and request.user.is_authenticated and request.user == model.project.owner or
+ request.user.is_superuser %}
@@ -53,26 +53,28 @@
{{ model.name }}
Add tag
@@ -82,19 +84,21 @@
{{ model.name }}
Tags
- {% with model.tags|split:"," as tags %}
-
+ {% with model.tags|split:"," as tags %}
+
{% for tag in tags %}
-
-
-
+
+
{% endfor %}
{% endwith %}
@@ -103,7 +107,7 @@
{{ model.name }}
- {% endif%}
+ {% endif %}
Download
diff --git a/monitor/setup.py b/monitor/setup.py
index 026d0cd6d..43a00a99e 100644
--- a/monitor/setup.py
+++ b/monitor/setup.py
@@ -10,8 +10,8 @@
package_dir={"monitor": "."},
python_requires=">=3.6,<4",
install_requires=[
- "django==4.1.7",
- "requests==2.28.1",
+ "django==4.2.1",
+ "requests==2.31.0",
],
license="Copyright Scaleout Systems AB. See license for details",
zip_safe=False,
diff --git a/portal/setup.py b/portal/setup.py
index 4b394dd02..d22a48990 100644
--- a/portal/setup.py
+++ b/portal/setup.py
@@ -10,8 +10,8 @@
package_dir={"portal": "."},
python_requires=">=3.6,<4",
install_requires=[
- "django==4.1.7",
- "requests==2.28.1",
+ "django==4.2.1",
+ "requests==2.31.0",
"Pillow==9.4.0",
],
license="Copyright Scaleout Systems AB. See license for details",
diff --git a/projects/models.py b/projects/models.py
index 9b9f88e3d..191f14210 100644
--- a/projects/models.py
+++ b/projects/models.py
@@ -2,14 +2,16 @@
import random
import secrets
import string
+from datetime import timedelta
from django.apps import apps
from django.conf import settings
from django.contrib.auth import get_user_model
from django.db import models
from django.db.models import Q
-from django.db.models.signals import pre_delete, pre_save
+from django.db.models.signals import post_save, pre_delete, pre_save
from django.dispatch import receiver
+from django.utils import timezone
from django.utils.text import slugify
from guardian.shortcuts import assign_perm
@@ -42,7 +44,10 @@ class Environment(models.Model):
image = models.CharField(max_length=100)
name = models.CharField(max_length=100)
project = models.ForeignKey(
- settings.PROJECTS_MODEL, on_delete=models.CASCADE, null=True
+ settings.PROJECTS_MODEL,
+ on_delete=models.CASCADE,
+ null=True,
+ blank=True,
)
registry = models.ForeignKey(
settings.APPINSTANCE_MODEL,
@@ -52,9 +57,11 @@ class Environment(models.Model):
on_delete=models.CASCADE,
)
repository = models.CharField(max_length=100, blank=True, null=True)
- slug = models.CharField(max_length=100, null=True)
+ slug = models.CharField(max_length=100, null=True, blank=True)
updated_at = models.DateTimeField(auto_now=True)
+ public = models.BooleanField(default=False)
+
def __str__(self):
return str(self.name)
@@ -135,9 +142,22 @@ def __str__(self):
return "{} ({})".format(self.name, self.project.slug)
+"""Post save signal when creating an mlflow object"""
+
+
+@receiver(post_save, sender=MLFlow)
+def create_mlflow(sender, instance, created, **kwargs):
+ if created:
+ if instance.project and not instance.project.mlflow:
+ instance.project.mlflow = instance
+ instance.project.save()
+
+
# it will become the default objects attribute for a Project model
class ProjectManager(models.Manager):
- def create_project(self, name, owner, description, repository):
+ def create_project(
+ self, name, owner, description, repository, status="active"
+ ):
user_can_create = self.user_can_create(owner)
if not user_can_create:
@@ -159,6 +179,7 @@ def create_project(self, name, owner, description, repository):
description=description,
repository=repository,
repository_imported=False,
+ status=status,
)
assign_perm("can_view_project", owner, project)
@@ -193,6 +214,18 @@ def user_can_create(self, user):
or has_perm
)
+ def get_projects_from_user(self, user):
+ return self.filter(Q(owner=user) | Q(authorized=user)).distinct()
+
+ def get_project(self, user, slug=None, id=None):
+ qs = (
+ self.filter(Q(owner=user) | Q(authorized=user), pk=id)
+ if id is not None
+ else self.filter(Q(owner=user) | Q(authorized=user), slug=slug)
+ )
+
+ return qs.first() if qs.count() != 0 else None
+
def get_random_pattern_class():
randint = random.randint(1, 12)
@@ -266,6 +299,17 @@ class Meta:
def __str__(self):
return "Name: {} ({})".format(self.name, self.status)
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+
+ if self.status == "created":
+ if (
+ self.created_at is not None
+ and self.created_at < timezone.now() - timedelta(minutes=2)
+ ):
+ self.status = "active"
+ self.save()
+
@receiver(pre_delete, sender=Project)
def on_project_delete(sender, instance, **kwargs):
@@ -337,6 +381,8 @@ class ProjectTemplate(models.Model):
slug = models.CharField(max_length=512, default="")
template = models.TextField(null=True, blank=True)
+ enabled = models.BooleanField(default=True)
+
class Meta:
unique_together = (
"slug",
diff --git a/projects/setup.py b/projects/setup.py
index b2214b189..b46dded86 100644
--- a/projects/setup.py
+++ b/projects/setup.py
@@ -10,8 +10,8 @@
package_dir={"projects": "."},
python_requires=">=3.6,<4",
install_requires=[
- "django==4.1.7",
- "requests==2.28.1",
+ "django==4.2.1",
+ "requests==2.31.0",
"django-guardian==2.4.0",
"celery==5.2.7",
"Pillow==9.4.0",
diff --git a/projects/tasks.py b/projects/tasks.py
index 7678170d4..a00fa7b15 100644
--- a/projects/tasks.py
+++ b/projects/tasks.py
@@ -147,6 +147,9 @@ def create_resources_from_template(user, project_slug, template):
print("Template has either not valid or unknown keys")
raise (ProjectCreationException)
+ project.status = "active"
+ project.save()
+
@shared_task
def delete_project_apps(project_slug):
diff --git a/projects/templates/project_create.html b/projects/templates/project_create.html
index a63c0583f..fe1a24730 100644
--- a/projects/templates/project_create.html
+++ b/projects/templates/project_create.html
@@ -10,56 +10,76 @@
+
+
+