Skip to content

Commit 5877882

Browse files
authored
Merge pull request #228 from hydroserver2/238-workspaces
238 workspaces
2 parents 4b247bd + faa58e9 commit 5877882

File tree

9 files changed

+86
-83
lines changed

9 files changed

+86
-83
lines changed

.env.example

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,37 @@
1-
# The base URL for the deployment.
2-
PROXY_BASE_URL = http://127.0.0.1:8000
3-
APP_CLIENT_URL = http://127.0.0.1:5173 # In production environments, this should generally be the same as the PROXY_BASE_URL.
4-
ALLOWED_HOSTS = 127.0.0.1,localhost
5-
61
# Deployment Settings
7-
SECRET_KEY = # This is required by Django. Keep this value secret.
8-
DEBUG = True # This should be set to False in production environments.
9-
DEPLOYMENT_BACKEND = aws # Use 'aws' for AWS deployments, otherwise use 'local'
10-
DISABLE_ACCOUNT_CREATION = False # Set this to True if you want administrative users to manage the creation of all other user accounts.
11-
12-
# The connection URL so the timescaleDB instance can connect to the PostgreSQL database
13-
DATABASE_URL = postgresql://postgres:password@localhost:5432/tsdb # Update this value to connect to your PostgreSQL database for this deployment.
14-
15-
# Email Settings. This email is used for user account verification and password reset.
16-
EMAIL_HOST =
17-
EMAIL_PORT =
18-
EMAIL_HOST_USER =
19-
EMAIL_HOST_PASSWORD =
20-
ADMIN_EMAIL =
21-
22-
# OAuth Settings. Leave these settings blank to disable any of these services.
23-
OAUTH_GOOGLE_CLIENT =
24-
OAUTH_GOOGLE_SECRET =
25-
OAUTH_ORCID_CLIENT =
26-
OAUTH_ORCID_SECRET =
27-
OAUTH_HYDROSHARE_CLIENT =
28-
OAUTH_HYDROSHARE_SECRET =
2+
3+
PROXY_BASE_URL = # The base URL HydroServer will be served from.
4+
ALLOWED_HOSTS = # Defaults to the host of PROXY_BASE_URL.
5+
DEBUG = # True/False: This should be set to False in production environments.
6+
DEPLOYMENT_BACKEND = # Use 'aws' or 'gcp' for cloud deployments, otherwise use 'local'.
7+
SECRET_KEY = # This is required by Django. Keep this value secret.
8+
DEFAULT_SUPERUSER_EMAIL = # The email of the admin user created during initial startup.
9+
DEFAULT_SUPERUSER_PASSWORD = # The password of the admin user created during initial startup.
10+
ENABLE_AUDITS = # True/False: This will track user activity in the sta app.
11+
12+
13+
# Account Settings
14+
15+
SMTP_URL = # A connection to the SMTP server Django will send account related emails from.
16+
DEFAULT_FROM_EMAIL = # The email address Django will send account related emails from.
17+
ACCOUNT_SIGNUP_ENABLED = # True/False: Controls whether new users can create their own accounts.
18+
ACCOUNT_OWNERSHIP_ENABLED = # True/False: Controls whether new users can create/own workspaces.
19+
SOCIALACCOUNT_SIGNUP_ONLY = # True/False: Controls whether non-social account creation is supported.
20+
21+
22+
# Database Settings
23+
24+
DATABASE_URL = # A connection to the PostgreSQL or TimescaleDB server HydroServer will use.
25+
CONN_MAX_AGE = # Controls how long Django will hold database connections open before closing them.
26+
CONN_HEALTH_CHECKS = # True/False: Controls whether connection health checks are enabled.
27+
SSL_REQUIRED = # True/False: Controls whether Django will connect to the database using SSL.
28+
29+
30+
# Storage Settings
31+
32+
MEDIA_BUCKET_NAME = # The name of the AWS or GCP bucket photos will be stored in.
33+
STATIC_BUCKET_NAME = # The name of the AWS or GCP bucket static files will be stored in.
34+
APP_CLIENT_URL = # The base URL media and static files will be served from, if not the PROXY_BASE_URL.
35+
AWS_CLOUDFRONT_KEY = # The AWS CloudFront key used to generate signed photo URLs.
36+
AWS_CLOUDFRONT_KEY_ID = # The ID of the AWS CloudFront key used to generate signed photo URLs.
37+
GS_PROJECT_ID = # The ID of the GCP project HydroServer is deployed to.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Generated by Django 5.2b1 on 2025-02-27 18:38
2+
3+
from django.db import migrations
4+
from django.core.management import call_command
5+
6+
7+
def load_fixtures(apps, schema_editor):
8+
call_command("loaddata", "iam/fixtures/default_roles.json")
9+
call_command("loaddata", "iam/fixtures/default_types.json")
10+
11+
12+
class Migration(migrations.Migration):
13+
dependencies = [
14+
("iam", "0008_alter_collaborator_role_alter_collaborator_user_and_more"),
15+
]
16+
17+
operations = [
18+
migrations.RunPython(load_fixtures),
19+
]

