You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by ma...@apache.org on 2022/04/26 20:08:54 UTC

[airavata-django-portal] branch AIRAVATA-3562 updated (e02b4256 -> 793b8f39)

This is an automated email from the ASF dual-hosted git repository.

machristie pushed a change to branch AIRAVATA-3562
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git


    from e02b4256 AIRAVATA-3565 Switch user profile editor to vuex store
     new 493ed1a5 AIRAVATA-3565 WIP: Ext User Profile UI with load/saving text and single_choice values
     new 308744c4 AIRAVATA-3565 Ext User Profile UI with load/saving multi_choice values
     new 9f9760ea AIRAVATA-3565 Ext User Profile UI with load/saving user_agreement values
     new 0999c030 AIRAVATA-3565 Integrated link display for extended user profile fields
     new bdd614dc AIRAVATA-3565 Fix saving updates to extended user profile fields
     new 793b8f39 AIRAVATA-3565 Other field for single and multi choice fields

The 6 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../api/static/django_airavata_api/js/index.js     |   6 +
 .../js/models/ExtendedUserProfileField.js          |  39 +++++
 .../js/models/ExtendedUserProfileFieldChoice.js    |  13 ++
 .../js/models/ExtendedUserProfileFieldLink.js      |  16 ++
 .../js/models/ExtendedUserProfileValue.js          |  18 ++
 .../django_airavata_api/js/service_config.js       |  10 ++
 django_airavata/apps/auth/serializers.py           |   6 +-
 .../js/components/ExtendedUserProfileEditor.vue    |  46 +++++
 .../components/ExtendedUserProfileFieldEditor.vue  |  38 ++++
 .../ExtendedUserProfileMultiChoiceFieldEditor.vue  | 101 +++++++++++
 .../ExtendedUserProfileSingleChoiceFieldEditor.vue |  94 ++++++++++
 .../ExtendedUserProfileTextFieldEditor.vue         |  30 ++++
 ...ExtendedUserProfileUserAgreementFieldEditor.vue |  33 ++++
 .../js/components/UserProfileEditor.vue            |   2 +-
 .../js/containers/UserProfileContainer.vue         |  18 +-
 .../django_airavata_auth/js/entry-user-profile.js  |  10 +-
 .../static/django_airavata_auth/js/store/index.js  |   2 +
 .../js/store/modules/extendedUserProfile.js        | 195 +++++++++++++++++++++
 django_airavata/apps/auth/urls.py                  |   4 +-
 19 files changed, 671 insertions(+), 10 deletions(-)
 create mode 100644 django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileField.js
 create mode 100644 django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileFieldChoice.js
 create mode 100644 django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileFieldLink.js
 create mode 100644 django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileValue.js
 create mode 100644 django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileEditor.vue
 create mode 100644 django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileFieldEditor.vue
 create mode 100644 django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileMultiChoiceFieldEditor.vue
 create mode 100644 django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileSingleChoiceFieldEditor.vue
 create mode 100644 django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileTextFieldEditor.vue
 create mode 100644 django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileUserAgreementFieldEditor.vue


[airavata-django-portal] 01/06: AIRAVATA-3565 WIP: Ext User Profile UI with load/saving text and single_choice values

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch AIRAVATA-3562
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit 493ed1a58f7f88963d397c4bb6cbe2217b3e829e
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Fri Apr 15 17:23:22 2022 -0400

    AIRAVATA-3565 WIP: Ext User Profile UI with load/saving text and single_choice values
---
 .../api/static/django_airavata_api/js/index.js     |   6 ++
 .../js/models/ExtendedUserProfileField.js          |  39 ++++++++
 .../js/models/ExtendedUserProfileFieldChoice.js    |  13 +++
 .../js/models/ExtendedUserProfileFieldLink.js      |  16 +++
 .../js/models/ExtendedUserProfileValue.js          |  18 ++++
 .../django_airavata_api/js/service_config.js       |  10 ++
 django_airavata/apps/auth/serializers.py           |   2 +-
 .../js/components/ExtendedUserProfileEditor.vue    |  42 ++++++++
 .../ExtendedUserProfileSingleChoiceFieldEditor.vue |  45 +++++++++
 .../ExtendedUserProfileTextFieldEditor.vue         |  31 ++++++
 .../js/containers/UserProfileContainer.vue         |  12 ++-
 .../static/django_airavata_auth/js/store/index.js  |   2 +
 .../js/store/modules/extendedUserProfile.js        | 108 +++++++++++++++++++++
 django_airavata/apps/auth/urls.py                  |   4 +-
 14 files changed, 344 insertions(+), 4 deletions(-)

