Loading resources/migrations/0025_projectproperty.py 0 → 100644 +40 −0 Original line number Diff line number Diff line # Generated by Django 4.0 on 2022-01-21 10:23 from django.db import migrations, models import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ ("resources", "0024_alter_metadata_options"), ] operations = [ migrations.CreateModel( name="ProjectProperty", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("key", models.CharField(max_length=64)), ("value", models.JSONField()), ( "project", models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, to="resources.project", ), ), ], options={ "unique_together": {("project", "key")}, }, ), ] resources/migrations/0026_project_props_fixtures.py 0 → 100644 +35 −0 Original line number Diff line number Diff line # Generated by Django 4.0 on 2022-01-21 10:25 from django.db import migrations from resources import models def add_fixtures(*args): create_right, _ = models.Permission.objects.get_or_create( label="project_property.create" ) read_right, _ = models.Permission.objects.get_or_create( label="project_property.read" ) update_right, _ = models.Permission.objects.get_or_create( label="project_property.update" ) delete_right, _ = models.Permission.objects.get_or_create( label="project_property.delete" ) for project in models.Project.objects.all(): admin_role = models.Role.objects.filter(project=project, label="admin").first() if admin_role: admin_role.permissions.add(create_right) admin_role.permissions.add(read_right) admin_role.permissions.add(update_right) admin_role.permissions.add(delete_right) class Migration(migrations.Migration): dependencies = [ ("resources", "0025_projectproperty"), ] operations = [migrations.RunPython(add_fixtures)] resources/models.py +18 −0 Original line number Diff line number Diff line Loading @@ -71,6 +71,24 @@ class Project(models.Model): return col class ProjectProperty(models.Model): """ Generic key/value store for the project. Store anything that is needed by the client application, like user prefs, field labels or other useful data. Be extra careful with access rights here. """ project = models.ForeignKey(Project, on_delete=models.CASCADE) key = models.CharField(max_length=64, null=False, blank=False) value = models.JSONField() class Meta: unique_together = (("project", "key"),) class Tag(models.Model): uid = models.TextField() ark = models.TextField(null=True, blank=True) Loading rpc/const.py +6 −0 Original line number Diff line number Diff line Loading @@ -15,6 +15,7 @@ NO_SUCH_RESOURCE = "no such resource" NO_SUCH_TAG = "no such tag" NO_SUCH_USER = "no such user" NO_PROJECT_ROOT = "project has no root" NO_SUCH_PROJECT_PROPERTY = "project has no such property" NOT_A_FILE = "not a file" PERM_COLLECTION_CREATE = "collection.create" PERM_COLLECTION_DELETE = "collection.delete" Loading @@ -34,6 +35,10 @@ PERM_RESOURCE_READ = "resource.read" PERM_RESOURCE_UPDATE = "resource.update" PERM_ROLE_READ = "role.read" PERM_TAG_DELETE = "tag.delete" PERM_PROJECT_PROPERTY_CREATE = "project_property.create" PERM_PROJECT_PROPERTY_READ = "project_property.read" PERM_PROJECT_PROPERTY_UPDATE = "project_property.update" PERM_PROJECT_PROPERTY_DELETE = "project_property.delete" ROOT_COLLECTION_TITLE = "root" SEARCH_TERMS_LIMIT = 10 SUPERUSER_NEEDED = "only for superusers" Loading @@ -41,3 +46,4 @@ TOO_MANY_SEARCH_TERMS = "Too many search terms." WRONG_ARGUMENT = "wrong argument" NO_FOOTGUNS = "Can't shoot your own foot" PROJECT_MISMATCH = "Can't mix items from different collections" INVALID_PROPERTY_KEY = "invalid property key" rpc/methods.py +84 −0 Original line number Diff line number Diff line Loading @@ -14,6 +14,7 @@ from resources.models import ( Role, Permission, APIKey, ProjectProperty, ) from django.contrib.auth.models import User from django.db.models import QuerySet Loading Loading @@ -2666,3 +2667,86 @@ def public_collections(user: User, project_id: int) -> List[dict]: for col in Collection.objects.filter(public_access=True, project_id=project_id): data.append(serializers.collection(col, cache=serializer_cache)) return data @_log_call @_rpc_groups(["Projects"]) def set_project_property( user: User, project_id: int, property_key: str, property_value: dict ) -> dict: """ Set a property value to the project. property_key is NOT case sensitive, ie. "ProPertY" is the same as "pRoperTy" or "property". """ _check_project_permission(user, project_id, PERM_PROJECT_PROPERTY_CREATE) _check_project_permission(user, project_id, PERM_PROJECT_PROPERTY_UPDATE) property_key = str(property_key) property_key = property_key.strip() property_key = property_key.lower() if not property_key or len(property_key) > 64: raise ServiceException(INVALID_PROPERTY_KEY) prop, _ = ProjectProperty.objects.update_or_create( project_id=project_id, key=property_key, defaults={"value": property_value} ) return serializers.project_property(prop) @_log_call @_rpc_groups(["Projects"]) def delete_project_property(user: User, project_id: int, property_key: str) -> bool: """ Delete a property from the project. property_key is NOT case sensitive, ie. "ProPertY" is the same as "pRoperTy" or "property". """ _check_project_permission(user, project_id, PERM_PROJECT_PROPERTY_DELETE) property_key = str(property_key) property_key = property_key.strip() property_key = property_key.lower() if not property_key or len(property_key) > 64: raise ServiceException(INVALID_PROPERTY_KEY) prop = ProjectProperty.objects.filter( project_id=project_id, key=property_key ).first() if prop: prop.delete() return True raise ServiceException(NO_SUCH_PROJECT_PROPERTY) @_log_call @_rpc_groups(["Projects"]) def project_property(user: User, project_id: int, property_key: str) -> dict: """ Get a property value from the project. property_key is NOT case sensitive, ie. "ProPertY" is the same as "pRoperTy" or "property". Will raise an exception if property does not exist. """ _check_project_permission(user, project_id, PERM_PROJECT_PROPERTY_READ) property_key = str(property_key) property_key = property_key.strip() property_key = property_key.lower() if not property_key or len(property_key) > 64: raise ServiceException(INVALID_PROPERTY_KEY) prop = ProjectProperty.objects.filter( project_id=project_id, key=property_key ).first() if prop: return serializers.project_property(prop) raise ServiceException(NO_SUCH_PROJECT_PROPERTY) @_log_call @_rpc_groups(["Projects"]) def project_properties(user: User, project_id: int) -> List[dict]: """ Get ALL properties from a project. """ _check_project_permission(user, project_id, PERM_PROJECT_PROPERTY_READ) data = [] for prop in ProjectProperty.objects.filter(project_id=project_id): data.append(serializers.project_property(prop)) return data Loading
resources/migrations/0025_projectproperty.py 0 → 100644 +40 −0 Original line number Diff line number Diff line # Generated by Django 4.0 on 2022-01-21 10:23 from django.db import migrations, models import django.db.models.deletion class Migration(migrations.Migration): dependencies = [ ("resources", "0024_alter_metadata_options"), ] operations = [ migrations.CreateModel( name="ProjectProperty", fields=[ ( "id", models.AutoField( auto_created=True, primary_key=True, serialize=False, verbose_name="ID", ), ), ("key", models.CharField(max_length=64)), ("value", models.JSONField()), ( "project", models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, to="resources.project", ), ), ], options={ "unique_together": {("project", "key")}, }, ), ]
resources/migrations/0026_project_props_fixtures.py 0 → 100644 +35 −0 Original line number Diff line number Diff line # Generated by Django 4.0 on 2022-01-21 10:25 from django.db import migrations from resources import models def add_fixtures(*args): create_right, _ = models.Permission.objects.get_or_create( label="project_property.create" ) read_right, _ = models.Permission.objects.get_or_create( label="project_property.read" ) update_right, _ = models.Permission.objects.get_or_create( label="project_property.update" ) delete_right, _ = models.Permission.objects.get_or_create( label="project_property.delete" ) for project in models.Project.objects.all(): admin_role = models.Role.objects.filter(project=project, label="admin").first() if admin_role: admin_role.permissions.add(create_right) admin_role.permissions.add(read_right) admin_role.permissions.add(update_right) admin_role.permissions.add(delete_right) class Migration(migrations.Migration): dependencies = [ ("resources", "0025_projectproperty"), ] operations = [migrations.RunPython(add_fixtures)]
resources/models.py +18 −0 Original line number Diff line number Diff line Loading @@ -71,6 +71,24 @@ class Project(models.Model): return col class ProjectProperty(models.Model): """ Generic key/value store for the project. Store anything that is needed by the client application, like user prefs, field labels or other useful data. Be extra careful with access rights here. """ project = models.ForeignKey(Project, on_delete=models.CASCADE) key = models.CharField(max_length=64, null=False, blank=False) value = models.JSONField() class Meta: unique_together = (("project", "key"),) class Tag(models.Model): uid = models.TextField() ark = models.TextField(null=True, blank=True) Loading
rpc/const.py +6 −0 Original line number Diff line number Diff line Loading @@ -15,6 +15,7 @@ NO_SUCH_RESOURCE = "no such resource" NO_SUCH_TAG = "no such tag" NO_SUCH_USER = "no such user" NO_PROJECT_ROOT = "project has no root" NO_SUCH_PROJECT_PROPERTY = "project has no such property" NOT_A_FILE = "not a file" PERM_COLLECTION_CREATE = "collection.create" PERM_COLLECTION_DELETE = "collection.delete" Loading @@ -34,6 +35,10 @@ PERM_RESOURCE_READ = "resource.read" PERM_RESOURCE_UPDATE = "resource.update" PERM_ROLE_READ = "role.read" PERM_TAG_DELETE = "tag.delete" PERM_PROJECT_PROPERTY_CREATE = "project_property.create" PERM_PROJECT_PROPERTY_READ = "project_property.read" PERM_PROJECT_PROPERTY_UPDATE = "project_property.update" PERM_PROJECT_PROPERTY_DELETE = "project_property.delete" ROOT_COLLECTION_TITLE = "root" SEARCH_TERMS_LIMIT = 10 SUPERUSER_NEEDED = "only for superusers" Loading @@ -41,3 +46,4 @@ TOO_MANY_SEARCH_TERMS = "Too many search terms." WRONG_ARGUMENT = "wrong argument" NO_FOOTGUNS = "Can't shoot your own foot" PROJECT_MISMATCH = "Can't mix items from different collections" INVALID_PROPERTY_KEY = "invalid property key"
rpc/methods.py +84 −0 Original line number Diff line number Diff line Loading @@ -14,6 +14,7 @@ from resources.models import ( Role, Permission, APIKey, ProjectProperty, ) from django.contrib.auth.models import User from django.db.models import QuerySet Loading Loading @@ -2666,3 +2667,86 @@ def public_collections(user: User, project_id: int) -> List[dict]: for col in Collection.objects.filter(public_access=True, project_id=project_id): data.append(serializers.collection(col, cache=serializer_cache)) return data @_log_call @_rpc_groups(["Projects"]) def set_project_property( user: User, project_id: int, property_key: str, property_value: dict ) -> dict: """ Set a property value to the project. property_key is NOT case sensitive, ie. "ProPertY" is the same as "pRoperTy" or "property". """ _check_project_permission(user, project_id, PERM_PROJECT_PROPERTY_CREATE) _check_project_permission(user, project_id, PERM_PROJECT_PROPERTY_UPDATE) property_key = str(property_key) property_key = property_key.strip() property_key = property_key.lower() if not property_key or len(property_key) > 64: raise ServiceException(INVALID_PROPERTY_KEY) prop, _ = ProjectProperty.objects.update_or_create( project_id=project_id, key=property_key, defaults={"value": property_value} ) return serializers.project_property(prop) @_log_call @_rpc_groups(["Projects"]) def delete_project_property(user: User, project_id: int, property_key: str) -> bool: """ Delete a property from the project. property_key is NOT case sensitive, ie. "ProPertY" is the same as "pRoperTy" or "property". """ _check_project_permission(user, project_id, PERM_PROJECT_PROPERTY_DELETE) property_key = str(property_key) property_key = property_key.strip() property_key = property_key.lower() if not property_key or len(property_key) > 64: raise ServiceException(INVALID_PROPERTY_KEY) prop = ProjectProperty.objects.filter( project_id=project_id, key=property_key ).first() if prop: prop.delete() return True raise ServiceException(NO_SUCH_PROJECT_PROPERTY) @_log_call @_rpc_groups(["Projects"]) def project_property(user: User, project_id: int, property_key: str) -> dict: """ Get a property value from the project. property_key is NOT case sensitive, ie. "ProPertY" is the same as "pRoperTy" or "property". Will raise an exception if property does not exist. """ _check_project_permission(user, project_id, PERM_PROJECT_PROPERTY_READ) property_key = str(property_key) property_key = property_key.strip() property_key = property_key.lower() if not property_key or len(property_key) > 64: raise ServiceException(INVALID_PROPERTY_KEY) prop = ProjectProperty.objects.filter( project_id=project_id, key=property_key ).first() if prop: return serializers.project_property(prop) raise ServiceException(NO_SUCH_PROJECT_PROPERTY) @_log_call @_rpc_groups(["Projects"]) def project_properties(user: User, project_id: int) -> List[dict]: """ Get ALL properties from a project. """ _check_project_permission(user, project_id, PERM_PROJECT_PROPERTY_READ) data = [] for prop in ProjectProperty.objects.filter(project_id=project_id): data.append(serializers.project_property(prop)) return data