iam/views/authentication.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ def get_auth_methods(request):
1818
{
1919
"id": social_app.provider,
2020
"name": social_app.name,
21-
"icon_link": f"{settings.PROXY_BASE_URL}{static(f'providers/{social_app.provider}.png')}",
21+
"icon_link": f"{settings.PROXY_BASE_URL if settings.DEPLOYMENT_BACKEND == 'local' else ''}"
22+
f"{static(f'providers/{social_app.provider}.png')}",
2223
"signup_enabled": True if social_app.settings.get("allowSignUp") is not False else False,
2324
"connect_enabled": True if social_app.settings.get("allowConnection") is True else False,
2425
} for social_app in SocialApp.objects.all()

sta/services/thing.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
from typing import Optional, Literal
33
from ninja.errors import HttpError
44
from django.contrib.auth import get_user_model
5+
from django.contrib.postgres.aggregates import ArrayAgg
6+
from django.db.models import F
57
from iam.services.utils import ServiceUtils
68
from sta.models import Thing, Location, Tag, Photo
79
from sta.schemas import ThingPostBody, ThingPatchBody, TagPostBody, TagDeleteBody, PhotoDeleteBody
@@ -107,7 +109,9 @@ def get_tag_keys(user: Optional[User], workspace_id: Optional[uuid.UUID], thing_
107109
if thing_id:
108110
queryset = queryset.filter(thing_id=thing_id)
109111

110-
return list(queryset.visible(user=user).values_list("key", flat=True).distinct())
112+
tags = queryset.visible(user=user).values("key").annotate(values=ArrayAgg(F("value"), distinct=True))
113+
114+
return {entry["key"]: entry["values"] for entry in tags}
111115

112116
def add_tag(self, user: User, uid: uuid.UUID, data: TagPostBody):
113117
thing = self.get_thing_for_action(user=user, uid=uid, action="edit")

sta/views/tag.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def get_tags(request: HydroServerHttpRequest, thing_id: Path[uuid.UUID]):
3636
"keys",
3737
auth=[session_auth, bearer_auth, anonymous_auth],
3838
response={
39-
200: list[str],
39+
200: dict[str, list[str]],
4040
401: str,
4141
}
4242
)

tests/fixtures/test_collaborators.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,29 @@
33
pk: 1000000010
44
fields:
55
user: 1000000011
6-
role: a7701d22-e716-4584-a18c-055c8c4f2bd6
6+
role: 2f05f775-5d8a-4778-9942-3d13a64ec7a3
77
workspace: 6e0deaf2-a92b-421b-9ece-86783265596f
88

99
# Public Workspace Viewer
1010
- model: iam.collaborator
1111
pk: 1000000011
1212
fields:
1313
user: 1000000012
14-
role: 702e2156-baee-451a-b7ee-dca8baeda378
14+
role: 1d91bff7-edf6-4b69-bb26-674436335725
1515
workspace: 6e0deaf2-a92b-421b-9ece-86783265596f
1616

