started migrating file editor page to generic model list

This commit is contained in:
vabene1111 2021-10-13 15:03:59 +02:00
parent 38c0d01dc1
commit 1b1945d954
11 changed files with 247 additions and 87 deletions

View File

@ -146,6 +146,7 @@ class UserPreferenceSerializer(serializers.ModelSerializer):
class UserFileSerializer(serializers.ModelSerializer):
def check_file_limit(self, validated_data):
if 'file' in validated_data:
if self.context['request'].space.max_file_storage_mb == -1:
raise ValidationError(_('File uploads are not enabled for this Space.'))
@ -156,10 +157,8 @@ class UserFileSerializer(serializers.ModelSerializer):
except TypeError:
current_file_size_mb = 0
if (
(validated_data['file'].size / 1000 / 1000 + current_file_size_mb - 5)
> self.context['request'].space.max_file_storage_mb != 0
):
if ((validated_data['file'].size / 1000 / 1000 + current_file_size_mb - 5)
> self.context['request'].space.max_file_storage_mb != 0):
raise ValidationError(_('You have reached your file upload limit.'))
def create(self, validated_data):

View File

@ -10,7 +10,7 @@ from cookbook.helper import dal
from .models import (Comment, Food, InviteLink, Keyword, MealPlan, Recipe,
RecipeBook, RecipeBookEntry, RecipeImport, ShoppingList,
Storage, Supermarket, SupermarketCategory, Sync, SyncLog, Unit, get_model_name, Automation)
Storage, Supermarket, SupermarketCategory, Sync, SyncLog, Unit, get_model_name, Automation, UserFile)
from .views import api, data, delete, edit, import_export, lists, new, views, telegram
router = routers.DefaultRouter()
@ -179,7 +179,7 @@ for m in generic_models:
)
)
vue_models = [Food, Keyword, Unit, Supermarket, SupermarketCategory, Automation]
vue_models = [Food, Keyword, Unit, Supermarket, SupermarketCategory, Automation, UserFile]
for m in vue_models:
py_name = get_model_name(m)
url_name = py_name.replace('_', '-')

View File

@ -200,6 +200,20 @@ def automation(request):
)
@group_required('user')
def user_file(request):
return render(
request,
'generic/model_template.html',
{
"title": _("Files"),
"config": {
'model': "USERFILE", # *REQUIRED* name of the model in models.js
}
}
)
@group_required('user')
def shopping_list_new(request):
# recipe-param is the name of the parameters used when filtering recipes by this attribute

View File