diff --git a/django_airavata/apps/api/static/django_airavata_api/js/index.js b/django_airavata/apps/api/static/django_airavata_api/js/index.js
index ca08f1d7..e177e42f 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/index.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/index.js
@@ -120,6 +120,12 @@ const services = {
   ExperimentStoragePathService: ServiceFactory.service(
     "ExperimentStoragePaths"
   ),
+  ExtendedUserProfileFieldService: ServiceFactory.service(
+    "ExtendedUserProfileFields"
+  ),
+  ExtendedUserProfileValueService: ServiceFactory.service(
+    "ExtendedUserProfileValues"
+  ),
   FullExperimentService: ServiceFactory.service("FullExperiments"),
   GatewayResourceProfileService: ServiceFactory.service(
     "GatewayResourceProfile"
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileField.js b/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileField.js
new file mode 100644
index 00000000..8b719db1
--- /dev/null
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileField.js
@@ -0,0 +1,39 @@
+import BaseModel from "./BaseModel";
+import ExtendedUserProfileFieldChoice from "./ExtendedUserProfileFieldChoice";
+import ExtendedUserProfileFieldLink from "./ExtendedUserProfileFieldLink";
+
+const FIELDS = [
+  "id",
+  "name",
+  "help_text",
+  "order",
+  {
+    name: "created_date",
+    type: "date",
+  },
+  {
+    name: "updated_date",
+    type: "date",
+  },
+  "field_type",
+  {
+    name: "links",
+    list: true,
+    type: ExtendedUserProfileFieldLink,
+  },
+  // For user_agreement type
+  "checkbox_label",
+  // For single_choice and multi_choice types
+  {
+    name: "choices",
+    list: true,
+    type: ExtendedUserProfileFieldChoice,
+  },
+  "other",
+];
+
+export default class ExtendedUserProfileField extends BaseModel {
+  constructor(data = {}) {
+    super(FIELDS, data);
+  }
+}
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileFieldChoice.js b/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileFieldChoice.js
new file mode 100644
index 00000000..edd0f8a0
--- /dev/null
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileFieldChoice.js
@@ -0,0 +1,13 @@
+import BaseModel from "./BaseModel";
+
+const FIELDS = [
+  "id",
+  "display_text",
+  "order",
+];
+
+export default class ExtendedUserProfileFieldChoice extends BaseModel {
+  constructor(data = {}) {
+    super(FIELDS, data);
+  }
+}
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileFieldLink.js b/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileFieldLink.js
new file mode 100644
index 00000000..5d52803a
--- /dev/null
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileFieldLink.js
@@ -0,0 +1,16 @@
+import BaseModel from "./BaseModel";
+
+const FIELDS = [
+  "id",
+  "label",
+  "url",
+  "order",
+  "display_link",
+  "display_inline",
+];
+
+export default class ExtendedUserProfileFieldLink extends BaseModel {
+  constructor(data = {}) {
+    super(FIELDS, data);
+  }
+}
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileValue.js b/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileValue.js
new file mode 100644
index 00000000..471faec7
--- /dev/null
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/ExtendedUserProfileValue.js
@@ -0,0 +1,18 @@
+import BaseModel from "./BaseModel";
+
+// TODO: do we need this?
+const FIELDS = [
+  "id",
+  "value_type",
+  "ext_user_profile_field",
+  "text_value",
+  "choices",
+  "other_value",
+  "agreement_value",
+];
+
+export default class ExtendedUserProfileValue extends BaseModel {
+  constructor(data = {}) {
+    super(FIELDS, data);
+  }
+}
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/service_config.js b/django_airavata/apps/api/static/django_airavata_api/js/service_config.js
index 152a3961..941ae12c 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/service_config.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/service_config.js
@@ -10,6 +10,7 @@ import ExperimentSearchFields from "./models/ExperimentSearchFields";
 import ExperimentStatistics from "./models/ExperimentStatistics";
 import ExperimentStoragePath from "./models/ExperimentStoragePath";
 import ExperimentSummary from "./models/ExperimentSummary";
+import ExtendedUserProfileField from "./models/ExtendedUserProfileField";
 import FullExperiment from "./models/FullExperiment";
 import GatewayResourceProfile from "./models/GatewayResourceProfile";
 import Group from "./models/Group";
@@ -237,6 +238,15 @@ export default {
       },
     },
   },