1717
# Private Workspace Editor
1818
- model: iam.collaborator
1919
pk: 1000000012
2020
fields:
2121
user: 1000000011
22-
role: a7701d22-e716-4584-a18c-055c8c4f2bd6
22+
role: 2f05f775-5d8a-4778-9942-3d13a64ec7a3
2323
workspace: b27c51a0-7374-462d-8a53-d97d47176c10
2424

2525
# Private Workspace Viewer
2626
- model: iam.collaborator
2727
pk: 1000000013
2828
fields:
2929
user: 1000000012
30-
role: 702e2156-baee-451a-b7ee-dca8baeda378
30+
role: 1d91bff7-edf6-4b69-bb26-674436335725
3131
workspace: b27c51a0-7374-462d-8a53-d97d47176c10

tests/fixtures/test_roles.yaml

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,3 @@
1-
# Editor Role
2-
- model: iam.role
3-
pk: a7701d22-e716-4584-a18c-055c8c4f2bd6
4-
fields:
5-
name: "Editor"
6-
description: "Editor"
7-
8-
# Editor Permissions
9-
- model: iam.permission
10-
pk: 1000000010
11-
fields:
12-
role: a7701d22-e716-4584-a18c-055c8c4f2bd6
13-
permission_type: "*"
14-
resource_type: "*"
15-
16-
# Viewer Role
17-
- model: iam.role
18-
pk: 702e2156-baee-451a-b7ee-dca8baeda378
19-
fields:
20-
name: "Viewer"
21-
description: "Viewer"
22-
23-
# Viewer Permissions
24-
- model: iam.permission
25-
pk: 1000000011
26-
fields:
27-
role: 702e2156-baee-451a-b7ee-dca8baeda378
28-
permission_type: "view"
29-
resource_type: "*"
30-
311
# Private Role
322
- model: iam.role
333
pk: 60b9d8b1-28d1-4d0d-9bee-4e47219d0118

tests/iam/services/test_collaborator.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,17 @@ def test_list_collaborator(get_user, user, workspace, message, error_code):
3232

3333

