Repository: learningequality/kolibri

Issue:
Make it possible to create a new facility on existing Kolibri
<!--
Instructions:
 * Fill out the sections below, replace …'s with information about your issue
 * Use the 'preview' function above this text box to verify formatting before submitting
-->

## Observed behavior
<!--
Description of the behavior that was observed, including screenshots or other references when applicable
-->

On my existing Kolibri installation I want to be able to create a new facility. However, it seems that this option is not available.

![msedge_t7ZbSSUTkH](https://github.com/learningequality/kolibri/assets/148026/ff3778a5-1d06-422d-8677-65f6c8f8fc3a)

…


## Errors and logs
<!--
Relevant logs from:
 * the command line
 * ~/.kolibri/logs/kolibri.txt
 * the browser console

Please wrap errors in triple backticks for clean formatting like this:
```
01:10 info: something happened
01:12 error: something bad happened
```
-->

…

## Expected behavior
<!--
Description of what behavior was expected but did not occur
-->

…

## User-facing consequences
<!--
Implications and real-world consequences for learners, coaches, admins, and other users of the application
-->

…

## Steps to reproduce
<!--
Precise steps that someone else can follow in order to see this behavior
-->

…

## Context
* Kolibri version: 0.15.12
 * Operating system: Windows
 * Browser: Chrome

<!--
Tell us about your environment, including:
 * Kolibri version: 0.15.12
 * Operating system: Windows
 * Browser: Chrome
-->

…



Discussion:
Hi @lsolesen - thanks for this report, we have definitely had this feedback before. There are workarounds for now, the easiest being setting up Kolibri on another device and then importing that facility from there.

I am fairly sure we had designed this flow, so I will see if we can find those designs to add to this issue. It's possible that we could do this without adding any new strings, therefore it's possible we might be able to address this is in the scope of a patch release, but I'll have to investigate further.
Here's a design that will allow someone to create a new facility. There may be new strings in the buttons and snackbar in the mockup, but hopefully there are some existing ones to replace what's there.

![newfacility](https://github.com/learningequality/kolibri/assets/6668144/0417b915-0262-46f3-9290-4e2d7376db14)

Hello @rtibbles, Please assign me this issue.
Thanks @Jaspreet-singh-1032 - hopefully the design by @jtamiace above is sufficient for implementation here, but feel free to ask any follow up questions as needed!

Resolving patch:
diff --git a/kolibri/core/auth/api.py b/kolibri/core/auth/api.py
--- a/kolibri/core/auth/api.py
+++ b/kolibri/core/auth/api.py
@@ -60,6 +60,7 @@
 from .models import Membership
 from .models import Role
 from .serializers import ClassroomSerializer
+from .serializers import CreateFacilitySerializer
 from .serializers import ExtraFieldsSerializer
 from .serializers import FacilityDatasetSerializer
 from .serializers import FacilitySerializer
@@ -74,6 +75,7 @@
 from kolibri.core.auth.constants.demographics import NOT_SPECIFIED
 from kolibri.core.auth.permissions.general import _user_is_admin_for_own_facility
 from kolibri.core.auth.permissions.general import DenyAll
+from kolibri.core.device.permissions import IsSuperuser
 from kolibri.core.device.utils import allow_guest_access
 from kolibri.core.device.utils import allow_other_browsers_to_connect
 from kolibri.core.device.utils import valid_app_key_on_request
@@ -621,6 +623,13 @@ def annotate_queryset(self, queryset):
             )
         )
 
+    @decorators.action(methods=["post"], detail=False, permission_classes=[IsSuperuser])
+    def create_facility(self, request):
+        serializer = CreateFacilitySerializer(data=request.data)
+        serializer.is_valid(raise_exception=True)
+        serializer.save()
+        return Response()
+
 
 class PublicFacilityViewSet(viewsets.ReadOnlyModelViewSet):
     queryset = Facility.objects.all()
diff --git a/kolibri/core/auth/serializers.py b/kolibri/core/auth/serializers.py
--- a/kolibri/core/auth/serializers.py
+++ b/kolibri/core/auth/serializers.py
@@ -2,10 +2,15 @@
 from __future__ import print_function
 from __future__ import unicode_literals
 
+import logging
+
 from django.core.validators import MinLengthValidator
+from django.db import transaction
 from rest_framework import serializers
+from rest_framework.exceptions import ParseError
 from rest_framework.validators import UniqueTogetherValidator
 
+from .constants import facility_presets
 from .errors import IncompatibleDeviceSettingError
 from .errors import InvalidCollectionHierarchy
 from .errors import InvalidMembershipError
@@ -20,6 +25,9 @@
 from kolibri.core.auth.constants.demographics import NOT_SPECIFIED
 
 
+logger = logging.getLogger(__name__)
+
+
 class RoleSerializer(serializers.ModelSerializer):
     class Meta:
         model = Role
@@ -137,6 +145,27 @@ class Meta:
         fields = ("id", "name")
 
 
+class CreateFacilitySerializer(serializers.ModelSerializer):
+    preset = serializers.ChoiceField(choices=facility_presets.choices)
+
+    class Meta:
+        model = Facility
+        fields = ("id", "name", "preset")
+
+    def create(self, validated_data):
+        preset = validated_data.get("preset")
+        name = validated_data.get("name")
+        with transaction.atomic():
+            try:
+                facility_dataset = FacilityDataset.objects.create(preset=preset)
+                facility = Facility.objects.create(name=name, dataset=facility_dataset)
+                facility.dataset.reset_to_default_settings(preset)
+            except Exception as e:
+                logger.error("Error occured while creating facility: %s", str(e))
+                raise ParseError("Error occured while creating facility")
+        return facility
+
+
 class PublicFacilitySerializer(serializers.ModelSerializer):
     learner_can_login_with_no_password = serializers.SerializerMethodField()
     learner_can_sign_up = serializers.SerializerMethodField()


Test patch: