From d752da99273e726facc67971245cb95eb10d76e6 Mon Sep 17 00:00:00 2001 From: MeisterSeSe <105605891+MeisterSeSe@users.noreply.github.com> Date: Thu, 19 Oct 2023 14:44:28 +0200 Subject: [PATCH 01/14] Refactor: Adapted Multiple Upload Card --- .../upload_cards/file_create/Multiple.vue | 39 ++++++++----------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/frontendVue3/src/components/upload_cards/file_create/Multiple.vue b/frontendVue3/src/components/upload_cards/file_create/Multiple.vue index 15cdd40c..8ba2f7fd 100644 --- a/frontendVue3/src/components/upload_cards/file_create/Multiple.vue +++ b/frontendVue3/src/components/upload_cards/file_create/Multiple.vue @@ -25,21 +25,21 @@ show-size > - - - - + + + + + @@ -161,7 +161,7 @@ let formData = reactive({ label: '', description: '', files: null, - license: null, + license: "CC BY - SA 4.0 DEED", family: null, version: '', tags: [], @@ -174,11 +174,6 @@ const showDetails = ref(false); let fileRules = [(v) => !!v || 'File is required']; -let { licenses } = storeToRefs(fileStore); -let licenseRules = [(v) => !!v || 'License is required']; - -let familyRules = [(v) => !!v || 'Family is required']; - let checkboxRules = [(v) => !!v || 'Checkbox must be checked']; From 5899183ca9081e6a827bd1fd6690f23db52775d2 Mon Sep 17 00:00:00 2001 From: MeisterSeSe <105605891+MeisterSeSe@users.noreply.github.com> Date: Thu, 19 Oct 2023 14:44:35 +0200 Subject: [PATCH 02/14] Refactor: Adapted Single Upload Card --- .../upload_cards/file_create/Single.vue | 82 ++++++++++--------- 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/frontendVue3/src/components/upload_cards/file_create/Single.vue b/frontendVue3/src/components/upload_cards/file_create/Single.vue index ae716572..dd000493 100644 --- a/frontendVue3/src/components/upload_cards/file_create/Single.vue +++ b/frontendVue3/src/components/upload_cards/file_create/Single.vue @@ -2,6 +2,20 @@ + + + - - - - - - - + + + + + @@ -67,7 +68,6 @@ item-title="label" item-value="id" :required="true" - :rules="familyRules" variant="outlined" density="comfortable" hint="Add to or create new family" @@ -176,7 +176,7 @@ + + From 0b913b4f430d6de603052f005b80cfc381092faf Mon Sep 17 00:00:00 2001 From: MeisterSeSe <105605891+MeisterSeSe@users.noreply.github.com> Date: Thu, 19 Oct 2023 14:44:44 +0200 Subject: [PATCH 03/14] Refactor: Adapted Zip Upload Card --- .../upload_cards/file_create/Zip.vue | 71 +++++++++---------- 1 file changed, 35 insertions(+), 36 deletions(-) diff --git a/frontendVue3/src/components/upload_cards/file_create/Zip.vue b/frontendVue3/src/components/upload_cards/file_create/Zip.vue index 22c56dab..9cce6ed5 100644 --- a/frontendVue3/src/components/upload_cards/file_create/Zip.vue +++ b/frontendVue3/src/components/upload_cards/file_create/Zip.vue @@ -2,6 +2,20 @@ + + + + - - - - - - + + + + @@ -126,7 +127,7 @@ !!v || 'Label is required']; let descriptionRules = [ - (v) => !!v || 'Description is required', (v) => v.length <= 250 || 'Max 250 characters please', ]; let fileRules = [(v) => !!v || 'File is required']; -let { licenses, tags, myOwnTags } = storeToRefs(fileStore); -let licenseRules = [(v) => !!v || 'License is required']; +let { tags, myOwnTags } = storeToRefs(fileStore); -let familyRules = [(v) => !!v || 'Family is required']; let checkboxRules = [(v) => !!v || 'Checkbox must be checked']; -//let versionRules = [(v) => !!v || 'Version is required']; - let addTagMenu = ref(false); let addFamilyMenu = ref(false); +const redirectToLicensePage = () => { + window.open('https://creativecommons.org/licenses/by-sa/4.0/deed.de', '_blank'); +}; From e21e4d2af76e1290dd4a1b940d99d5e975bac7e9 Mon Sep 17 00:00:00 2001 From: MeisterSeSe <105605891+MeisterSeSe@users.noreply.github.com> Date: Thu, 19 Oct 2023 14:45:09 +0200 Subject: [PATCH 04/14] Feat: Implemented DefaultLicense --- .../upload_cards/file_create/ActionButtons.vue | 11 ++++++----- frontendVue3/src/store/file.js | 2 ++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/frontendVue3/src/components/upload_cards/file_create/ActionButtons.vue b/frontendVue3/src/components/upload_cards/file_create/ActionButtons.vue index b2e8ee82..0d35ca8c 100644 --- a/frontendVue3/src/components/upload_cards/file_create/ActionButtons.vue +++ b/frontendVue3/src/components/upload_cards/file_create/ActionButtons.vue @@ -24,11 +24,12 @@ From 282265a8964a33375955114b8cd3ec669e64c176 Mon Sep 17 00:00:00 2001 From: MeisterSeSe <105605891+MeisterSeSe@users.noreply.github.com> Date: Fri, 20 Oct 2023 10:45:09 +0200 Subject: [PATCH 09/14] Fix: Added TagsFIlter for all Views --- .../src/components/FeatureModelTable.vue | 16 +++++++++++++--- frontendVue3/src/views/HistoryDetail.vue | 12 ++++++------ frontendVue3/src/views/Home.vue | 5 +++-- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/frontendVue3/src/components/FeatureModelTable.vue b/frontendVue3/src/components/FeatureModelTable.vue index a6c281e7..3e5f3faf 100644 --- a/frontendVue3/src/components/FeatureModelTable.vue +++ b/frontendVue3/src/components/FeatureModelTable.vue @@ -21,20 +21,21 @@ vertical > - + > {{ new Date(item.raw.uploaded_at).toLocaleString('en-US') }} - + @@ -289,6 +290,15 @@ const createDialog = ref(false); const checkLocalStorage = computed(() => { return !!localStorage.featureModelData; }); +const noDataMessage = computed(() => { + // Wenn entweder die Suche oder die Tags aktiviert sind und keine passenden Elemente gefunden wurden + if ((search.value || selectedTags.value.length > 0) && filteredItems.value.length === 0) { + return "No Feature Model matches the search criteria."; + } else { + // Wenn weder Suche noch Tags aktiviert sind oder passende Elemente gefunden wurden + return props.noDataText; + } +}); const filteredItems = computed(() => { // Wenn die Suche leer ist, zeige alle Elemente if (!search.value && selectedTags.value.length === 0) { diff --git a/frontendVue3/src/views/HistoryDetail.vue b/frontendVue3/src/views/HistoryDetail.vue index 2ee73f46..1805e2b4 100644 --- a/frontendVue3/src/views/HistoryDetail.vue +++ b/frontendVue3/src/views/HistoryDetail.vue @@ -221,7 +221,7 @@ :no-data-text="`No feature models in ${family.label} yet`" :addable="false" headline="Feature Models of Family" - /> + :available-tags="tags"/> @@ -235,16 +235,15 @@ import {busyBoxConfigs} from "@/assets/busyBoxAnalyzeExample"; import {useDisplay, useTheme} from "vuetify"; import {VSkeletonLoader} from 'vuetify/labs/VSkeletonLoader' import LineChart from '@/components/Charts/LineChart.vue'; - - - - - +import {useFileStore} from "@/store/file"; +import {storeToRefs} from "pinia"; const breakpoints = useDisplay(); const theme = useTheme(); const route = useRoute(); const API_URL = import.meta.env.VITE_APP_DOMAIN; +const fileStore = useFileStore(); +const { tags } = storeToRefs(useFileStore()); const family = ref({}); const files = ref([]); @@ -364,5 +363,6 @@ function onElementHover(elem) { onMounted(async () => { await getFamily(); await fetchFeatureModelOfFamily(); + fileStore.fetchTags(); }); diff --git a/frontendVue3/src/views/Home.vue b/frontendVue3/src/views/Home.vue index 470e38dc..ddf09111 100644 --- a/frontendVue3/src/views/Home.vue +++ b/frontendVue3/src/views/Home.vue @@ -8,6 +8,7 @@ @@ -37,7 +38,7 @@ import { onMounted, ref } from 'vue'; import { storeToRefs } from 'pinia'; import { useFileStore } from '@/store/file'; -const { confirmedFeatureModels } = storeToRefs(useFileStore()); +const { confirmedFeatureModels, tags } = storeToRefs(useFileStore()); const fileStore = useFileStore(); const search = ''; @@ -79,7 +80,6 @@ const defaultItem = { }; const licenses = []; const families = []; -const tags = []; const check1 = false; const check2 = false; const check3 = false; @@ -127,5 +127,6 @@ const tutorialSteps = [ onMounted(() => { fileStore.fetchConfirmedFeatureModels(); + fileStore.fetchTags(); }); From 90a1395b9753ff6c720109c62bd8e2eb6279bf9a Mon Sep 17 00:00:00 2001 From: MeisterSeSe <105605891+MeisterSeSe@users.noreply.github.com> Date: Fri, 20 Oct 2023 14:19:09 +0200 Subject: [PATCH 10/14] Fix: Removed Register Button --- frontendVue3/src/components/Navbar.vue | 7 ------- 1 file changed, 7 deletions(-) diff --git a/frontendVue3/src/components/Navbar.vue b/frontendVue3/src/components/Navbar.vue index 45e4bcea..f6fab250 100644 --- a/frontendVue3/src/components/Navbar.vue +++ b/frontendVue3/src/components/Navbar.vue @@ -85,13 +85,6 @@ >
Admin
- - Register - Date: Fri, 27 Oct 2023 12:57:03 +0200 Subject: [PATCH 11/14] Feat: Added Private Field for File Model --- .../fileupload/migrations/0014_file_private.py | 18 ++++++++++++++++++ backend/core/fileupload/models.py | 6 +++++- 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 backend/core/fileupload/migrations/0014_file_private.py diff --git a/backend/core/fileupload/migrations/0014_file_private.py b/backend/core/fileupload/migrations/0014_file_private.py new file mode 100644 index 00000000..bb4070f8 --- /dev/null +++ b/backend/core/fileupload/migrations/0014_file_private.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.2 on 2023-10-26 07:29 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core_fileupload', '0013_file_private_alter_file_family_alter_file_version'), + ] + + operations = [ + migrations.AddField( + model_name='file', + name='private', + field=models.BooleanField(default=False), + ), + ] diff --git a/backend/core/fileupload/models.py b/backend/core/fileupload/models.py index 1b1eec76..0bf48814 100644 --- a/backend/core/fileupload/models.py +++ b/backend/core/fileupload/models.py @@ -119,8 +119,9 @@ def save_file(self, local_file, **kwargs): raise TypeError("Tags is not set") family = kwargs.get("family", None) version = kwargs.get("version", None) + private = kwargs.get("private", None) # get license from id - if kwargs.get("license", None) is None: + if kwargs.get("license", None) is None and kwargs.get("private") is None: raise TypeError("License not set!") file = self.model(**kwargs) @@ -176,6 +177,9 @@ class File(models.Model): ) # indicates if the user confirmed the upload slug = models.SlugField(null=True) confirmation_token = models.CharField(default="", max_length=255) + private = models.BooleanField( + default=False + ) def __str__(self): # do not change that From 482c754783a3a9c86418f436ffd24df4b4f433e5 Mon Sep 17 00:00:00 2001 From: MeisterSeSe <105605891+MeisterSeSe@users.noreply.github.com> Date: Fri, 27 Oct 2023 12:57:44 +0200 Subject: [PATCH 12/14] Feat: Added new ViewSets for PrivateFileUpload --- backend/core/fileupload/viewsets.py | 105 +++++++++++++++++++++++----- backend/core/routers.py | 4 +- 2 files changed, 92 insertions(+), 17 deletions(-) diff --git a/backend/core/fileupload/viewsets.py b/backend/core/fileupload/viewsets.py index 037611b8..4e7cf4a1 100644 --- a/backend/core/fileupload/viewsets.py +++ b/backend/core/fileupload/viewsets.py @@ -180,7 +180,6 @@ def check_family(self, request, family_id): return family and family.owner == request.user return True - def check_tags(self, request, tag_ids): for tag_id in tag_ids: tag = Tag.objects.get(pk=tag_id) @@ -204,21 +203,72 @@ def schedule_analysis(self, fs): analysis_result.save() -class BulkUploadApiView(UploadApiView): +class PrivateFileUploadApiView(APIView): + permission_classes = [permissions.IsAuthenticated] + def post(self, request, format=None): if not request.data["files"]: return Response( - {"files": "This field may not be blank."}, - status.HTTP_400_BAD_REQUEST, - ) + {"files": "This field may not be blank."}, + status.HTTP_400_BAD_REQUEST, + ) try: files = json.loads(request.data["files"]) except: return Response( - {"files": "This field must contain JSON."}, - status.HTTP_400_BAD_REQUEST, + {"files": "This field must contain JSON."}, + status.HTTP_400_BAD_REQUEST, + ) + for uploaded_file in files: + label = uploaded_file["label"] + description = uploaded_file["description"] + file = request.FILES[uploaded_file["file"]] + license = uploaded_file["license"] + tags = uploaded_file["tags"] + + if not label or not file: + return Response( + {"message": "Missing required fields"}, + status=status.HTTP_400_BAD_REQUEST, ) + validated_data = QueryDict("", mutable=True) + validated_data["label"] = label + validated_data["description"] = description + validated_data.setlist("tags", tags) + validated_data["local_file"] = file + validated_data["private"] = True + validated_data["license"] = license + + fs = FilesSerializer(data=validated_data) + if fs.is_valid(): + fs.save(owner=request.user) + return Response( + {"message": "File uploaded successfully"}, + status=status.HTTP_201_CREATED, + ) + else: + return Response( + {"message": "Invalid file format"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + +class BulkUploadApiView(UploadApiView): + def post(self, request, format=None): + if not request.data["files"]: + return Response( + {"files": "This field may not be blank."}, + status.HTTP_400_BAD_REQUEST, + ) + try: + files = json.loads(request.data["files"]) + except: + return Response( + {"files": "This field must contain JSON."}, + status.HTTP_400_BAD_REQUEST, + ) + serializers = [] confirmation_token = generate_random_string(30) @@ -270,16 +320,16 @@ class ZipUploadApiView(UploadApiView): def post(self, request, format=None): if not request.data["files"]: return Response( - {"files": "This field may not be blank."}, - status.HTTP_400_BAD_REQUEST, - ) + {"files": "This field may not be blank."}, + status.HTTP_400_BAD_REQUEST, + ) try: file_data = json.loads(request.data["files"]) except: return Response( - {"files": "This field must contain JSON."}, - status.HTTP_400_BAD_REQUEST, - ) + {"files": "This field must contain JSON."}, + status.HTTP_400_BAD_REQUEST, + ) label = file_data["label"] description = file_data["description"] @@ -305,7 +355,7 @@ def post(self, request, format=None): validated_data["label"] = label validated_data["description"] = description validated_data["license"] = license - validated_data["version"] = f"{i+1}.0.0" + validated_data["version"] = f"{i + 1}.0.0" validated_data["family"] = family validated_data.setlist("tags", tags) validated_data["local_file"] = local_file @@ -411,6 +461,29 @@ def retrieve(self, request, *args, **kwargs): return Response(anonymized_file) +class PrivateFileViewSet(viewsets.ModelViewSet): + queryset = File.objects.filter(private=True) + serializer_class = FilesSerializer + permission_classes = [IsAuthenticatedOrReadOnly, IsOwnerOrIsAdminOrReadOnly] + + def list(self, request, *args, **kwargs): + queryset = self.queryset.filter(owner=request.user) + serializer = self.serializer_class(queryset, many=True) + files = serializer.data + anonymized_files = [] + for file in files: + anonymized_file = anonymize_file(file, request) + anonymized_files.append(anonymized_file) + return Response(anonymized_files) + + def retrieve(self, request, *args, **kwargs): + instance = self.get_object() + if instance.owner != request.user: + return Response({"error": "Access denied"}, status=status.HTTP_403_FORBIDDEN) + serializer = self.serializer_class(instance) + return Response(serializer.data) + + class FamiliesViewSet( viewsets.GenericViewSet, mixins.ListModelMixin, @@ -527,6 +600,7 @@ def retrieve(self, request, *args, **kwargs): def perform_create(self, serializer): serializer.save(owner=self.request.user) + class AnalysesViewSet( viewsets.GenericViewSet, mixins.CreateModelMixin, @@ -534,7 +608,6 @@ class AnalysesViewSet( mixins.DestroyModelMixin, mixins.ListModelMixin, ): - queryset = Analysis.objects.all() serializer_class = AnalysesSerializer permission_classes = [ @@ -546,12 +619,12 @@ def list(self, request, **kwargs): analyses = AnalysesSerializer(queryset, many=True).data return Response(analyses) + class AnalysisResultsViewSet( viewsets.GenericViewSet, mixins.RetrieveModelMixin, mixins.ListModelMixin, ): - queryset = AnalysisResult.objects.all() serializer_class = AnalysisResultsSerializer diff --git a/backend/core/routers.py b/backend/core/routers.py index 617ce2fc..fb014092 100644 --- a/backend/core/routers.py +++ b/backend/core/routers.py @@ -13,7 +13,7 @@ ConfirmedFileViewSet, UnconfirmedFileViewSet, ConfirmFileUploadApiView, - DeleteFileUploadApiView, + DeleteFileUploadApiView, PrivateFileUploadApiView, PrivateFileViewSet, ) from core.user.viewsets import ActivateUserViewSet, UserInfoApiView from core.auth.viewsets import LoginViewSet, RegistrationViewSet, RefreshViewSet @@ -41,6 +41,7 @@ router.register( r"files/uploaded/confirmed", ConfirmedFileViewSet, basename="confirmed-files" ) +router.register(r'files/uploaded/private', PrivateFileViewSet, basename='private-files') router.register( r"files/uploaded/unconfirmed", UnconfirmedFileViewSet, basename="unconfirmed-files" ) @@ -55,6 +56,7 @@ *router.urls, path("bulk-upload/", BulkUploadApiView.as_view()), path("zip-upload/", ZipUploadApiView.as_view()), + path("private-upload/", PrivateFileUploadApiView.as_view()), re_path(r"files/uploaded/unconfirmed/confirm/(?P[\w\d]+)", ConfirmFileUploadApiView.as_view()), re_path(r"files/uploaded/unconfirmed/delete/(?P[\w\d]+)", DeleteFileUploadApiView.as_view()), path("api-auth/", include("rest_framework.urls")), From 1fc3561abc0d65e459197dc44bb13f86d32d9c91 Mon Sep 17 00:00:00 2001 From: MeisterSeSe <105605891+MeisterSeSe@users.noreply.github.com> Date: Fri, 27 Oct 2023 12:59:24 +0200 Subject: [PATCH 13/14] Feat: Implemented Possibility to upload private Feature from LocalStore --- .../src/components/FeatureModelTable.vue | 40 +++++++- .../file_create/ActionButtons.vue | 26 ++++- .../file_create/PrivateUpload.vue | 96 +++++++++++++++++++ frontendVue3/src/store/file.js | 31 ++++++ frontendVue3/src/views/Models.vue | 4 +- 5 files changed, 194 insertions(+), 3 deletions(-) create mode 100644 frontendVue3/src/components/upload_cards/file_create/PrivateUpload.vue diff --git a/frontendVue3/src/components/FeatureModelTable.vue b/frontendVue3/src/components/FeatureModelTable.vue index 3e5f3faf..afe787db 100644 --- a/frontendVue3/src/components/FeatureModelTable.vue +++ b/frontendVue3/src/components/FeatureModelTable.vue @@ -54,6 +54,12 @@ density="comfortable" > + + + See local storage + + + Upload from local storage