recipe stuff
This commit is contained in:
parent
816ced83b5
commit
24ed6a1cd2
1
boot.sh
1
boot.sh
@ -4,6 +4,7 @@ source venv/bin/activate
|
||||
echo "Updating database"
|
||||
python manage.py migrate
|
||||
python manage.py collectstatic --noinput
|
||||
python manage.py collectstatic_js_reverse
|
||||
echo "Done"
|
||||
|
||||
chmod -R 755 /opt/recipes/mediafiles
|
||||
|
18
cookbook/templates/javascript_urls.html
Normal file
18
cookbook/templates/javascript_urls.html
Normal file
@ -0,0 +1,18 @@
|
||||
<!--
|
||||
As there is apparently no good way to pass django named URLs to Vue/Webpack we will pack the urls we need into
|
||||
this object and load it in all the templates where we load Vue apps
|
||||
|
||||
Reason for not using other alternatives
|
||||
|
||||
## django-js-reverse
|
||||
bad performance because the 25kb or so path file needs to be loaded before any other request can be made
|
||||
or all paths need to be printed in template which is apparently not recommended for CSP reasons (although this here
|
||||
might do the same)
|
||||
|
||||
-->
|
||||
|
||||
<script type="application/javascript">
|
||||
window.DJANGO_URLS = {
|
||||
'edit_storage'
|
||||
}
|
||||
</script>
|
@ -16,8 +16,15 @@
|
||||
{% block script %}
|
||||
<script src="{% url 'javascript-catalog' %}"></script>
|
||||
|
||||
{% if debug %}
|
||||
<script src="{% url 'js_reverse' %}"></script>
|
||||
{% else %}
|
||||
<script src="{% static 'django_js_reverse/js/reverse.js' %}"></script>
|
||||
{% endif %}
|
||||
|
||||
<script type="application/javascript">
|
||||
window.RECIPE_ID = 5;
|
||||
|
||||
</script>
|
||||
|
||||
{% render_bundle 'chunk-vendors' %}
|
||||
|
@ -24,6 +24,10 @@ SECRET_KEY = os.getenv('SECRET_KEY') if os.getenv('SECRET_KEY') else 'INSECURE_S
|
||||
|
||||
DEBUG = bool(int(os.getenv('DEBUG', True)))
|
||||
|
||||
INTERNAL_IPS = [
|
||||
'127.0.0.1',
|
||||
]
|
||||
|
||||
# allow djangos wsgi server to server mediafiles
|
||||
GUNICORN_MEDIA = bool(int(os.getenv('GUNICORN_MEDIA', True)))
|
||||
|
||||
@ -74,6 +78,7 @@ INSTALLED_APPS = [
|
||||
'rest_framework.authtoken',
|
||||
'django_cleanup.apps.CleanupConfig',
|
||||
'webpack_loader',
|
||||
'django_js_reverse',
|
||||
'cookbook.apps.CookbookConfig',
|
||||
]
|
||||
|
||||
|
@ -20,6 +20,7 @@ from django.contrib import admin
|
||||
from django.urls import include, path
|
||||
from django.views.i18n import JavaScriptCatalog
|
||||
from django.views.static import serve
|
||||
from django_js_reverse import views as reverse_views
|
||||
|
||||
urlpatterns = [
|
||||
path('', include('cookbook.urls')),
|
||||
@ -31,6 +32,7 @@ urlpatterns = [
|
||||
JavaScriptCatalog.as_view(domain='django'),
|
||||
name='javascript-catalog'
|
||||
),
|
||||
url(r'^jsreverse.json$', reverse_views.urls_js, name='js_reverse'),
|
||||
]
|
||||
|
||||
if settings.GUNICORN_MEDIA or settings.DEBUG:
|
||||
|
@ -29,3 +29,4 @@ microdata==0.7.1
|
||||
django-random-queryset==0.1.3
|
||||
Jinja2==2.11.2
|
||||
django-webpack-loader==0.7.0
|
||||
django-js-reverse==0.9.1
|
@ -1,6 +1,7 @@
|
||||
<template>
|
||||
<div id="app" v-if="!loading">
|
||||
<h1>{{ recipe.name }}</h1>
|
||||
<recipe-context-menu v-bind:recipe="recipe"></recipe-context-menu>
|
||||
|
||||
<img v-bind:src="recipe.image">
|
||||
|
||||
@ -17,31 +18,23 @@ import {BootstrapVue} from 'bootstrap-vue'
|
||||
|
||||
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
||||
|
||||
import {makeToast} from "@/utils/utils.js";
|
||||
|
||||
const _ = window.gettext
|
||||
import {apiLoadRecipe} from "@/utils/api";
|
||||
|
||||
import Step from "@/components/Step";
|
||||
import RecipeContextMenu from "@/components/RecipeContextMenu";
|
||||
import {GettextMixin, ToastMixin} from "@/utils/utils";
|
||||
|
||||
Vue.use(BootstrapVue)
|
||||
|
||||
import Vuex from 'vuex'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
const store = new Vuex.Store({
|
||||
state: {
|
||||
servings: 1
|
||||
}
|
||||
})
|
||||
|
||||
import {apiLoadRecipe} from "@/utils/django";
|
||||
|
||||
export default {
|
||||
name: 'RecipeView',
|
||||
store: store,
|
||||
mixins: [
|
||||
GettextMixin,
|
||||
ToastMixin,
|
||||
],
|
||||
components: {
|
||||
Step
|
||||
Step,
|
||||
RecipeContextMenu,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
64
vue/src/components/RecipeContextMenu.vue
Normal file
64
vue/src/components/RecipeContextMenu.vue
Normal file
@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<div>
|
||||
{{ resolveDjangoUrl('api:recipe-detail', 5) }}
|
||||
|
||||
<div class="col col-md-4 d-print-none" style="text-align: right">
|
||||
<div class="dropdown">
|
||||
<a class="btn shadow-none" href="#" role="button" id="dropdownMenuLink"
|
||||
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<i class="fas fa-ellipsis-v"></i>
|
||||
</a>
|
||||
|
||||
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenuLink">
|
||||
<a class="dropdown-item" :href="resolveDjangoUrl('edit_recipe', recipe.id)"><i
|
||||
class="fas fa-pencil-alt fa-fw"></i> {{ _('Edit') }}</a>
|
||||
<button class="dropdown-item" onclick="$('#bookmarkModal').modal({'show':true})">
|
||||
<i class="fas fa-bookmark fa-fw"></i> {{ _('Add to Book') }}
|
||||
</button>
|
||||
|
||||
<a class="dropdown-item" :href="recipe.name" v-if="true"> <!--TODO implement -->
|
||||
<i class="fas fa-shopping-cart fa-fw"></i> {{ _('Add to Shopping') }} </a>
|
||||
|
||||
<a class="dropdown-item" :href="resolveDjangoUrl('new_meal_plan', recipe.id)"><i
|
||||
class="fas fa-calendar fa-fw"></i> {{ _('Add to Plan') }}</a>
|
||||
|
||||
<!--
|
||||
<button class="dropdown-item" :onclick="openCookLogModal(recipe.id)"><i
|
||||
class="fas fa-clipboard-list fa-fw"></i> {{ _('Log Cooking') }}
|
||||
</button>
|
||||
-->
|
||||
<button class="dropdown-item" onclick="window.print()"><i
|
||||
class="fas fa-print fa-fw"></i> {{ _('Print') }}
|
||||
</button>
|
||||
|
||||
<a class="dropdown-item" :href="resolveDjangoUrl('view_export', recipe.id)" target="_blank"
|
||||
rel="noopener noreferrer"><i class="fas fa-file-export fa-fw"></i> {{ _('Export') }}</a>
|
||||
|
||||
<a class="dropdown-item" :href="resolveDjangoUrl('new_share_link', recipe.id)" target="_blank"
|
||||
rel="noopener noreferrer" v-if="recipe.internal"><i class="fas fa-share-alt fa-fw"></i> {{
|
||||
_('Share')
|
||||
}}</a>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import {GettextMixin, ResolveUrlMixin} from "@/utils/utils";
|
||||
|
||||
export default {
|
||||
name: 'RecipeContextMenu',
|
||||
mixins: [
|
||||
ResolveUrlMixin,
|
||||
GettextMixin
|
||||
],
|
||||
props: {
|
||||
recipe: Object,
|
||||
}
|
||||
}
|
||||
</script>
|
@ -0,0 +1,12 @@
|
||||
import axios from "axios";
|
||||
import {makeToast} from "@/utils/utils";
|
||||
import {resolveDjangoUrl} from "@/utils/utils";
|
||||
|
||||
export function apiLoadRecipe(recipe_id) {
|
||||
return axios.get(resolveDjangoUrl('api:recipe-detail', recipe_id)).then((response) => {
|
||||
return response.data
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
makeToast('Error', 'There was an error loading a resource!', 'danger')
|
||||
})
|
||||
}
|
@ -1,12 +0,0 @@
|
||||
import axios from "axios";
|
||||
import {makeToast} from "@/utils/utils";
|
||||
|
||||
|
||||
export function apiLoadRecipe(recipe_id) {
|
||||
return axios.get(`/api/recipe/${recipe_id}`).then((response) => {
|
||||
return response.data
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
makeToast('Error', 'There was an error loading a resource!', 'danger')
|
||||
})
|
||||
}
|
@ -1,4 +1,15 @@
|
||||
import { BToast } from 'bootstrap-vue'
|
||||
/*
|
||||
* Utility functions to call bootstrap toasts
|
||||
* */
|
||||
import {BToast} from 'bootstrap-vue'
|
||||
|
||||
export const ToastMixin = {
|
||||
methods: {
|
||||
makeToast: function (title, message, variant = null) {
|
||||
return makeToast(title, message, variant)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function makeToast(title, message, variant = null) {
|
||||
let toaster = new BToast()
|
||||
@ -8,4 +19,46 @@ export function makeToast(title, message, variant = null) {
|
||||
toaster: 'b-toaster-top-center',
|
||||
solid: true
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* Utility functions to use djangos gettext
|
||||
* */
|
||||
|
||||
export const GettextMixin = {
|
||||
methods: {
|
||||
/**
|
||||
* uses djangos javascript gettext implementation to localize text
|
||||
* @param {string} param string to translate
|
||||
*/
|
||||
_: function (param) {
|
||||
return djangoGettext(param)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function djangoGettext(param) {
|
||||
return window.gettext(param)
|
||||
}
|
||||
|
||||
/*
|
||||
* Utility function to use djangos named urls
|
||||
* */
|
||||
|
||||
// uses https://github.com/ierror/django-js-reverse#use-the-urls-in-javascript
|
||||
export const ResolveUrlMixin = {
|
||||
methods: {
|
||||
/**
|
||||
* Returns path of a django named URL
|
||||
* @param {string} url name of url
|
||||
* @param {*} params tuple of params to pass to django named url
|
||||
*/
|
||||
resolveDjangoUrl: function (url, params) {
|
||||
return resolveDjangoUrl(url, params)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function resolveDjangoUrl(url, params) {
|
||||
return window.Urls[url](params)
|
||||
}
|
@ -1 +1 @@
|
||||
{"status":"done","publicPath":"http://localhost:8080/","error":"ModuleError","message":"Module Error (from ./node_modules/eslint-loader/index.js):\n\nF:\\Developement\\Django\\recipes\\vue\\src\\apps\\RecipeView\\RecipeView.vue\n 47:11 error 'root_element' is not defined no-undef\n\n✖ 1 problem (1 error, 0 warnings)\n","chunks":{"chunk-vendors":[{"name":"js/chunk-vendors.js","publicPath":"http://localhost:8080/js/chunk-vendors.js","path":"F:\\Developement\\Django\\recipes\\cookbook\\static\\vue\\js\\chunk-vendors.js"}],"recipe_view":[{"name":"js/recipe_view.js","publicPath":"http://localhost:8080/js/recipe_view.js","path":"F:\\Developement\\Django\\recipes\\cookbook\\static\\vue\\js\\recipe_view.js"},{"name":"recipe_view.85b0fba40931a99bbc77.hot-update.js","publicPath":"http://localhost:8080/recipe_view.85b0fba40931a99bbc77.hot-update.js","path":"F:\\Developement\\Django\\recipes\\cookbook\\static\\vue\\recipe_view.85b0fba40931a99bbc77.hot-update.js"}]}}
|
||||
{"status":"done","publicPath":"http://localhost:8080/","chunks":{"chunk-vendors":[{"name":"js/chunk-vendors.js","publicPath":"http://localhost:8080/js/chunk-vendors.js","path":"F:\\Developement\\Django\\recipes\\cookbook\\static\\vue\\js\\chunk-vendors.js"}],"recipe_view":[{"name":"js/recipe_view.js","publicPath":"http://localhost:8080/js/recipe_view.js","path":"F:\\Developement\\Django\\recipes\\cookbook\\static\\vue\\js\\recipe_view.js"},{"name":"recipe_view.298e10642301889fe8ed.hot-update.js","publicPath":"http://localhost:8080/recipe_view.298e10642301889fe8ed.hot-update.js","path":"F:\\Developement\\Django\\recipes\\cookbook\\static\\vue\\recipe_view.298e10642301889fe8ed.hot-update.js"}]},"error":"ModuleError","message":"Module Error (from ./node_modules/vue-loader/lib/loaders/templateLoader.js):\n(Emitted value instead of an instance of Error) \n\n Errors compiling template:\n\n href=\"{% url 'view_export' %}?r={{ recipe.pk }}\": Interpolation inside attributes has been removed. Use v-bind or the colon shorthand instead. For example, instead of <div id=\"{{ val }}\">, use <div :id=\"val\">.\n\n 34 | </button>\n 35 | \n 36 | <a class=\"dropdown-item\" href=\"{% url 'view_export' %}?r={{ recipe.pk }}\" target=\"_blank\"\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n 37 | rel=\"noopener noreferrer\"><i class=\"fas fa-file-export fa-fw\"></i> {% trans 'Export' %}</a>\n 38 | {% if recipe.internal %}\n"}
|
Loading…
Reference in New Issue
Block a user