+  ExtendedUserProfileFields: {
+    url: "/auth/extended-user-profile-fields",
+    viewSet: true,
+    modelClass: ExtendedUserProfileField,
+  },
+  ExtendedUserProfileValues: {
+    url: "/auth/extended-user-profile-values",
+    viewSet: true,
+  },
   FullExperiments: {
     url: "/api/full-experiments",
     viewSet: [
diff --git a/django_airavata/apps/auth/serializers.py b/django_airavata/apps/auth/serializers.py
index 66dd54f6..61fc0239 100644
--- a/django_airavata/apps/auth/serializers.py
+++ b/django_airavata/apps/auth/serializers.py
@@ -222,7 +222,7 @@ class ExtendedUserProfileFieldSerializer(serializers.ModelSerializer):
 
 class ExtendedUserProfileValueSerializer(serializers.ModelSerializer):
     text_value = serializers.CharField(required=False, allow_blank=True)
-    choices = serializers.ListField(child=serializers.IntegerField(), required=False, allow_empty=False, min_length=1)
+    choices = serializers.ListField(child=serializers.IntegerField(), required=False)
     other_value = serializers.CharField(required=False, allow_blank=True)
     agreement_value = serializers.BooleanField(required=False)
 
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileEditor.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileEditor.vue
new file mode 100644
index 00000000..3b1cfea1
--- /dev/null
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileEditor.vue
@@ -0,0 +1,42 @@
+<template>
+  <b-card>
+    <template v-for="extendedUserProfileField in extendedUserProfileFields">
+      <component
+        :key="extendedUserProfileField.id"
+        :is="getEditor(extendedUserProfileField)"
+        :extended-user-profile-field="extendedUserProfileField"
+      />
+    </template>
+  </b-card>
+</template>
+
+<script>
+import { mapGetters } from "vuex";
+import ExtendedUserProfileSingleChoiceFieldEditor from "./ExtendedUserProfileSingleChoiceFieldEditor.vue";
+import ExtendedUserProfileTextFieldEditor from "./ExtendedUserProfileTextFieldEditor.vue";
+export default {
+  computed: {
+    ...mapGetters("extendedUserProfile", ["extendedUserProfileFields"]),
+  },
+  methods: {
+    getEditor(extendedUserProfileField) {
+      const fieldTypeEditors = {
+        text: ExtendedUserProfileTextFieldEditor,
+        single_choice: ExtendedUserProfileSingleChoiceFieldEditor,
+      };
+
+      if (extendedUserProfileField.field_type in fieldTypeEditors) {
+        return fieldTypeEditors[extendedUserProfileField.field_type];
+      } else {
+        // eslint-disable-next-line no-console
+        console.error(
+          "Unexpected field_type",
+          extendedUserProfileField.field_type
+        );
+      }
+    },
+  },
+};
+</script>
+
+<style></style>
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileSingleChoiceFieldEditor.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileSingleChoiceFieldEditor.vue
new file mode 100644
index 00000000..bf95019f
--- /dev/null
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileSingleChoiceFieldEditor.vue
@@ -0,0 +1,45 @@
+<template>
+  <b-form-group
+    :label="extendedUserProfileField.name"
+    :description="extendedUserProfileField.help_text"
+  >
+    <b-form-select v-model="value" :options="options"></b-form-select>
+  </b-form-group>
+</template>
+
+<script>
+import { mapGetters, mapMutations } from "vuex";
+export default {
+  props: ["extendedUserProfileField"],
+  computed: {
+    ...mapGetters("extendedUserProfile", ["getSingleChoiceValue"]),
+    value: {
+      get() {
+        return this.getSingleChoiceValue(this.extendedUserProfileField.id);
+      },
+      set(value) {
+        this.setSingleChoiceValue({
+          value,
+          id: this.extendedUserProfileField.id,
+        });
+      },
+    },
+    options() {
+      return this.extendedUserProfileField &&
+        this.extendedUserProfileField.choices
+        ? this.extendedUserProfileField.choices.map((choice) => {
+            return {
+              value: choice.id,
+              text: choice.display_text,
+            };
+          })
+        : [];
+    },
+  },
+  methods: {
+    ...mapMutations("extendedUserProfile", ["setSingleChoiceValue"]),
+  },
+};
+</script>
+
+<style></style>
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileTextFieldEditor.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileTextFieldEditor.vue
new file mode 100644
index 00000000..8f2b9989
--- /dev/null
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileTextFieldEditor.vue
@@ -0,0 +1,31 @@
+<template>
+  <b-form-group
+    :label="extendedUserProfileField.name"
+    :description="extendedUserProfileField.help_text"
+  >
+    <b-form-input v-model="value" />
+  </b-form-group>
+</template>
+
+<script>
+import { mapGetters, mapMutations } from 'vuex';
+export default {
+  props: ["extendedUserProfileField"],
+  computed: {
+    ...mapGetters('extendedUserProfile', ['getTextValue']),
+    value: {
+      get() {
+        return this.getTextValue(this.extendedUserProfileField.id);
+      },
+      set(value) {
+        this.setTextValue({value, id: this.extendedUserProfileField.id})
+      }
+    }
+  },
+  methods: {
+    ...mapMutations('extendedUserProfile', ['setTextValue']),
+  }
+};
+</script>
+
+<style></style>
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/containers/UserProfileContainer.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/containers/UserProfileContainer.vue
index 65499d58..4d44405f 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/containers/UserProfileContainer.vue
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/containers/UserProfileContainer.vue
@@ -21,6 +21,9 @@
       @save="onSave"
       @resend-email-verification="handleResendEmailVerification"
     />
+    <!-- TODO: include both forms in the same card -->
+    <!-- include extended-user-profile-editor if there are extendedUserProfileFields -->
+    <extended-user-profile-editor v-if="extendedUserProfileFields && extendedUserProfileFields.length > 0"/>
     <b-link
       v-if="user && user.complete"
       class="text-muted small"
@@ -34,12 +37,15 @@
 import UserProfileEditor from "../components/UserProfileEditor.vue";
 import { notifications } from "django-airavata-common-ui";
 import { mapActions, mapGetters } from "vuex";
+import ExtendedUserProfileEditor from '../components/ExtendedUserProfileEditor.vue';
 
 export default {
-  components: { UserProfileEditor },
+  components: { UserProfileEditor, ExtendedUserProfileEditor },
   name: "user-profile-container",
   async created() {
     await this.loadCurrentUser();
+    await this.loadExtendedUserProfileFields();
+    await this.loadExtendedUserProfileValues();
 
     const queryParams = new URLSearchParams(window.location.search);
     if (queryParams.has("code")) {
@@ -60,6 +66,7 @@ export default {
   },
   computed: {
     ...mapGetters("userProfile", ["user"]),
+    ...mapGetters("extendedUserProfile", ["extendedUserProfileFields"]),
   },
   methods: {
     ...mapActions("userProfile", [
@@ -68,7 +75,10 @@ export default {
       "updateUser",
       "resendEmailVerification",
     ]),
+    ...mapActions("extendedUserProfile", ["loadExtendedUserProfileFields", "loadExtendedUserProfileValues", "saveExtendedUserProfileValues"]),
     async onSave() {
+      // TODO: only save if both standard and extended user profiles are valid
+      this.saveExtendedUserProfileValues();
       if (this.$refs.userProfileEditor.valid) {
         await this.updateUser();
         notifications.NotificationList.add(
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/store/index.js b/django_airavata/apps/auth/static/django_airavata_auth/js/store/index.js
index bb13e8ac..8dd262a7 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/store/index.js
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/store/index.js
@@ -1,5 +1,6 @@
 import Vuex from "vuex";
 import userProfile from "./modules/userProfile";
+import extendedUserProfile from "./modules/extendedUserProfile";
 
 const debug = process.env.NODE_ENV !== "production";
 
@@ -8,6 +9,7 @@ function createStore(Vue) {
   return new Vuex.Store({
     modules: {
       userProfile,
+      extendedUserProfile,
     },
     strict: debug,
   });
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/store/modules/extendedUserProfile.js b/django_airavata/apps/auth/static/django_airavata_auth/js/store/modules/extendedUserProfile.js
index e69de29b..85c085d7 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/store/modules/extendedUserProfile.js
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/store/modules/extendedUserProfile.js
@@ -0,0 +1,108 @@
+import { services } from "django-airavata-api";
+
+const state = () => ({
+  extendedUserProfileFields: null,
+  extendedUserProfileValues: [],
+});
+
+const getters = {
+  extendedUserProfileFields: (state) => state.extendedUserProfileFields,
+  extendedUserProfileValues: (state) => state.extendedUserProfileValues,
+  getTextValue: (state) => (id) => {
+    const value = state.extendedUserProfileValues.find(
+      (v) => v.ext_user_profile_field === id
+    );
+    return value ? value.text_value : null;
+  },
+  getSingleChoiceValue: (state) => (id) => {
+    const value = state.extendedUserProfileValues.find(
+      (v) => v.ext_user_profile_field === id
+    );
+    if (value && value.choices && value.choices.length === 1) {
+      return value.choices[0];
+    } else {
+      return null;
+    }
+  },
+};
+
+const actions = {
+  async loadExtendedUserProfileFields({ commit }) {
+    const extendedUserProfileFields = await services.ExtendedUserProfileFieldService.list();
+    commit("setExtendedUserProfileFields", { extendedUserProfileFields });
+  },
+  async loadExtendedUserProfileValues({ commit }) {
+    const extendedUserProfileValues = await services.ExtendedUserProfileValueService.list();
+    commit("setExtendedUserProfileValues", { extendedUserProfileValues });
+  },
+  async saveExtendedUserProfileValues({ state, commit }) {
+    for (const value of state.extendedUserProfileValues) {
+      // Create or update each value
+      if (value.id) {
+        await services.ExtendedUserProfileValueService.update({
+          lookup: value.id,
+          data: value,
+        });
+      } else {
+        const extendedUserProfileValue = await services.ExtendedUserProfileValueService.create(
+          { data: value }
+        );
+        commit("updateExtendedUserProfileValue", { extendedUserProfileValue });
+      }
+    }
+  },
+};
+
+const mutations = {
+  setExtendedUserProfileFields(state, { extendedUserProfileFields }) {
+    state.extendedUserProfileFields = extendedUserProfileFields;
+  },
+  setExtendedUserProfileValues(state, { extendedUserProfileValues }) {
+    state.extendedUserProfileValues = extendedUserProfileValues;
+  },
+  setTextValue(state, { value, id }) {
+    const profileValue = state.extendedUserProfileValues.find(
+      (v) => v.ext_user_profile_field === id
+    );
+    if (profileValue) {
+      profileValue.text_value = value;
+    } else {
+      state.extendedUserProfileValues.push({
+        value_type: "text",
+        ext_user_profile_field: id,
+        text_value: value,
+      });
+    }
+  },
+  setSingleChoiceValue(state, { value, id }) {
+    const profileValue = state.extendedUserProfileValues.find(
+      (v) => v.ext_user_profile_field === id
+    );
+    if (profileValue) {
+      profileValue.choices = [value];
+      profileValue.other_value = "";
+    } else {
+      state.extendedUserProfileValues.push({
+        value_type: "single_choice",
+        ext_user_profile_field: id,
+        choices: [value],
+      });
+    }
+  },
+  updateExperimentInputValue(state, { extendedUserProfileValue }) {
+    const index = state.extendedUserProfileValues.findIndex(
+      (v) =>
+        v.ext_user_profile_field ===
+        extendedUserProfileValue.ext_user_profile_field
+    );
+    state.extendedUserProfileValues.splice(index, 1, extendedUserProfileValue);
+  },
+};
+
+export default {
+  namespaced: true,
+  state,
+  getters,
+  actions,
+  mutations,
+};
diff --git a/django_airavata/apps/auth/urls.py b/django_airavata/apps/auth/urls.py
index 8ca2f532..a7622718 100644
--- a/django_airavata/apps/auth/urls.py
+++ b/django_airavata/apps/auth/urls.py
@@ -7,8 +7,8 @@ from . import views
 
 router = routers.DefaultRouter()
 router.register(r'users', views.UserViewSet, basename='user')
-router.register(r'extended-user-profile-fields', views.ExtendedUserProfileFieldViewset, basename='extend-user-profile-field')
-router.register(r'extended-user-profile-values', views.ExtendedUserProfileValueViewset, basename='extend-user-profile-value')
+router.register(r'extended-user-profile-fields', views.ExtendedUserProfileFieldViewset, basename='extended-user-profile-field')
+router.register(r'extended-user-profile-values', views.ExtendedUserProfileValueViewset, basename='extended-user-profile-value')
 app_name = 'django_airavata_auth'
 urlpatterns = [
     re_path(r'^', include(router.urls)),


[airavata-django-portal] 02/06: AIRAVATA-3565 Ext User Profile UI with load/saving multi_choice values

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch AIRAVATA-3562
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit 308744c4002b09588c23fdd67ea220076f4404a7
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Tue Apr 26 13:26:37 2022 -0400

    AIRAVATA-3565 Ext User Profile UI with load/saving multi_choice values
---
 .../js/components/ExtendedUserProfileEditor.vue    |  2 +
 .../ExtendedUserProfileMultiChoiceFieldEditor.vue  | 49 ++++++++++++++++++++++
 .../js/store/modules/extendedUserProfile.js        | 25 +++++++++++
 3 files changed, 76 insertions(+)

diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileEditor.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileEditor.vue
index 3b1cfea1..052d2893 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileEditor.vue
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileEditor.vue
@@ -12,6 +12,7 @@
 
 <script>
 import { mapGetters } from "vuex";
+import ExtendedUserProfileMultiChoiceFieldEditor from "./ExtendedUserProfileMultiChoiceFieldEditor.vue";
 import ExtendedUserProfileSingleChoiceFieldEditor from "./ExtendedUserProfileSingleChoiceFieldEditor.vue";
 import ExtendedUserProfileTextFieldEditor from "./ExtendedUserProfileTextFieldEditor.vue";
 export default {
@@ -23,6 +24,7 @@ export default {
       const fieldTypeEditors = {
         text: ExtendedUserProfileTextFieldEditor,
         single_choice: ExtendedUserProfileSingleChoiceFieldEditor,
+        multi_choice: ExtendedUserProfileMultiChoiceFieldEditor,
       };
 
       if (extendedUserProfileField.field_type in fieldTypeEditors) {
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileMultiChoiceFieldEditor.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileMultiChoiceFieldEditor.vue
new file mode 100644
index 00000000..2a17d614
--- /dev/null
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileMultiChoiceFieldEditor.vue
@@ -0,0 +1,49 @@
+<template>
+  <b-form-group
+    :label="extendedUserProfileField.name"
+    :description="extendedUserProfileField.help_text"
+  >
+    <b-form-checkbox-group
+      v-model="value"
+      :options="options"
+      stacked
+    ></b-form-checkbox-group>
+  </b-form-group>
+</template>
+
+<script>
+import { mapGetters, mapMutations } from "vuex";
+export default {
+  props: ["extendedUserProfileField"],
+  computed: {
+    ...mapGetters("extendedUserProfile", ["getMultiChoiceValue"]),
+    value: {
+      get() {
+        return this.getMultiChoiceValue(this.extendedUserProfileField.id);
+      },
+      set(value) {
+        this.setMultiChoiceValue({
+          value,
+          id: this.extendedUserProfileField.id,
+        });
+      },
+    },
+    options() {
+      return this.extendedUserProfileField &&
+        this.extendedUserProfileField.choices
+        ? this.extendedUserProfileField.choices.map((choice) => {
+            return {
+              value: choice.id,
+              text: choice.display_text,
+            };
+          })
+        : [];
+    },
+  },
+  methods: {
+    ...mapMutations("extendedUserProfile", ["setMultiChoiceValue"]),
+  },
+};
+</script>
+
+<style></style>
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/store/modules/extendedUserProfile.js b/django_airavata/apps/auth/static/django_airavata_auth/js/store/modules/extendedUserProfile.js
index 85c085d7..e55c5353 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/store/modules/extendedUserProfile.js
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/store/modules/extendedUserProfile.js
@@ -24,6 +24,16 @@ const getters = {
       return null;
     }
   },
+  getMultiChoiceValue: (state) => (id) => {
+    const value = state.extendedUserProfileValues.find(
+      (v) => v.ext_user_profile_field === id
+    );
+    if (value && value.choices) {
+      return value.choices;
+    } else {
+      return null;
+    }
+  },
 };
 
 const actions = {
@@ -89,6 +99,21 @@ const mutations = {
       });
     }
   },
+  setMultiChoiceValue(state, { value, id }) {
+    const profileValue = state.extendedUserProfileValues.find(
+      (v) => v.ext_user_profile_field === id
+    );
+    if (profileValue) {
+      profileValue.choices = value;
+      profileValue.other_value = "";
+    } else {
+      state.extendedUserProfileValues.push({
+        value_type: "multi_choice",
+        ext_user_profile_field: id,
+        choices: value,
+      });
+    }
+  },
   updateExperimentInputValue(state, { extendedUserProfileValue }) {
     const index = state.extendedUserProfileValues.findIndex(
       (v) =>


[airavata-django-portal] 05/06: AIRAVATA-3565 Fix saving updates to extended user profile fields

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch AIRAVATA-3562
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit bdd614dc5f79b72e7fc3c3967699c00e35afed65
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Tue Apr 26 14:25:57 2022 -0400

    AIRAVATA-3565 Fix saving updates to extended user profile fields
---
 django_airavata/apps/auth/serializers.py | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/django_airavata/apps/auth/serializers.py b/django_airavata/apps/auth/serializers.py
index 61fc0239..7d3a61dc 100644
--- a/django_airavata/apps/auth/serializers.py
+++ b/django_airavata/apps/auth/serializers.py
@@ -189,6 +189,7 @@ class ExtendedUserProfileFieldSerializer(serializers.ModelSerializer):
                         id=choice_id,
                         defaults=choice,
                     )
+            instance.single_choice.save()
         elif instance.field_type == 'multi_choice':
             instance.multi_choice.other = validated_data.get('other', instance.multi_choice.other)
             choices = validated_data.pop('choices', None)
@@ -202,8 +203,10 @@ class ExtendedUserProfileFieldSerializer(serializers.ModelSerializer):
                         id=choice_id,
                         defaults=choice,
                     )
+            instance.multi_choice.save()
         elif instance.field_type == 'user_agreement':
             instance.user_agreement.checkbox_label = validated_data.pop('checkbox_label', instance.user_agreement.checkbox_label)
+            instance.user_agreement.save()
 
         # update links
         links = validated_data.pop('links', [])
@@ -211,6 +214,7 @@ class ExtendedUserProfileFieldSerializer(serializers.ModelSerializer):
         instance.links.exclude(id__in=link_ids).delete()
         for link in links:
             link_id = link.pop('id', None)
+            link['field'] = instance
             models.ExtendedUserProfileFieldLink.objects.update_or_create(
                 id=link_id,
                 defaults=link,


[airavata-django-portal] 03/06: AIRAVATA-3565 Ext User Profile UI with load/saving user_agreement values

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch AIRAVATA-3562
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit 9f9760ea6bca7a4cae9a59a624be26ea33cca580
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Tue Apr 26 13:38:44 2022 -0400

    AIRAVATA-3565 Ext User Profile UI with load/saving user_agreement values
---
 .../js/components/ExtendedUserProfileEditor.vue    |  2 ++
 ...ExtendedUserProfileUserAgreementFieldEditor.vue | 36 ++++++++++++++++++++++
 .../js/store/modules/extendedUserProfile.js        | 20 ++++++++++++
 3 files changed, 58 insertions(+)

diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileEditor.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileEditor.vue
index 052d2893..4ab87d89 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileEditor.vue
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileEditor.vue
@@ -15,6 +15,7 @@ import { mapGetters } from "vuex";
 import ExtendedUserProfileMultiChoiceFieldEditor from "./ExtendedUserProfileMultiChoiceFieldEditor.vue";
 import ExtendedUserProfileSingleChoiceFieldEditor from "./ExtendedUserProfileSingleChoiceFieldEditor.vue";
 import ExtendedUserProfileTextFieldEditor from "./ExtendedUserProfileTextFieldEditor.vue";
+import ExtendedUserProfileUserAgreementFieldEditor from './ExtendedUserProfileUserAgreementFieldEditor.vue';
 export default {
   computed: {
     ...mapGetters("extendedUserProfile", ["extendedUserProfileFields"]),
@@ -25,6 +26,7 @@ export default {
         text: ExtendedUserProfileTextFieldEditor,
         single_choice: ExtendedUserProfileSingleChoiceFieldEditor,
         multi_choice: ExtendedUserProfileMultiChoiceFieldEditor,
+        user_agreement: ExtendedUserProfileUserAgreementFieldEditor,
       };
 
       if (extendedUserProfileField.field_type in fieldTypeEditors) {
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileUserAgreementFieldEditor.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileUserAgreementFieldEditor.vue
new file mode 100644
index 00000000..4857c4e3
--- /dev/null
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileUserAgreementFieldEditor.vue
@@ -0,0 +1,36 @@
+<template>
+  <b-form-group
+    :label="extendedUserProfileField.name"
+    :description="extendedUserProfileField.help_text"
+  >
+    <b-form-checkbox v-model="value" :unchecked-value="false">
+      {{ extendedUserProfileField.checkbox_label }}
+    </b-form-checkbox>
+  </b-form-group>
+</template>
+
+<script>
+import { mapGetters, mapMutations } from "vuex";
+export default {
+  props: ["extendedUserProfileField"],
+  computed: {
+    ...mapGetters("extendedUserProfile", ["getUserAgreementValue"]),
+    value: {
+      get() {
+        return this.getUserAgreementValue(this.extendedUserProfileField.id);
+      },
+      set(value) {
+        this.setUserAgreementValue({
+          value,
+          id: this.extendedUserProfileField.id,
+        });
+      },
+    },
+  },
+  methods: {
+    ...mapMutations("extendedUserProfile", ["setUserAgreementValue"]),
+  },
+};
+</script>
+
+<style></style>
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/store/modules/extendedUserProfile.js b/django_airavata/apps/auth/static/django_airavata_auth/js/store/modules/extendedUserProfile.js
index e55c5353..b58e9ca4 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/store/modules/extendedUserProfile.js
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/store/modules/extendedUserProfile.js
@@ -34,6 +34,12 @@ const getters = {
       return null;
     }
   },
+  getUserAgreementValue: (state) => (id) => {
+    const value = state.extendedUserProfileValues.find(
+      (v) => v.ext_user_profile_field === id
+    );
+    return value && value.agreement_value;
+  },
 };
 
 const actions = {
@@ -114,6 +120,20 @@ const mutations = {
       });
     }
   },
+  setUserAgreementValue(state, { value, id }) {
+    const profileValue = state.extendedUserProfileValues.find(
+      (v) => v.ext_user_profile_field === id
+    );
+    if (profileValue) {
+      profileValue.agreement_value = value;
+    } else {
+      state.extendedUserProfileValues.push({
+        value_type: "user_agreement",
+        ext_user_profile_field: id,
+        agreement_value: value,
+      });
+    }
+  },
   updateExperimentInputValue(state, { extendedUserProfileValue }) {
     const index = state.extendedUserProfileValues.findIndex(
       (v) =>


[airavata-django-portal] 04/06: AIRAVATA-3565 Integrated link display for extended user profile fields

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch AIRAVATA-3562
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit 0999c030432bf1cfc4bb94199a60ccd616426221
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Tue Apr 26 14:25:34 2022 -0400

    AIRAVATA-3565 Integrated link display for extended user profile fields
---
 .../js/components/ExtendedUserProfileEditor.vue    |  2 +-
 .../components/ExtendedUserProfileFieldEditor.vue  | 38 ++++++++++++++++++++++
 .../ExtendedUserProfileMultiChoiceFieldEditor.vue  |  9 +++--
 .../ExtendedUserProfileSingleChoiceFieldEditor.vue |  9 +++--
 .../ExtendedUserProfileTextFieldEditor.vue         | 23 +++++++------
 ...ExtendedUserProfileUserAgreementFieldEditor.vue | 11 +++----
 .../js/components/UserProfileEditor.vue            |  2 +-
 .../js/containers/UserProfileContainer.vue         | 12 +++++--
 .../django_airavata_auth/js/entry-user-profile.js  | 10 +++---
 9 files changed, 77 insertions(+), 39 deletions(-)

diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileEditor.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileEditor.vue
index 4ab87d89..b224fba2 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileEditor.vue
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileEditor.vue
@@ -15,7 +15,7 @@ import { mapGetters } from "vuex";
 import ExtendedUserProfileMultiChoiceFieldEditor from "./ExtendedUserProfileMultiChoiceFieldEditor.vue";
 import ExtendedUserProfileSingleChoiceFieldEditor from "./ExtendedUserProfileSingleChoiceFieldEditor.vue";
 import ExtendedUserProfileTextFieldEditor from "./ExtendedUserProfileTextFieldEditor.vue";
-import ExtendedUserProfileUserAgreementFieldEditor from './ExtendedUserProfileUserAgreementFieldEditor.vue';
+import ExtendedUserProfileUserAgreementFieldEditor from "./ExtendedUserProfileUserAgreementFieldEditor.vue";
 export default {
   computed: {
     ...mapGetters("extendedUserProfile", ["extendedUserProfileFields"]),
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileFieldEditor.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileFieldEditor.vue
new file mode 100644
index 00000000..1115ea7e
--- /dev/null
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileFieldEditor.vue
@@ -0,0 +1,38 @@
+<template>
+  <b-form-group
+    :label="extendedUserProfileField.name"
+    :description="extendedUserProfileField.help_text"
+  >
+    <b-card
+      v-for="link in extendedUserProfileField.links"
+      :key="link.id"
+      :header="link.label"
+    >
+      <b-card-text v-if="link.display_inline">
+        <iframe :src="link.url" />
+      </b-card-text>
+      <a
+        v-if="link.display_link"
+        :href="link.url"
+        target="_blank"
+        class="card-link"
+        >Open '{{ link.label }}' in separate tab.</a
+      >
+    </b-card>
+    <slot />
+  </b-form-group>
+</template>
+
+<script>
+export default {
+  props: ["extendedUserProfileField"],
+};
+</script>
+
+<style scoped>
+iframe {
+  border: none;
+  width: 100%;
+  height: 50vh;
+}
+</style>
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileMultiChoiceFieldEditor.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileMultiChoiceFieldEditor.vue
index 2a17d614..d83872e7 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileMultiChoiceFieldEditor.vue
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileMultiChoiceFieldEditor.vue
@@ -1,19 +1,18 @@
 <template>
-  <b-form-group
-    :label="extendedUserProfileField.name"
-    :description="extendedUserProfileField.help_text"
-  >
+  <extended-user-profile-field-editor v-bind="$props">
     <b-form-checkbox-group
       v-model="value"
       :options="options"
       stacked
     ></b-form-checkbox-group>
-  </b-form-group>
+  </extended-user-profile-field-editor>
 </template>
 
 <script>
 import { mapGetters, mapMutations } from "vuex";
+import ExtendedUserProfileFieldEditor from "./ExtendedUserProfileFieldEditor.vue";
 export default {
+  components: { ExtendedUserProfileFieldEditor },
   props: ["extendedUserProfileField"],
   computed: {
     ...mapGetters("extendedUserProfile", ["getMultiChoiceValue"]),
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileSingleChoiceFieldEditor.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileSingleChoiceFieldEditor.vue
index bf95019f..a78fc028 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileSingleChoiceFieldEditor.vue
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileSingleChoiceFieldEditor.vue
@@ -1,15 +1,14 @@
 <template>
-  <b-form-group
-    :label="extendedUserProfileField.name"
-    :description="extendedUserProfileField.help_text"
-  >
+  <extended-user-profile-field-editor v-bind="$props">
     <b-form-select v-model="value" :options="options"></b-form-select>
-  </b-form-group>
+  </extended-user-profile-field-editor>
 </template>
 
 <script>
 import { mapGetters, mapMutations } from "vuex";
+import ExtendedUserProfileFieldEditor from "./ExtendedUserProfileFieldEditor.vue";
 export default {
+  components: { ExtendedUserProfileFieldEditor },
   props: ["extendedUserProfileField"],
   computed: {
     ...mapGetters("extendedUserProfile", ["getSingleChoiceValue"]),
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileTextFieldEditor.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileTextFieldEditor.vue
index 8f2b9989..c9a96488 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileTextFieldEditor.vue
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileTextFieldEditor.vue
@@ -1,30 +1,29 @@
 <template>
-  <b-form-group
-    :label="extendedUserProfileField.name"
-    :description="extendedUserProfileField.help_text"
-  >
+  <extended-user-profile-field-editor v-bind="$props">
     <b-form-input v-model="value" />
-  </b-form-group>
+  </extended-user-profile-field-editor>
 </template>
 
 <script>
-import { mapGetters, mapMutations } from 'vuex';
+import { mapGetters, mapMutations } from "vuex";
+import ExtendedUserProfileFieldEditor from "./ExtendedUserProfileFieldEditor.vue";
 export default {
+  components: { ExtendedUserProfileFieldEditor },
   props: ["extendedUserProfileField"],
   computed: {
-    ...mapGetters('extendedUserProfile', ['getTextValue']),
+    ...mapGetters("extendedUserProfile", ["getTextValue"]),
     value: {
       get() {
         return this.getTextValue(this.extendedUserProfileField.id);
       },
       set(value) {
-        this.setTextValue({value, id: this.extendedUserProfileField.id})
-      }
-    }
+        this.setTextValue({ value, id: this.extendedUserProfileField.id });
+      },
+    },
   },
   methods: {
-    ...mapMutations('extendedUserProfile', ['setTextValue']),
-  }
+    ...mapMutations("extendedUserProfile", ["setTextValue"]),
+  },
 };
 </script>
 
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileUserAgreementFieldEditor.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileUserAgreementFieldEditor.vue
index 4857c4e3..f21965be 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileUserAgreementFieldEditor.vue
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileUserAgreementFieldEditor.vue
@@ -1,17 +1,16 @@
 <template>
-  <b-form-group
-    :label="extendedUserProfileField.name"
-    :description="extendedUserProfileField.help_text"
-  >
+  <extended-user-profile-field-editor v-bind="$props">
     <b-form-checkbox v-model="value" :unchecked-value="false">
       {{ extendedUserProfileField.checkbox_label }}
     </b-form-checkbox>
-  </b-form-group>
+  </extended-user-profile-field-editor>
 </template>
 
 <script>
 import { mapGetters, mapMutations } from "vuex";
+import ExtendedUserProfileFieldEditor from "./ExtendedUserProfileFieldEditor.vue";
 export default {
+  components: { ExtendedUserProfileFieldEditor },
   props: ["extendedUserProfileField"],
   computed: {
     ...mapGetters("extendedUserProfile", ["getUserAgreementValue"]),
@@ -32,5 +31,3 @@ export default {
   },
 };
 </script>
-
-<style></style>
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/components/UserProfileEditor.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/components/UserProfileEditor.vue
index 8816b93e..68f423e8 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/components/UserProfileEditor.vue
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/components/UserProfileEditor.vue
@@ -101,7 +101,7 @@ export default {
     },
     valid() {
       return !this.$v.$invalid;
-    }
+    },
   },
   validations() {
     return {
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/containers/UserProfileContainer.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/containers/UserProfileContainer.vue
index 4d44405f..3955c151 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/containers/UserProfileContainer.vue
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/containers/UserProfileContainer.vue
@@ -23,7 +23,9 @@
     />
     <!-- TODO: include both forms in the same card -->
     <!-- include extended-user-profile-editor if there are extendedUserProfileFields -->
-    <extended-user-profile-editor v-if="extendedUserProfileFields && extendedUserProfileFields.length > 0"/>
+    <extended-user-profile-editor
+      v-if="extendedUserProfileFields && extendedUserProfileFields.length > 0"
+    />
     <b-link
       v-if="user && user.complete"
       class="text-muted small"
@@ -37,7 +39,7 @@
 import UserProfileEditor from "../components/UserProfileEditor.vue";
 import { notifications } from "django-airavata-common-ui";
 import { mapActions, mapGetters } from "vuex";
-import ExtendedUserProfileEditor from '../components/ExtendedUserProfileEditor.vue';
+import ExtendedUserProfileEditor from "../components/ExtendedUserProfileEditor.vue";
 
 export default {
   components: { UserProfileEditor, ExtendedUserProfileEditor },
@@ -75,7 +77,11 @@ export default {
       "updateUser",
       "resendEmailVerification",
     ]),
-    ...mapActions("extendedUserProfile", ["loadExtendedUserProfileFields", "loadExtendedUserProfileValues", "saveExtendedUserProfileValues"]),
+    ...mapActions("extendedUserProfile", [
+      "loadExtendedUserProfileFields",
+      "loadExtendedUserProfileValues",
+      "saveExtendedUserProfileValues",
+    ]),
     async onSave() {
       // TODO: only save if both standard and extended user profiles are valid
       this.saveExtendedUserProfileValues();
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/entry-user-profile.js b/django_airavata/apps/auth/static/django_airavata_auth/js/entry-user-profile.js
index 182c6ced..382bfcd2 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/entry-user-profile.js
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/entry-user-profile.js
@@ -2,10 +2,10 @@ import { components, entry } from "django-airavata-common-ui";
 import UserProfileContainer from "./containers/UserProfileContainer.vue";
 import createStore from "./store";
 
-entry(Vue => {
+entry((Vue) => {
   const store = createStore(Vue);
-    new Vue({
-      store,
-      render: (h) => h(components.MainLayout, [h(UserProfileContainer)]),
-    }).$mount("#user-profile");
+  new Vue({
+    store,
+    render: (h) => h(components.MainLayout, [h(UserProfileContainer)]),
+  }).$mount("#user-profile");
 });


[airavata-django-portal] 06/06: AIRAVATA-3565 Other field for single and multi choice fields

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch AIRAVATA-3562
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit 793b8f39c6df177259babd0e190892513335d9c5
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Tue Apr 26 15:38:57 2022 -0400

    AIRAVATA-3565 Other field for single and multi choice fields
---
 .../ExtendedUserProfileMultiChoiceFieldEditor.vue  | 61 ++++++++++++++++++++--
 .../ExtendedUserProfileSingleChoiceFieldEditor.vue | 60 +++++++++++++++++++--
 .../js/store/modules/extendedUserProfile.js        | 46 +++++++++++++++-
 3 files changed, 156 insertions(+), 11 deletions(-)

diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileMultiChoiceFieldEditor.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileMultiChoiceFieldEditor.vue
index d83872e7..db45cbb9 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileMultiChoiceFieldEditor.vue
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileMultiChoiceFieldEditor.vue
@@ -4,29 +4,70 @@
       v-model="value"
       :options="options"
       stacked
-    ></b-form-checkbox-group>
+      @change="onChange"
+    >
+      <b-form-checkbox :value="otherOptionValue"
+        >Other (please specify)</b-form-checkbox
+      >
+    </b-form-checkbox-group>
+    <b-form-input
+      class="mt-2"
+      v-if="showOther"
+      v-model="other"
+      placeholder="Please specify"
+    />
   </extended-user-profile-field-editor>
 </template>
 
 <script>
 import { mapGetters, mapMutations } from "vuex";
 import ExtendedUserProfileFieldEditor from "./ExtendedUserProfileFieldEditor.vue";
+const OTHER_OPTION = new Object(); // sentinel value
 export default {
   components: { ExtendedUserProfileFieldEditor },
   props: ["extendedUserProfileField"],
+  data() {
+    return {
+      otherOptionSelected: false,
+    };
+  },
   computed: {
-    ...mapGetters("extendedUserProfile", ["getMultiChoiceValue"]),
+    ...mapGetters("extendedUserProfile", [
+      "getMultiChoiceValue",
+      "getMultiChoiceOther",
+    ]),
     value: {
       get() {
-        return this.getMultiChoiceValue(this.extendedUserProfileField.id);
+        const copy = this.getMultiChoiceValue(
+          this.extendedUserProfileField.id
+        ).slice();
+        if (this.showOther) {
+          copy.push(this.otherOptionValue);
+        }
+        return copy;
       },
       set(value) {
+        const values = value.filter((v) => v !== this.otherOptionValue);
         this.setMultiChoiceValue({
+          value: values,
+          id: this.extendedUserProfileField.id,
+        });
+      },
+    },
+    other: {
+      get() {
+        return this.getMultiChoiceOther(this.extendedUserProfileField.id);
+      },
+      set(value) {
+        this.setMultiChoiceOther({
           value,
           id: this.extendedUserProfileField.id,
         });
       },
     },
+    showOther() {
+      return this.other || this.otherOptionSelected;
+    },
     options() {
       return this.extendedUserProfileField &&
         this.extendedUserProfileField.choices
@@ -38,9 +79,21 @@ export default {
           })
         : [];
     },
+    otherOptionValue() {
+      return OTHER_OPTION;
+    },
   },
   methods: {
-    ...mapMutations("extendedUserProfile", ["setMultiChoiceValue"]),
+    ...mapMutations("extendedUserProfile", [
+      "setMultiChoiceValue",
+      "setMultiChoiceOther",
+    ]),
+    onChange(value) {
+      this.otherOptionSelected = value.includes(this.otherOptionValue);
+      if (!this.otherOptionSelected) {
+        this.other = "";
+      }
+    },
   },
 };
 </script>
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileSingleChoiceFieldEditor.vue b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileSingleChoiceFieldEditor.vue
index a78fc028..524fee70 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileSingleChoiceFieldEditor.vue
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/components/ExtendedUserProfileSingleChoiceFieldEditor.vue
@@ -1,28 +1,69 @@
 <template>
   <extended-user-profile-field-editor v-bind="$props">
-    <b-form-select v-model="value" :options="options"></b-form-select>
+    <b-form-select v-model="value" :options="options" @change="onChange">
+      <b-form-select-option :value="otherOptionValue"
+        >Other (please specify)</b-form-select-option
+      >
+    </b-form-select>
+    <b-form-input
+      class="mt-2"
+      v-if="showOther"
+      v-model="other"
+      placeholder="Please specify"
+    />
   </extended-user-profile-field-editor>
 </template>
 
 <script>
 import { mapGetters, mapMutations } from "vuex";
 import ExtendedUserProfileFieldEditor from "./ExtendedUserProfileFieldEditor.vue";
+const OTHER_OPTION = new Object(); // sentinel value
+
 export default {
   components: { ExtendedUserProfileFieldEditor },
   props: ["extendedUserProfileField"],
+  data() {
+    return {
+      otherOptionSelected: false,
+    };
+  },
   computed: {
-    ...mapGetters("extendedUserProfile", ["getSingleChoiceValue"]),
+    ...mapGetters("extendedUserProfile", [
+      "getSingleChoiceValue",
+      "getSingleChoiceOther",
+    ]),
     value: {
       get() {
-        return this.getSingleChoiceValue(this.extendedUserProfileField.id);
+        if (this.showOther) {
+          return this.otherOptionValue;
+        } else {
+          return this.getSingleChoiceValue(this.extendedUserProfileField.id);
+        }
+      },
+      set(value) {
+        if (value !== this.otherOptionValue) {
+          this.setSingleChoiceValue({
+            value,
+            id: this.extendedUserProfileField.id,
+          });
+        }
+      },
+    },
+    other: {
+      get() {
+        return this.getSingleChoiceOther(this.extendedUserProfileField.id);
       },
       set(value) {
-        this.setSingleChoiceValue({
+        this.setSingleChoiceOther({
           value,
           id: this.extendedUserProfileField.id,
         });
       },
     },
+    showOther() {
+      const value = this.getSingleChoiceValue(this.extendedUserProfileField.id);
+      return (value === null && this.other) || this.otherOptionSelected;
+    },
     options() {
       return this.extendedUserProfileField &&
         this.extendedUserProfileField.choices
@@ -34,9 +75,18 @@ export default {
           })
         : [];
     },
+    otherOptionValue() {
+      return OTHER_OPTION;
+    },
   },
   methods: {
-    ...mapMutations("extendedUserProfile", ["setSingleChoiceValue"]),
+    ...mapMutations("extendedUserProfile", [
+      "setSingleChoiceValue",
+      "setSingleChoiceOther",
+    ]),
+    onChange(value) {
+      this.otherOptionSelected = value === this.otherOptionValue;
+    },
   },
 };
 </script>
diff --git a/django_airavata/apps/auth/static/django_airavata_auth/js/store/modules/extendedUserProfile.js b/django_airavata/apps/auth/static/django_airavata_auth/js/store/modules/extendedUserProfile.js
index b58e9ca4..d2163890 100644
--- a/django_airavata/apps/auth/static/django_airavata_auth/js/store/modules/extendedUserProfile.js
+++ b/django_airavata/apps/auth/static/django_airavata_auth/js/store/modules/extendedUserProfile.js
@@ -24,6 +24,12 @@ const getters = {
       return null;
     }
   },
+  getSingleChoiceOther: (state) => (id) => {
+    const value = state.extendedUserProfileValues.find(
+      (v) => v.ext_user_profile_field === id
+    );
+    return value ? value.other_value : null;
+  },
   getMultiChoiceValue: (state) => (id) => {
     const value = state.extendedUserProfileValues.find(
       (v) => v.ext_user_profile_field === id
@@ -31,9 +37,15 @@ const getters = {
     if (value && value.choices) {
       return value.choices;
     } else {
-      return null;
+      return [];
     }
   },
+  getMultiChoiceOther: (state) => (id) => {
+    const value = state.extendedUserProfileValues.find(
+      (v) => v.ext_user_profile_field === id
+    );
+    return value ? value.other_value : null;
+  },
   getUserAgreementValue: (state) => (id) => {
     const value = state.extendedUserProfileValues.find(
       (v) => v.ext_user_profile_field === id
@@ -105,13 +117,28 @@ const mutations = {
       });
     }
   },
+  setSingleChoiceOther(state, { value, id }) {
+    const profileValue = state.extendedUserProfileValues.find(
+      (v) => v.ext_user_profile_field === id
+    );
+    if (profileValue) {
+      profileValue.choices = [];
+      profileValue.other_value = value;
+    } else {
+      state.extendedUserProfileValues.push({
+        value_type: "single_choice",
+        ext_user_profile_field: id,
+        choices: [],
+        other_value: value,
+      });
+    }
+  },
   setMultiChoiceValue(state, { value, id }) {
     const profileValue = state.extendedUserProfileValues.find(
       (v) => v.ext_user_profile_field === id
     );
     if (profileValue) {
       profileValue.choices = value;
-      profileValue.other_value = "";
     } else {
       state.extendedUserProfileValues.push({
         value_type: "multi_choice",
@@ -120,6 +147,21 @@ const mutations = {
       });
     }
   },
+  setMultiChoiceOther(state, { value, id }) {
+    const profileValue = state.extendedUserProfileValues.find(
+      (v) => v.ext_user_profile_field === id
+    );
+    if (profileValue) {
+      profileValue.other_value = value;
+    } else {
+      state.extendedUserProfileValues.push({
+        value_type: "multi_choice",
+        ext_user_profile_field: id,
+        choices: [],
+        other_value: value,
+      });
+    }
+  },
   setUserAgreementValue(state, { value, id }) {
     const profileValue = state.extendedUserProfileValues.find(
       (v) => v.ext_user_profile_field === id