Browse Source

finished first version of the project creation

Dimitri Korsch 3 years ago
parent
commit
2fce5f060f

+ 4 - 2
backend/pycs_api/admin.py

@@ -1,4 +1,6 @@
 from django.contrib import admin
-from pycs_api.models import Model
+from pycs_api import models
 
-admin.site.register(Model)
+admin.site.register(models.Model)
+admin.site.register(models.Project)
+admin.site.register(models.LabelProvider)

+ 6 - 0
backend/pycs_api/models/label_provider.py

@@ -10,6 +10,12 @@ class LabelProvider(base.BaseModel):
 
     configuration_file = models.CharField(max_length=255, unique=True)
 
+
+    serializer_fields = base.BaseModel.serializer_fields + [
+        "description",
+    ]
+
+
     class Meta:
         unique_together = [
             "root_folder",

+ 0 - 2
backend/pycs_api/models/model.py

@@ -12,6 +12,4 @@ class Model(base.BaseModel):
 
     serializer_fields = base.BaseModel.serializer_fields + [
         "description",
-        "root_folder",
-        "supports_encoded",
     ]

+ 23 - 3
backend/pycs_api/models/project.py

@@ -1,11 +1,19 @@
+import uuid
+
+from django.conf import settings
 from django.contrib.auth.models import User
 from django.db import models
+
 from pycs_api.models import base
-from pycs_api.models.model import Model
 from pycs_api.models.label_provider import LabelProvider
+from pycs_api.models.model import Model
+
+def new_root_folder():
+    return f"{settings.PROJECTS_DIR}/{uuid.uuid4()}"
 
 class Project(base.BaseModel):
 
+
     user = models.ForeignKey(
         User,
         on_delete=models.CASCADE,
@@ -31,8 +39,20 @@ class Project(base.BaseModel):
         on_delete=models.SET_NULL
     )
 
-    root_folder = models.CharField(max_length=255, unique=True)
+    root_folder = models.CharField(max_length=255,
+        default=new_root_folder,
+        unique=True)
 
     data_folder = models.CharField(max_length=255)
 
-    external_data = models.BooleanField()
+    external_data = models.BooleanField(default=False)
+
+    serializer_fields = base.BaseModel.serializer_fields + [
+        "description",
+        "created",
+        "model_id",
+        "label_provider_id",
+        "data_folder",
+        "root_folder",
+    ]
+

+ 15 - 4
backend/pycs_api/serializers.py

@@ -1,10 +1,21 @@
 
 from rest_framework import serializers
-from pycs_api.models import Model
-
+from pycs_api import models
 
 class ModelSerializer(serializers.HyperlinkedModelSerializer):
 
     class Meta:
-        model = Model
-        fields = Model.serializer_fields
+        model = models.Model
+        fields = models.Model.serializer_fields
+
+class LabelProviderSerializer(serializers.HyperlinkedModelSerializer):
+
+    class Meta:
+        model = models.LabelProvider
+        fields = models.LabelProvider.serializer_fields
+
+class ProjectSerializer(serializers.HyperlinkedModelSerializer):
+
+    class Meta:
+        model = models.Project
+        fields = models.Project.serializer_fields

+ 12 - 12
backend/pycs_api/urls.py

@@ -27,21 +27,21 @@ urlpatterns = [
     path('api-token-refresh/',
         views.TokenRefreshView.as_view(), name="refresh-token"),
 
-    path('projects/',
-        views.ProjectView.as_view(), name="projects"),
+    # path('projects/',
+    #     views.ProjectView.as_view(), name="projects"),
 
-    path('projects/<int:project_id>/remove',
-        views.ProjectView.remove, name="remove_project"),
+    # path('projects/<int:project_id>/remove',
+    #     views.ProjectView.remove, name="remove_project"),
 
-    path('projects/<int:project_id>/name',
-        views.ProjectView.edit_name, name="edit_project_name"),
+    # path('projects/<int:project_id>/name',
+    #     views.ProjectView.edit_name, name="edit_project_name"),
 
-    path('projects/<int:project_id>/description',
-        views.ProjectView.edit_description, name="edit_project_description"),
+    # path('projects/<int:project_id>/description',
+    #     views.ProjectView.edit_description, name="edit_project_description"),
 
-    path('projects/<int:project_id>/external_storage',
-        views.ProjectView.run_external_storage, name="execute_external_storage"),
+    # path('projects/<int:project_id>/external_storage',
+    #     views.ProjectView.run_external_storage, name="execute_external_storage"),
 
-    path('projects/<int:project_id>/label_provider',
-        views.ProjectView.run_label_provider, name="execute_label_provider"),
+    # path('projects/<int:project_id>/label_provider',
+    #     views.ProjectView.run_label_provider, name="execute_label_provider"),
 ]

+ 6 - 13
backend/pycs_api/views/__init__.py

@@ -1,11 +1,9 @@
-from pycs_api.views.project import ProjectView
+from pycs_api.views.project import ProjectViewSet
+from pycs_api.views.model import ModelViewSet
+from pycs_api.views.label_provider import LabelProviderViewSet
 
-from pycs_api import models
-from pycs_api import serializers
 
-from rest_framework import permissions
 from rest_framework import routers
-from rest_framework import viewsets
 from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
 from rest_framework_simplejwt.views import TokenObtainPairView as BaseTokenObtainView
 from rest_framework_simplejwt.views import TokenRefreshView as BaseTokenRefreshView
@@ -29,12 +27,7 @@ class TokenRefreshView(BaseTokenRefreshView):
     pass
 
 
-class ModelViewSet(viewsets.ModelViewSet):
-
-    queryset = models.Model.objects.all().order_by("id")
-    serializer_class = serializers.ModelSerializer
-    permission_classes = [permissions.IsAuthenticated]
-
-
 router = routers.DefaultRouter()
-router.register(r'models', ModelViewSet)
+router.register(r'model', ModelViewSet)
+router.register(r'label-provider', LabelProviderViewSet)
+router.register(r'project', ProjectViewSet)

+ 7 - 0
backend/pycs_api/views/base.py

@@ -1,8 +1,15 @@
 from django import http
 from django import views
+from rest_framework import permissions
+from rest_framework import viewsets
 
 class JsonResponseView(views.View):
 
     @classmethod
     def respond(cls, obj = {}):
         return http.JsonResponse(obj, safe=False)
+
+
+class BaseViewSet(viewsets.ModelViewSet):
+    permission_classes = []#permissions.IsAuthenticated]
+

+ 8 - 0
backend/pycs_api/views/label_provider.py

@@ -0,0 +1,8 @@
+from pycs_api.models import LabelProvider
+from pycs_api.serializers import LabelProviderSerializer
+from pycs_api.views.base import BaseViewSet
+
+class LabelProviderViewSet(BaseViewSet):
+
+    queryset = LabelProvider.objects.all().order_by("id")
+    serializer_class = LabelProviderSerializer

+ 8 - 0
backend/pycs_api/views/model.py

@@ -0,0 +1,8 @@
+from pycs_api.models import Model
+from pycs_api.serializers import ModelSerializer
+from pycs_api.views.base import BaseViewSet
+
+class ModelViewSet(BaseViewSet):
+
+    queryset = Model.objects.all().order_by("id")
+    serializer_class = ModelSerializer

+ 79 - 44
backend/pycs_api/views/project.py

@@ -1,61 +1,96 @@
 from django.views.decorators.http import require_POST
-from pycs_api.views.base import JsonResponseView
+from rest_framework import status
 from rest_framework.decorators import permission_classes
+from rest_framework.response import Response
 
+from pycs_api.models import Project
+from pycs_api.serializers import ProjectSerializer
+from pycs_api.views.base import BaseViewSet
 
-class ProjectView(JsonResponseView):
 
-    """
-        serves POST and GET for '/projects/'
+class ProjectViewSet(BaseViewSet):
 
-        staticmethods serve POST methods for
-            '/projects/<id>/name'
-            '/projects/<id>/description'
-            '/projects/<id>/remove'
-    """
+    queryset = Project.objects.all().order_by("id")
+    serializer_class = ProjectSerializer
 
-    http_method_names = ["get", "post"]
+    def list(self, request):
+        if request.user.is_anonymous:
+            return Response('Unauthorized',
+                status=status.HTTP_401_UNAUTHORIZED)
 
+        projects = request.user.projects.all()
+        serializer = self.get_serializer(projects, many=True)
+        return Response(serializer.data)
 
-    def get(self, request, *args, **kwargs):
-        """ lists projects """
-        return self.respond([
-            dict(name="Project 1", description="Desc1", ),
-            dict(name="Project 2", description="Desc2"),
-        ])
+    def create(self, request):
+        if request.user.is_anonymous:
+            return Response('Unauthorized',
+                status=status.HTTP_401_UNAUTHORIZED)
 
-    def post(self, request, *args, **kwargs):
-        """ creates a project """
-        return self.respond()
+        project = Project(
+            user=request.user,
+            **request.data
+        )
 
+        project.save()
+        serializer = self.get_serializer(project)
+        return Response(serializer.data)
 
-    @require_POST
-    @classmethod
-    def remove(cls, request, project_id: int):
-        """ removes a project """
-        return cls.respond()
 
-    @require_POST
-    @classmethod
-    def edit_name(cls, request, project_id: int):
-        """ edit project's name """
-        return cls.respond()
 
-    @require_POST
-    @classmethod
-    def edit_description(cls, request, project_id: int):
-        """ edit project's description """
-        return cls.respond()
+# class ProjectView(JsonResponseView):
 
-    @require_POST
-    @classmethod
-    def run_external_storage(cls, request, project_id: int):
-        """ runs external storage routine """
-        return cls.respond()
+#     """
+#         serves POST and GET for '/projects/'
 
-    @require_POST
-    @classmethod
-    def run_label_provider(cls, request, project_id: int):
-        """ runs label provider routine """
-        return cls.respond()
+#         staticmethods serve POST methods for
+#             '/projects/<id>/name'
+#             '/projects/<id>/description'
+#             '/projects/<id>/remove'
+#     """
+
+#     http_method_names = ["get", "post"]
+
+
+#     def get(self, request, *args, **kwargs):
+#         """ lists projects """
+#         return self.respond([
+#             dict(name="Project 1", description="Desc1", ),
+#             dict(name="Project 2", description="Desc2"),
+#         ])
+
+#     def post(self, request, *args, **kwargs):
+#         """ creates a project """
+#         return self.respond()
+
+
+#     @require_POST
+#     @classmethod
+#     def remove(cls, request, project_id: int):
+#         """ removes a project """
+#         return cls.respond()
+
+#     @require_POST
+#     @classmethod
+#     def edit_name(cls, request, project_id: int):
+#         """ edit project's name """
+#         return cls.respond()
+
+#     @require_POST
+#     @classmethod
+#     def edit_description(cls, request, project_id: int):
+#         """ edit project's description """
+#         return cls.respond()
+
+#     @require_POST
+#     @classmethod
+#     def run_external_storage(cls, request, project_id: int):
+#         """ runs external storage routine """
+#         return cls.respond()
+
+#     @require_POST
+#     @classmethod
+#     def run_label_provider(cls, request, project_id: int):
+#         """ runs label provider routine """
+#         return cls.respond()
 

+ 2 - 0
backend/pycs_backend/settings/media.py

@@ -28,3 +28,5 @@ STATIC_DIR = os.environ.get("PYCS_STATIC_DIR", BASE_DIR / "static")
 STATICFILES_DIRS = [
     STATIC_DIR,
 ]
+
+PROJECTS_DIR = os.environ.get("PYCS_PROJECTS_DIR", "projects")

+ 27 - 1
frontend/src/services/data.service.js

@@ -3,7 +3,33 @@ import api from "./api";
 class DataService {
 
   getModels() {
-    return api.get("/models/").then(
+    return api.get("/model/").then(
+      (response) => {
+        return response.data;
+      });
+  }
+
+  getLabelProviders() {
+    return api.get("/label-provider/").then(
+      (response) => {
+        return response.data;
+      });
+  }
+
+  getProjects() {
+    return api.get("/project/").then(
+      (response) => {
+        return response.data;
+      });
+  }
+
+  createProject(project) {
+    return api.post("/project/", {
+      'name': project.name,
+      'description': project.description,
+      'model_id': project.model.id,
+      'label_provider_id': project.labelProvider?.id,
+    }).then(
       (response) => {
         return response.data;
       });

+ 1 - 1
frontend/src/services/setupInterceptors.js

@@ -23,9 +23,9 @@ const setup = (store) => {
     },
     async (err) => {
       const originalConfig = err.config;
-      console.log("Access token expired!")
 
       if (originalConfig.url !== "/api-token/" && err.response) {
+        console.log("Access token expired!")
         // Access Token was expired
         if (err.response.status === 401 && !originalConfig._retry) {
 

+ 29 - 3
frontend/src/store/data.module.js

@@ -4,7 +4,9 @@ export const data = {
 
   namespaced: true,
   state: {
-    models: []
+    models: [],
+    labelProviders: [],
+    projects: [],
   },
 
   actions : {
@@ -13,12 +15,36 @@ export const data = {
         (models) => {
           commit("setModels", models)
         })
-    }
+    },
+
+    getLabelProviders({ commit }) {
+      DataService.getLabelProviders().then(
+        (labelProviders) => {
+          commit("setLabelProviders", labelProviders)
+        })
+    },
+
+    getProjects({ commit }) {
+      DataService.getProjects().then(
+        (projects) => {
+          commit("setProjects", projects)
+        })
+    },
+
   },
 
   mutations : {
+
     setModels(state, models) {
       state.models = models;
-    }
+    },
+
+    setLabelProviders(state, labelProviders) {
+      state.labelProviders = labelProviders;
+    },
+
+    setProjects(state, projects) {
+      state.projects = projects;
+    },
   },
 }

+ 1 - 1
frontend/src/store/models/project.js

@@ -3,6 +3,6 @@ export default class Project {
     this.name = '';
     this.description = '';
     this.model = null;
-    this.labels = null;
+    this.labelProvider = null;
   }
 }

+ 50 - 6
frontend/src/views/project/Create.vue

@@ -61,6 +61,23 @@
                 </v-select>
               </v-col>
             </v-row>
+            <v-row>
+              <v-col cols=12 sm=12>
+                <v-select
+                  v-model="project.labelProvider"
+                  name="labelProvider"
+                  label="Label Provider"
+                  :error-messages="labelProviderErrors"
+                  :items="labelProviders"
+                  :hint="labelProviderHint"
+                  item-text="name"
+                  item-value="id"
+                  persistent-hint
+                  return-object
+                  required>
+                </v-select>
+              </v-col>
+            </v-row>
             <v-row>
               <v-col cols=12 sm=12 class="d-flex align-end flex-column">
                 <v-btn
@@ -81,6 +98,7 @@
 
 <script>
   import Project from '@/store/models/project';
+  import DataService from '@/services/data.service';
   import { validationMixin } from 'vuelidate';
   import { required, minLength } from 'vuelidate/lib/validators';
   import { mapState } from 'vuex'
@@ -92,9 +110,10 @@
 
     validations: {
       project: {
-        name: { required, minLength: minLength(5) },
+        name: { required, minLength: minLength(3) },
         description: { required },
         model: { required },
+        labelProvider: {  },
       }
     },
 
@@ -108,7 +127,7 @@
       nameErrors () {
         const errors = [];
         if (!this.$v.project.name.$dirty) return errors
-        !this.$v.project.name.minLength && errors.push('Name must be at least 5 characters long')
+        !this.$v.project.name.minLength && errors.push('Name must be at least 3 characters long')
         !this.$v.project.name.required && errors.push('Name is required')
         return errors;
       },
@@ -123,15 +142,36 @@
       modelErrors () {
         const errors = [];
         if (!this.$v.project.model.$dirty) return errors
-        !this.$v.project.model.required && errors.push('Model selection is required')
+        !this.$v.project.model.required && errors.push('Please select a model.')
         return errors;
       },
 
+      labelProviderErrors () {
+        const errors = [];
+        if (!this.$v.project.labelProvider.$dirty) return errors
+        return errors;
+      },
 
       modelHint() {
         return this.project.model?.description
       },
 
+      labelProviderHint() {
+        return this.project.labelProvider?.description
+      },
+
+      labelProviders(){
+        let providers = this.data.labelProviders;
+        if (!providers)
+          providers = []
+        providers.unshift({
+          'name': "None",
+          'id': null,
+          "description": "Do not use any label provider"
+        });
+        return providers;
+      },
+
       ...mapState(['data'])
     },
 
@@ -139,9 +179,13 @@
       create() {
         this.$v.$touch();
 
-        if (!this.$v.$invalid) {
-          console.log("Creating project:", this.project);
-        }
+        if (this.$v.$invalid)
+          return
+
+        DataService.createProject(this.project).then(
+          () => {
+            this.$router.push({ name: 'projects' })
+        })
       }
     },
 

+ 6 - 8
frontend/src/views/project/List.vue

@@ -13,19 +13,18 @@
     </v-row>
     <v-row dense>
       <v-col
-        v-for="model in data.models"
-        :key="model.id"
+        v-for="project in data.projects"
+        :key="project.id"
         :cols=4
       >
         <v-card
           outlined elevation="2"
           max-width=450px class="mx-auto"
           >
-          <v-card-title>Name: {{model.name}}</v-card-title>
+          <v-card-title>Name: {{project.name}}</v-card-title>
           <v-card-text>
-              <p>Desc: {{model.description}}</p>
-              <p>Root: {{model.root_folder}}</p>
-              <p>Supports: {{model.supports_encoded}}</p>
+              <p>Desc: {{project.description}}</p>
+              <p>Root: {{project.root_folder}}</p>
           </v-card-text>
         </v-card>
       </v-col>
@@ -35,14 +34,13 @@
 </template>
 
 <script>
-  // import api from '@/services/api'
   import { mapState } from 'vuex'
 
   export default {
     name: 'Projects',
     computed: mapState(['data']),
     created () {
-      this.$store.dispatch('data/getModels')
+      this.$store.dispatch('data/getProjects')
     },
   }
 </script>