@ -121,7 +121,6 @@ export default {
case this.Actions.UPDATE:
update = e.form_data
update.id = this.this_item.id
console.log('form', update)
this.saveThis(update)
break;
case this.Actions.MERGE:

View File

@ -237,7 +237,6 @@ export default {
case this.Actions.UPDATE:
update = e.form_data
update.id = this.this_item.id
console.log('form', update)
this.saveThis(update)
break;
case this.Actions.MERGE:

View File

@ -0,0 +1,39 @@
<template>
<div>
<div v-if="isImage()">
<b-img :src="url" :alt="$t('Image')" class="w-100"></b-img>
</div>
<div v-else-if="url.toLowerCase().includes('.pdf')">
<iframe :src="`/static/pdfjs/viewer.html?file=${url}`" class="w-100" style="border: none; height: 30vh"></iframe>
</div>
<div v-else>
<div class="h-20 w-100 border border-primary rounded text-center">
<i class="fas fa-eye-slash fa-2x text-primary mt-2"></i>
<br/>
<a :href="url" target="_blank" rel="noreferrer nofollow" class="mt-4">{{$t('Download')}}</a>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'FileViewer',
props: {
url: String,
},
methods: {
isImage: function (){
let cleaned_url = this.url.toLowerCase()
return (cleaned_url.endsWith('.png') || cleaned_url.endsWith('.jpg') || cleaned_url.endsWith('.jpeg') || cleaned_url.endsWith('.gif'))
}
}
}
</script>

View File

@ -0,0 +1,60 @@
<template>
<div>
<b-form-group
v-bind:label="label"
class="mb-3">
<b-form-file
v-model="new_value"
type="file"
:placeholder="placeholder_text"
></b-form-file>
</b-form-group>
<file-viewer :url="preview_url" v-if="preview_url"></file-viewer>
</div>
</template>
<script>
import FileViewer from "@/components/FileViewer";
export default {
name: "FileInput",
components: {FileViewer},
props: {
field: {type: String, default: 'You Forgot To Set Field Name'},
label: {type: String, default: 'Text Field'},
value: {type: String, default: ''},
placeholder: {type: String, default: ''},
show_merge: {type: Boolean, default: false},
},
data() {
return {
new_value: undefined,
placeholder_text: this.placeholder,
preview_url: this.value
}
},
mounted() {
if (this.placeholder_text === '') {
this.placeholder_text = this.$t('Select_File')
}
},
watch: {
'new_value': function () {
if (!(typeof this.new_value === 'string' || this.new_value instanceof String)) { // only update file if it was changed
this.preview_url = URL.createObjectURL(this.new_value) //TODO improve creating obj url so file viewer know which type to display
this.$root.$emit('change', this.field, this.new_value)
}
},
},
methods: {}
}
</script>
<style scoped>
</style>

View File

@ -1,9 +1,9 @@
<template>
<div>
<b-modal :id="'modal_'+id" @hidden="cancelAction">
<template v-slot:modal-title><h4>{{form.title}}</h4></template>
<template v-slot:modal-title><h4>{{ form.title }}</h4></template>
<div v-for="(f, i) in form.fields" v-bind:key=i>
<p v-if="f.type=='instruction'">{{f.label}}</p>
<p v-if="f.type=='instruction'">{{ f.label }}</p>
<!-- this lookup is single selection -->
<lookup-input v-if="f.type=='lookup'"
:form="f"
@ -30,11 +30,16 @@
:value="f.value"
:field="f.field"
@change="storeValue"/>
<file-input v-if="f.type=='file'"
:label="f.label"
:value="f.value"
:field="f.field"
@change="storeValue"/>
</div>
<template v-slot:modal-footer>
<b-button class="float-right mx-1" variant="secondary" v-on:click="cancelAction">{{$t('Cancel')}}</b-button>
<b-button class="float-right mx-1" variant="primary" v-on:click="doAction">{{form.ok_label}}</b-button>
<b-button class="float-right mx-1" variant="secondary" v-on:click="cancelAction">{{ $t('Cancel') }}</b-button>
<b-button class="float-right mx-1" variant="primary" v-on:click="doAction">{{ form.ok_label }}</b-button>
</template>
</b-modal>
</div>
@ -44,6 +49,7 @@
import Vue from 'vue'
import {BootstrapVue} from 'bootstrap-vue'
import {getForm} from "@/utils/utils";
Vue.use(BootstrapVue)
import {Models} from "@/utils/models";
@ -52,15 +58,24 @@ import LookupInput from "@/components/Modals/LookupInput";
import TextInput from "@/components/Modals/TextInput";
import EmojiInput from "@/components/Modals/EmojiInput";
import ChoiceInput from "@/components/Modals/ChoiceInput";
import FileInput from "@/components/Modals/FileInput";
export default {
name: 'GenericModalForm',
components: {CheckboxInput, LookupInput, TextInput, EmojiInput, ChoiceInput},
components: {FileInput, CheckboxInput, LookupInput, TextInput, EmojiInput, ChoiceInput},
props: {
model: {required: true, type: Object},
action: {required: true, type: Object},
item1: {type: Object, default () {return undefined}},
item2: {type: Object, default () {return undefined}},
item1: {
type: Object, default() {
return undefined
}
},
item2: {
type: Object, default() {
return undefined
}
},
show: {required: true, type: Boolean, default: false},
},
data() {
@ -95,27 +110,27 @@ export default {
},
},
methods: {
doAction: function(){
doAction: function () {
this.dirty = false
this.$emit('finish-action', {'form_data': this.detectOverride(this.form_data) })
this.$emit('finish-action', {'form_data': this.detectOverride(this.form_data)})
},
cancelAction: function() {
cancelAction: function () {
if (this.dirty) {
this.dirty = false
this.$emit('finish-action', 'cancel')
}
},
storeValue: function(field, value) {
storeValue: function (field, value) {
this.form_data[field] = value
},
listModel: function(m) {
listModel: function (m) {
if (m === 'self') {
return this.model
} else {
return Models[m]
}
},
detectOverride: function(form) {
detectOverride: function (form) {
for (const [k, v] of Object.entries(form)) {
if (form[k].__override__) {
form[k] = form[k].__override__

View File

@ -30,6 +30,7 @@
"Manage_Books": "Manage Books",
"Meal_Plan": "Meal Plan",
"Select_Book": "Select Book",
"Select_File": "Select File",
"Recipe_Image": "Recipe Image",
"Import_finished": "Import finished",
"View_Recipes": "View Recipes",
@ -92,6 +93,7 @@
"Files": "Files",
"File": "File",
"Edit": "Edit",
"Image": "Image",
"Delete": "Delete",
"Open": "Open",
"Ok": "Open",

View File

@ -113,6 +113,7 @@ export class Models {
},
}
static KEYWORD = {
'name': i18n.t('Keyword'), // *OPTIONAL: parameters will be built model -> model_type -> default
'apiName': 'Keyword',
@ -150,6 +151,7 @@ export class Models {
}
},
}
static UNIT = {
'name': i18n.t('Unit'),
'apiName': 'Unit',
@ -175,10 +177,12 @@ export class Models {
},
'merge': true
}
static SHOPPING_LIST = {
'name': i18n.t('Shopping_list'),
'apiName': 'ShoppingListEntry',
}
static RECIPE_BOOK = {
'name': i18n.t('Recipe_Book'),
'apiName': 'RecipeBook',
@ -208,6 +212,7 @@ export class Models {
}
},
}
static SHOPPING_CATEGORY = {
'name': i18n.t('Shopping_Category'),
'apiName': 'SupermarketCategory',
@ -231,6 +236,7 @@ export class Models {
}
},
}
static SHOPPING_CATEGORY_RELATION = {
'name': i18n.t('Shopping_Category_Relation'),
'apiName': 'SupermarketCategoryRelation',
@ -254,6 +260,7 @@ export class Models {
}
},
}
static SUPERMARKET = {
'name': i18n.t('Supermarket'),
'apiName': 'Supermarket',
@ -395,6 +402,31 @@ export class Models {
},
}
static USERFILE = {
'name': i18n.t('File'),
'apiName': 'UserFile',
'paginated': false,
'create': {
'params': ['name', 'file',],
'form': {
'name': {
'form_field': true,
'type': 'text',
'field': 'name',
'label': i18n.t('Name'),
'placeholder': ''
},
'file': {
'form_field': true,
'type': 'file',
'field': 'file',
'label': i18n.t('File'),
'placeholder': ''
},
}
},
}
}

View File

@ -165,6 +165,7 @@ import axios from "axios";
axios.defaults.xsrfCookieName = 'csrftoken'
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN"
import { Actions, Models } from './models';
import {RequestArgs} from "@/utils/openapi/base";
export const ApiMixin = {
data() {