Merge pull request #1031 from pateljannat/brand-settings
feat: brand settings
This commit is contained in:
85
frontend/src/components/BrandSettings.vue
Normal file
85
frontend/src/components/BrandSettings.vue
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-col justify-between h-full">
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="font-semibold mb-1">
|
||||||
|
{{ __(label) }}
|
||||||
|
</div>
|
||||||
|
<Badge
|
||||||
|
v-if="isDirty"
|
||||||
|
:label="__('Not Saved')"
|
||||||
|
variant="subtle"
|
||||||
|
theme="orange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-gray-600">
|
||||||
|
{{ __(description) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<SettingFields :fields="fields" :data="data.data" />
|
||||||
|
<div class="flex flex-row-reverse mt-auto">
|
||||||
|
<Button variant="solid" :loading="saveSettings.loading" @click="update">
|
||||||
|
{{ __('Update') }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { createResource, Button, Badge } from 'frappe-ui'
|
||||||
|
import SettingFields from '@/components/SettingFields.vue'
|
||||||
|
import { watch, ref } from 'vue'
|
||||||
|
|
||||||
|
const isDirty = ref(false)
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
fields: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const saveSettings = createResource({
|
||||||
|
url: 'frappe.client.set_value',
|
||||||
|
makeParams(values) {
|
||||||
|
console.log(values)
|
||||||
|
return {
|
||||||
|
doctype: 'Website Settings',
|
||||||
|
name: 'Website Settings',
|
||||||
|
fieldname: values.fields,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const update = () => {
|
||||||
|
let fieldsToSave = {}
|
||||||
|
let imageFields = ['favicon', 'banner_image', 'footer_logo']
|
||||||
|
props.fields.forEach((f) => {
|
||||||
|
if (imageFields.includes(f.name)) {
|
||||||
|
fieldsToSave[f.name] = f.value ? f.value.file_url : null
|
||||||
|
} else {
|
||||||
|
fieldsToSave[f.name] = f.value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
saveSettings.submit({
|
||||||
|
fields: fieldsToSave,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(props.data, (newData) => {
|
||||||
|
console.log(newData)
|
||||||
|
if (newData && !isDirty.value) {
|
||||||
|
isDirty.value = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -45,6 +45,13 @@
|
|||||||
:label="activeTab.label"
|
:label="activeTab.label"
|
||||||
:description="activeTab.description"
|
:description="activeTab.description"
|
||||||
/>
|
/>
|
||||||
|
<BrandSettings
|
||||||
|
v-else-if="activeTab.label === 'Branding'"
|
||||||
|
:label="activeTab.label"
|
||||||
|
:description="activeTab.description"
|
||||||
|
:fields="activeTab.fields"
|
||||||
|
:data="branding"
|
||||||
|
/>
|
||||||
<SettingDetails
|
<SettingDetails
|
||||||
v-else
|
v-else
|
||||||
:fields="activeTab.fields"
|
:fields="activeTab.fields"
|
||||||
@@ -58,13 +65,14 @@
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Dialog, createDocumentResource } from 'frappe-ui'
|
import { Dialog, createDocumentResource, createResource } from 'frappe-ui'
|
||||||
import { ref, computed, watch } from 'vue'
|
import { ref, computed, watch } from 'vue'
|
||||||
import { useSettings } from '@/stores/settings'
|
import { useSettings } from '@/stores/settings'
|
||||||
import SettingDetails from '../SettingDetails.vue'
|
import SettingDetails from '../SettingDetails.vue'
|
||||||
import SidebarLink from '@/components/SidebarLink.vue'
|
import SidebarLink from '@/components/SidebarLink.vue'
|
||||||
import Members from '@/components/Members.vue'
|
import Members from '@/components/Members.vue'
|
||||||
import Categories from '@/components/Categories.vue'
|
import Categories from '@/components/Categories.vue'
|
||||||
|
import BrandSettings from '@/components/BrandSettings.vue'
|
||||||
|
|
||||||
const show = defineModel()
|
const show = defineModel()
|
||||||
const doctype = ref('LMS Settings')
|
const doctype = ref('LMS Settings')
|
||||||
@@ -79,6 +87,12 @@ const data = createDocumentResource({
|
|||||||
auto: true,
|
auto: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const branding = createResource({
|
||||||
|
url: 'lms.lms.api.get_branding',
|
||||||
|
auto: true,
|
||||||
|
cache: 'brand',
|
||||||
|
})
|
||||||
|
|
||||||
const tabsStructure = computed(() => {
|
const tabsStructure = computed(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -155,6 +169,54 @@ const tabsStructure = computed(() => {
|
|||||||
label: 'Customise',
|
label: 'Customise',
|
||||||
hideLabel: false,
|
hideLabel: false,
|
||||||
items: [
|
items: [
|
||||||
|
{
|
||||||
|
label: 'Branding',
|
||||||
|
icon: 'Blocks',
|
||||||
|
description:
|
||||||
|
'Customize the brand information of your learning system',
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
label: 'Brand Name',
|
||||||
|
name: 'app_name',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Copyright',
|
||||||
|
name: 'copyright',
|
||||||
|
type: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Address',
|
||||||
|
name: 'address',
|
||||||
|
type: 'textarea',
|
||||||
|
rows: 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Footer "Powered By"',
|
||||||
|
name: 'footer_powered',
|
||||||
|
type: 'textarea',
|
||||||
|
rows: 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'Column Break',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Logo',
|
||||||
|
name: 'banner_image',
|
||||||
|
type: 'Upload',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Favicon',
|
||||||
|
name: 'favicon',
|
||||||
|
type: 'Upload',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Footer Logo',
|
||||||
|
name: 'footer_logo',
|
||||||
|
type: 'Upload',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Sidebar',
|
label: 'Sidebar',
|
||||||
icon: 'PanelLeftIcon',
|
icon: 'PanelLeftIcon',
|
||||||
|
|||||||
@@ -1,53 +1,23 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col justify-between h-full">
|
<div class="flex flex-col justify-between h-full">
|
||||||
<div>
|
<div>
|
||||||
<div class="font-semibold mb-1">
|
<div class="flex itemsc-center justify-between">
|
||||||
{{ __(label) }}
|
<div class="font-semibold mb-1">
|
||||||
|
{{ __(label) }}
|
||||||
|
</div>
|
||||||
|
<Badge
|
||||||
|
v-if="data.isDirty"
|
||||||
|
:label="__('Not Saved')"
|
||||||
|
variant="subtle"
|
||||||
|
theme="orange"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-xs text-gray-600">
|
<div class="text-xs text-gray-600">
|
||||||
{{ __(description) }}
|
{{ __(description) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
class="my-5"
|
|
||||||
:class="{ 'flex justify-between w-full': columns.length > 1 }"
|
|
||||||
>
|
|
||||||
<div v-for="(column, index) in columns" :key="index">
|
|
||||||
<div
|
|
||||||
class="flex flex-col space-y-5"
|
|
||||||
:class="columns.length > 1 ? 'w-72' : 'w-full'"
|
|
||||||
>
|
|
||||||
<div v-for="field in column">
|
|
||||||
<Link
|
|
||||||
v-if="field.type == 'Link'"
|
|
||||||
v-model="field.value"
|
|
||||||
:doctype="field.doctype"
|
|
||||||
:label="field.label"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Codemirror
|
<SettingFields :fields="fields" :data="data.doc" />
|
||||||
v-else-if="field.type == 'Code'"
|
|
||||||
v-model:value="field.value"
|
|
||||||
:label="field.label"
|
|
||||||
:height="200"
|
|
||||||
:options="{
|
|
||||||
mode: field.mode,
|
|
||||||
theme: 'seti',
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormControl
|
|
||||||
v-else
|
|
||||||
:key="field.name"
|
|
||||||
v-model="field.value"
|
|
||||||
:label="field.label"
|
|
||||||
:type="field.type"
|
|
||||||
:rows="field.rows"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-row-reverse mt-auto">
|
<div class="flex flex-row-reverse mt-auto">
|
||||||
<Button variant="solid" :loading="data.save.loading" @click="update">
|
<Button variant="solid" :loading="data.save.loading" @click="update">
|
||||||
{{ __('Update') }}
|
{{ __('Update') }}
|
||||||
@@ -57,12 +27,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { FormControl, Button } from 'frappe-ui'
|
import { Button, Badge } from 'frappe-ui'
|
||||||
import { computed } from 'vue'
|
import SettingFields from '@/components/SettingFields.vue'
|
||||||
import Link from '@/components/Controls/Link.vue'
|
|
||||||
import Codemirror from 'codemirror-editor-vue3'
|
|
||||||
import 'codemirror/theme/seti.css'
|
|
||||||
import 'codemirror/mode/htmlmixed/htmlmixed.js'
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
fields: {
|
fields: {
|
||||||
@@ -82,40 +48,16 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const columns = computed(() => {
|
|
||||||
const cols = []
|
|
||||||
let currentColumn = []
|
|
||||||
|
|
||||||
props.fields.forEach((field) => {
|
|
||||||
if (field.type === 'Column Break') {
|
|
||||||
if (currentColumn.length > 0) {
|
|
||||||
cols.push(currentColumn)
|
|
||||||
currentColumn = []
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (field.type == 'checkbox') {
|
|
||||||
field.value = props.data.doc[field.name] ? true : false
|
|
||||||
} else {
|
|
||||||
field.value = props.data.doc[field.name]
|
|
||||||
}
|
|
||||||
currentColumn.push(field)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (currentColumn.length > 0) {
|
|
||||||
cols.push(currentColumn)
|
|
||||||
}
|
|
||||||
|
|
||||||
return cols
|
|
||||||
})
|
|
||||||
|
|
||||||
const update = () => {
|
const update = () => {
|
||||||
props.fields.forEach((f) => {
|
props.fields.forEach((f) => {
|
||||||
props.data.doc[f.name] = f.value
|
if (f.type != 'Column Break') {
|
||||||
|
props.data.doc[f.name] = f.value
|
||||||
|
}
|
||||||
})
|
})
|
||||||
props.data.save.submit()
|
props.data.save.submit()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.CodeMirror pre.CodeMirror-line,
|
.CodeMirror pre.CodeMirror-line,
|
||||||
.CodeMirror pre.CodeMirror-line-like {
|
.CodeMirror pre.CodeMirror-line-like {
|
||||||
|
|||||||
136
frontend/src/components/SettingFields.vue
Normal file
136
frontend/src/components/SettingFields.vue
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="my-5"
|
||||||
|
:class="{ 'flex justify-between w-full': columns.length > 1 }"
|
||||||
|
>
|
||||||
|
<div v-for="(column, index) in columns" :key="index">
|
||||||
|
<div
|
||||||
|
class="flex flex-col space-y-5"
|
||||||
|
:class="columns.length > 1 ? 'w-72' : 'w-full'"
|
||||||
|
>
|
||||||
|
<div v-for="field in column">
|
||||||
|
<Link
|
||||||
|
v-if="field.type == 'Link'"
|
||||||
|
v-model="data[field.name]"
|
||||||
|
:doctype="field.doctype"
|
||||||
|
:label="__(field.label)"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div v-else-if="field.type == 'Code'">
|
||||||
|
<div>
|
||||||
|
{{ __(field.label) }}
|
||||||
|
</div>
|
||||||
|
<Codemirror
|
||||||
|
v-model:value="data[field.name]"
|
||||||
|
:height="200"
|
||||||
|
:options="{
|
||||||
|
mode: field.mode,
|
||||||
|
theme: 'seti',
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="field.type == 'Upload'">
|
||||||
|
<div class="text-sm text-gray-600 mb-1">
|
||||||
|
{{ __(field.label) }}
|
||||||
|
</div>
|
||||||
|
<FileUploader
|
||||||
|
v-if="!data[field.name]"
|
||||||
|
:fileTypes="['image/*']"
|
||||||
|
:validateFile="validateFile"
|
||||||
|
@success="(file) => (data[field.name] = file)"
|
||||||
|
>
|
||||||
|
<template
|
||||||
|
v-slot="{ file, progress, uploading, openFileSelector }"
|
||||||
|
>
|
||||||
|
<div class="">
|
||||||
|
<Button @click="openFileSelector" :loading="uploading">
|
||||||
|
{{
|
||||||
|
uploading ? `Uploading ${progress}%` : 'Upload an image'
|
||||||
|
}}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</FileUploader>
|
||||||
|
<div v-else>
|
||||||
|
<div class="flex items-center text-sm">
|
||||||
|
<div class="border rounded-md p-2 mr-2">
|
||||||
|
<FileText class="h-5 w-5 stroke-1.5 text-gray-700" />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col flex-wrap">
|
||||||
|
<span class="break-all">
|
||||||
|
{{ data[field.name]?.file_name }}
|
||||||
|
</span>
|
||||||
|
<span class="text-sm text-gray-500 mt-1">
|
||||||
|
{{ getFileSize(data[field.name]?.file_size) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<X
|
||||||
|
@click="data[field.name] = null"
|
||||||
|
class="bg-gray-200 rounded-md cursor-pointer stroke-1.5 w-5 h-5 p-1 ml-4"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<FormControl
|
||||||
|
v-else
|
||||||
|
:key="field.name"
|
||||||
|
v-model="data[field.name]"
|
||||||
|
:label="__(field.label)"
|
||||||
|
:type="field.type"
|
||||||
|
:rows="field.rows"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script setup>
|
||||||
|
import { FormControl, FileUploader, Button } from 'frappe-ui'
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { getFileSize } from '@/utils'
|
||||||
|
import { X, FileText } from 'lucide-vue-next'
|
||||||
|
import Link from '@/components/Controls/Link.vue'
|
||||||
|
import Codemirror from 'codemirror-editor-vue3'
|
||||||
|
import 'codemirror/theme/seti.css'
|
||||||
|
import 'codemirror/mode/htmlmixed/htmlmixed.js'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
fields: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const columns = computed(() => {
|
||||||
|
const cols = []
|
||||||
|
let currentColumn = []
|
||||||
|
|
||||||
|
props.fields.forEach((field) => {
|
||||||
|
if (field.type === 'Column Break') {
|
||||||
|
if (currentColumn.length > 0) {
|
||||||
|
cols.push(currentColumn)
|
||||||
|
currentColumn = []
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (field.type == 'checkbox') {
|
||||||
|
field.value = props.data[field.name] ? true : false
|
||||||
|
} else {
|
||||||
|
field.value = props.data[field.name]
|
||||||
|
}
|
||||||
|
currentColumn.push(field)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (currentColumn.length > 0) {
|
||||||
|
cols.push(currentColumn)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cols
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -11,11 +11,11 @@
|
|||||||
: 'hover:bg-gray-200 px-2 w-52'
|
: 'hover:bg-gray-200 px-2 w-52'
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<span
|
<img
|
||||||
v-if="branding.data?.brand_html"
|
v-if="branding.data?.banner_image"
|
||||||
v-html="branding.data?.brand_html"
|
:src="branding.data?.banner_image.file_url"
|
||||||
class="w-8 h-8 rounded flex-shrink-0"
|
class="w-8 h-8 rounded flex-shrink-0"
|
||||||
></span>
|
/>
|
||||||
<LMSLogo v-else class="w-8 h-8 rounded flex-shrink-0" />
|
<LMSLogo v-else class="w-8 h-8 rounded flex-shrink-0" />
|
||||||
<div
|
<div
|
||||||
class="flex flex-1 flex-col text-left duration-300 ease-in-out"
|
class="flex flex-1 flex-col text-left duration-300 ease-in-out"
|
||||||
@@ -28,11 +28,10 @@
|
|||||||
<div class="text-base font-medium text-gray-900 leading-none">
|
<div class="text-base font-medium text-gray-900 leading-none">
|
||||||
<span
|
<span
|
||||||
v-if="
|
v-if="
|
||||||
branding.data?.brand_name &&
|
branding.data?.app_name && branding.data?.app_name != 'Frappe'
|
||||||
branding.data?.brand_name != 'Frappe'
|
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
{{ branding.data?.brand_name }}
|
{{ branding.data?.app_name }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else> Learning </span>
|
<span v-else> Learning </span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ const shareOnSocial = (badge, medium) => {
|
|||||||
const summary = `I am happy to announce that I earned the ${
|
const summary = `I am happy to announce that I earned the ${
|
||||||
badge.badge
|
badge.badge
|
||||||
} badge on ${dayjs(badge.issued_on).format('DD MMM YYYY')} at ${
|
} badge on ${dayjs(badge.issued_on).format('DD MMM YYYY')} at ${
|
||||||
branding.data?.brand_name
|
branding.data?.app_name
|
||||||
}.`
|
}.`
|
||||||
|
|
||||||
if (medium == 'LinkedIn')
|
if (medium == 'LinkedIn')
|
||||||
|
|||||||
@@ -499,3 +499,10 @@ export function singularize(word) {
|
|||||||
(r) => endings[r]
|
(r) => endings[r]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const validateFile = (file) => {
|
||||||
|
let extension = file.name.split('.').pop().toLowerCase()
|
||||||
|
if (!['jpg', 'jpeg', 'png', 'webp'].includes(extension)) {
|
||||||
|
return __('Only image file is allowed.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
from frappe import _
|
|
||||||
|
|
||||||
|
|
||||||
def get_data():
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
"module_name": "Community",
|
|
||||||
"color": "grey",
|
|
||||||
"icon": "octicon octicon-file-directory",
|
|
||||||
"type": "module",
|
|
||||||
"label": _("Community"),
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
"""
|
|
||||||
Configuration for docs
|
|
||||||
"""
|
|
||||||
|
|
||||||
# source_link = "https://github.com/[org_name]/community"
|
|
||||||
# docs_base_url = "https://[org_name].github.io/community"
|
|
||||||
# headline = "App that does everything"
|
|
||||||
# sub_heading = "Yes, you got that right the first time, everything"
|
|
||||||
|
|
||||||
|
|
||||||
def get_context(context):
|
|
||||||
context.brand_html = "Community"
|
|
||||||
@@ -289,11 +289,13 @@ def get_file_info(file_url):
|
|||||||
@frappe.whitelist(allow_guest=True)
|
@frappe.whitelist(allow_guest=True)
|
||||||
def get_branding():
|
def get_branding():
|
||||||
"""Get branding details."""
|
"""Get branding details."""
|
||||||
return {
|
website_settings = frappe.get_single("Website Settings")
|
||||||
"brand_name": frappe.db.get_single_value("Website Settings", "app_name"),
|
image_fields = ["banner_image", "footer_logo", "favicon"]
|
||||||
"brand_html": frappe.db.get_single_value("Website Settings", "brand_html"),
|
|
||||||
"favicon": frappe.db.get_single_value("Website Settings", "favicon"),
|
for field in image_fields:
|
||||||
}
|
website_settings.update({field: get_file_info(website_settings.get(field))})
|
||||||
|
|
||||||
|
return website_settings
|
||||||
|
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
|
|||||||
Reference in New Issue
Block a user