3434
@pytest.mark.parametrize("user, collaborator, workspace, role, message, error_code", [
35-
("owner", "anonymous", "b27c51a0-7374-462d-8a53-d97d47176c10", "a7701d22-e716-4584-a18c-055c8c4f2bd6", None, None),
36-
("admin", "anonymous", "b27c51a0-7374-462d-8a53-d97d47176c10", "a7701d22-e716-4584-a18c-055c8c4f2bd6", None, None),
35+
("owner", "anonymous", "b27c51a0-7374-462d-8a53-d97d47176c10", "2f05f775-5d8a-4778-9942-3d13a64ec7a3", None, None),
36+
("admin", "anonymous", "b27c51a0-7374-462d-8a53-d97d47176c10", "2f05f775-5d8a-4778-9942-3d13a64ec7a3", None, None),
3737
("owner", "anonymous", "b27c51a0-7374-462d-8a53-d97d47176c10", "60b9d8b1-28d1-4d0d-9bee-4e47219d0118", None, None),
38-
("owner", "limited", "b27c51a0-7374-462d-8a53-d97d47176c10", "a7701d22-e716-4584-a18c-055c8c4f2bd6", None, None),
39-
("editor", "anonymous", "b27c51a0-7374-462d-8a53-d97d47176c10", "a7701d22-e716-4584-a18c-055c8c4f2bd6", None, None),
40-
("viewer", "anonymous", "b27c51a0-7374-462d-8a53-d97d47176c10", "a7701d22-e716-4584-a18c-055c8c4f2bd6", "You do not have permission", 403),
41-
("anonymous", "anonymous", "6e0deaf2-a92b-421b-9ece-86783265596f", "a7701d22-e716-4584-a18c-055c8c4f2bd6", "You do not have permission", 403),
42-
("anonymous", "anonymous", "b27c51a0-7374-462d-8a53-d97d47176c10", "a7701d22-e716-4584-a18c-055c8c4f2bd6", "Workspace does not exist", 404),
43-
("owner", "fake", "b27c51a0-7374-462d-8a53-d97d47176c10", "a7701d22-e716-4584-a18c-055c8c4f2bd6", "No account with email", 400),
44-
("owner", "owner", "b27c51a0-7374-462d-8a53-d97d47176c10", "a7701d22-e716-4584-a18c-055c8c4f2bd6", "Account with email", 400),
45-
("owner", "viewer", "b27c51a0-7374-462d-8a53-d97d47176c10", "a7701d22-e716-4584-a18c-055c8c4f2bd6", "Account with email", 400),
38+
("owner", "limited", "b27c51a0-7374-462d-8a53-d97d47176c10", "2f05f775-5d8a-4778-9942-3d13a64ec7a3", None, None),
39+
("editor", "anonymous", "b27c51a0-7374-462d-8a53-d97d47176c10", "2f05f775-5d8a-4778-9942-3d13a64ec7a3", None, None),
40+
("viewer", "anonymous", "b27c51a0-7374-462d-8a53-d97d47176c10", "2f05f775-5d8a-4778-9942-3d13a64ec7a3", "You do not have permission", 403),
41+
("anonymous", "anonymous", "6e0deaf2-a92b-421b-9ece-86783265596f", "2f05f775-5d8a-4778-9942-3d13a64ec7a3", "You do not have permission", 403),
42+
("anonymous", "anonymous", "b27c51a0-7374-462d-8a53-d97d47176c10", "2f05f775-5d8a-4778-9942-3d13a64ec7a3", "Workspace does not exist", 404),
43+
("owner", "fake", "b27c51a0-7374-462d-8a53-d97d47176c10", "2f05f775-5d8a-4778-9942-3d13a64ec7a3", "No account with email", 400),
44+
("owner", "owner", "b27c51a0-7374-462d-8a53-d97d47176c10", "2f05f775-5d8a-4778-9942-3d13a64ec7a3", "Account with email", 400),
45+
("owner", "viewer", "b27c51a0-7374-462d-8a53-d97d47176c10", "2f05f775-5d8a-4778-9942-3d13a64ec7a3", "Account with email", 400),
4646
("owner", "anonymous", "b27c51a0-7374-462d-8a53-d97d47176c10", "00000000-0000-0000-0000-000000000000", "Role does not exist", 404),
4747
("owner", "anonymous", "6e0deaf2-a92b-421b-9ece-86783265596f", "60b9d8b1-28d1-4d0d-9bee-4e47219d0118", "Role does not exist", 404),
4848
])
@@ -65,15 +65,15 @@ def test_create_collaborator(get_user, user, collaborator, workspace, role, mess
6565

6666

6767
@pytest.mark.parametrize("user, collaborator, workspace, role, message, error_code", [
68-
("owner", "editor", "b27c51a0-7374-462d-8a53-d97d47176c10", "a7701d22-e716-4584-a18c-055c8c4f2bd6", None, None),
69-
("admin", "editor", "b27c51a0-7374-462d-8a53-d97d47176c10", "a7701d22-e716-4584-a18c-055c8c4f2bd6", None, None),
68+
("owner", "editor", "b27c51a0-7374-462d-8a53-d97d47176c10", "2f05f775-5d8a-4778-9942-3d13a64ec7a3", None, None),
69+
("admin", "editor", "b27c51a0-7374-462d-8a53-d97d47176c10", "2f05f775-5d8a-4778-9942-3d13a64ec7a3", None, None),
7070
("owner", "editor", "b27c51a0-7374-462d-8a53-d97d47176c10", "60b9d8b1-28d1-4d0d-9bee-4e47219d0118", None, None),
71-
("editor", "viewer", "b27c51a0-7374-462d-8a53-d97d47176c10", "a7701d22-e716-4584-a18c-055c8c4f2bd6", None, None),
72-
("viewer", "editor", "b27c51a0-7374-462d-8a53-d97d47176c10", "a7701d22-e716-4584-a18c-055c8c4f2bd6", "You do not have permission", 403),
73-
("anonymous", "editor", "6e0deaf2-a92b-421b-9ece-86783265596f", "a7701d22-e716-4584-a18c-055c8c4f2bd6", "You do not have permission", 403),
74-
("anonymous", "editor", "b27c51a0-7374-462d-8a53-d97d47176c10", "a7701d22-e716-4584-a18c-055c8c4f2bd6", "Workspace does not exist", 404),
75-
("owner", "fake", "b27c51a0-7374-462d-8a53-d97d47176c10", "a7701d22-e716-4584-a18c-055c8c4f2bd6", "No collaborator with email", 400),
76-
("owner", "anonymous", "b27c51a0-7374-462d-8a53-d97d47176c10", "a7701d22-e716-4584-a18c-055c8c4f2bd6", "No collaborator with email", 400),
71+
("editor", "viewer", "b27c51a0-7374-462d-8a53-d97d47176c10", "2f05f775-5d8a-4778-9942-3d13a64ec7a3", None, None),
72+
("viewer", "editor", "b27c51a0-7374-462d-8a53-d97d47176c10", "2f05f775-5d8a-4778-9942-3d13a64ec7a3", "You do not have permission", 403),
73+
("anonymous", "editor", "6e0deaf2-a92b-421b-9ece-86783265596f", "2f05f775-5d8a-4778-9942-3d13a64ec7a3", "You do not have permission", 403),
74+
("anonymous", "editor", "b27c51a0-7374-462d-8a53-d97d47176c10", "2f05f775-5d8a-4778-9942-3d13a64ec7a3", "Workspace does not exist", 404),
75+
("owner", "fake", "b27c51a0-7374-462d-8a53-d97d47176c10", "2f05f775-5d8a-4778-9942-3d13a64ec7a3", "No collaborator with email", 400),
76+
("owner", "anonymous", "b27c51a0-7374-462d-8a53-d97d47176c10", "2f05f775-5d8a-4778-9942-3d13a64ec7a3", "No collaborator with email", 400),
7777
("owner", "editor", "b27c51a0-7374-462d-8a53-d97d47176c10", "00000000-0000-0000-0000-000000000000", "Role does not exist", 404),
7878
("owner", "editor", "6e0deaf2-a92b-421b-9ece-86783265596f", "60b9d8b1-28d1-4d0d-9bee-4e47219d0118", "Role does not exist", 404),
7979
])

tests/iam/services/test_role.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def test_list_role(get_user, user, workspace, length, message, error_code):
3838
@pytest.mark.parametrize("user, workspace, role, message, error_code", [
3939
("owner", "b27c51a0-7374-462d-8a53-d97d47176c10", "60b9d8b1-28d1-4d0d-9bee-4e47219d0118", "Private", None),
4040
("admin", "b27c51a0-7374-462d-8a53-d97d47176c10", "60b9d8b1-28d1-4d0d-9bee-4e47219d0118", "Private", None),
41-
("anonymous", "6e0deaf2-a92b-421b-9ece-86783265596f", "a7701d22-e716-4584-a18c-055c8c4f2bd6", "Editor", None),
41+
("anonymous", "6e0deaf2-a92b-421b-9ece-86783265596f", "2f05f775-5d8a-4778-9942-3d13a64ec7a3", "Editor", None),
4242
("owner", "00000000-0000-0000-0000-000000000000", "6e0deaf2-a92b-421b-9ece-86783265596f", "Workspace does not exist", 404),
4343
("owner", "b27c51a0-7374-462d-8a53-d97d47176c10", "00000000-0000-0000-0000-000000000000", "Role does not exist", 404),
4444
("anonymous", "b27c51a0-7374-462d-8a53-d97d47176c10", "60b9d8b1-28d1-4d0d-9bee-4e47219d0118", "Workspace does not exist", 404),

0 commit comments

Comments
 (0)