loads of work...
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
__pycache__
|
__pycache__
|
||||||
db.sqlite3
|
db.sqlite3
|
||||||
run_dev.sh
|
run_dev.sh
|
||||||
|
**/migrations/
|
||||||
|
|||||||
@@ -1,46 +1,49 @@
|
|||||||
"""cookAssistant URL Configuration
|
"""cookAssistant URL Configuration
|
||||||
|
|
||||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||||
https://docs.djangoproject.com/en/2.2/topics/http/urls/
|
https://docs.djangoproject.com/en/2.2/topics/http/urls/
|
||||||
Examples:
|
Examples:
|
||||||
Function views
|
Function views
|
||||||
1. Add an import: from my_app import views
|
1. Add an import: from my_app import views
|
||||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||||
Class-based views
|
Class-based views
|
||||||
1. Add an import: from other_app.views import Home
|
1. Add an import: from other_app.views import Home
|
||||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||||
Including another URLconf
|
Including another URLconf
|
||||||
1. Import the include() function: from django.urls import include, path
|
1. Import the include() function: from django.urls import include, path
|
||||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||||
"""
|
"""
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.urls import path, include
|
from django.urls import path, include
|
||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
from recipe_book import views
|
from recipe_book import views
|
||||||
from rest_framework_nested import routers
|
from planning import views as planning_views
|
||||||
|
from rest_framework_nested import routers
|
||||||
router = routers.DefaultRouter()
|
|
||||||
router.register(r'recips', views.RecipesViewSet)
|
router = routers.DefaultRouter()
|
||||||
router.register(r'ingdts', views.IngredientViewSet)
|
router.register(r'recips', views.RecipesViewSet)
|
||||||
|
router.register(r'ingdts', views.IngredientViewSet)
|
||||||
amounts_router = routers.NestedDefaultRouter(
|
|
||||||
router, r'recips', lookup="recipe"
|
amounts_router = routers.NestedDefaultRouter(
|
||||||
)
|
router, r'recips', lookup="recipe"
|
||||||
amounts_router.register(
|
)
|
||||||
r'amounts', views.IngredientWAmountViewSet,
|
amounts_router.register(
|
||||||
base_name="recipe-ingdt-amounts"
|
r'amounts', views.IngredientWAmountViewSet,
|
||||||
)
|
base_name="recipe-ingdt-amounts"
|
||||||
|
)
|
||||||
|
|
||||||
urlpatterns = [
|
router.register(r'planning', planning_views.MealViewSet)
|
||||||
path('admin/', admin.site.urls),
|
|
||||||
path('api/', include(router.urls)),
|
|
||||||
path('api/', include(amounts_router.urls)),
|
urlpatterns = [
|
||||||
path('',
|
path('admin/', admin.site.urls),
|
||||||
TemplateView.as_view(
|
path('api/', include(router.urls)),
|
||||||
template_name="application.html"
|
path('api/', include(amounts_router.urls)),
|
||||||
),
|
path('',
|
||||||
name="app"
|
TemplateView.as_view(
|
||||||
),
|
template_name="application.html"
|
||||||
]
|
),
|
||||||
|
name="app"
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|||||||
@@ -1,14 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-app dark>
|
<v-app light>
|
||||||
<v-navigation-drawer app clipped mini-variant v-model="showNav">
|
<v-navigation-drawer app clipped mini-variant v-model="showNav">
|
||||||
<v-list>
|
<v-list>
|
||||||
<v-list-tile>
|
|
||||||
<v-list-tile-title>
|
|
||||||
CookAssistant
|
|
||||||
</v-list-tile-title>
|
|
||||||
</v-list-tile>
|
|
||||||
</v-list>
|
|
||||||
<v-list dense>
|
|
||||||
<v-list-tile
|
<v-list-tile
|
||||||
v-for="link in links"
|
v-for="link in links"
|
||||||
:key="link.path"
|
:key="link.path"
|
||||||
@@ -24,7 +17,7 @@
|
|||||||
</v-list>
|
</v-list>
|
||||||
</v-navigation-drawer>
|
</v-navigation-drawer>
|
||||||
|
|
||||||
<v-toolbar app clipped-left>
|
<v-toolbar app flat color="light-green" clipped-left>
|
||||||
<v-toolbar-side-icon
|
<v-toolbar-side-icon
|
||||||
@click.stop="showNav = !showNav"
|
@click.stop="showNav = !showNav"
|
||||||
></v-toolbar-side-icon>
|
></v-toolbar-side-icon>
|
||||||
@@ -47,9 +40,7 @@
|
|||||||
</v-toolbar>
|
</v-toolbar>
|
||||||
|
|
||||||
<v-content>
|
<v-content>
|
||||||
<v-container fluid>
|
<router-view></router-view>
|
||||||
<router-view></router-view>
|
|
||||||
</v-container>
|
|
||||||
</v-content>
|
</v-content>
|
||||||
</v-app>
|
</v-app>
|
||||||
</template>
|
</template>
|
||||||
@@ -58,12 +49,6 @@
|
|||||||
|
|
||||||
import NotFound from './routes/NotFound'
|
import NotFound from './routes/NotFound'
|
||||||
|
|
||||||
export const RecipeCategories = {
|
|
||||||
0: "Petit-déjeuner",
|
|
||||||
1: "Entrée",
|
|
||||||
2: "Plat",
|
|
||||||
3: "Dessert"
|
|
||||||
}
|
|
||||||
// *** TOOLS ***
|
// *** TOOLS ***
|
||||||
//
|
//
|
||||||
// * create_recipe: go to creation page
|
// * create_recipe: go to creation page
|
||||||
@@ -83,7 +68,6 @@ export default {
|
|||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
links: [],
|
links: [],
|
||||||
categories: RecipeCategories,
|
|
||||||
showNav: null,//
|
showNav: null,//
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -151,4 +151,5 @@ export default {
|
|||||||
ingredients: mountNestedEndPoint(
|
ingredients: mountNestedEndPoint(
|
||||||
`${baseUrl}/recips/*/amounts/`
|
`${baseUrl}/recips/*/amounts/`
|
||||||
),
|
),
|
||||||
|
meals: mountEndPoint(`${baseUrl}/planning/`),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
<template>
|
|
||||||
<!-- Categories list -->
|
|
||||||
<v-tabs v-if="showTabs" :mandatory="false">
|
|
||||||
<v-tab v-for="key in Object.keys(categories)"
|
|
||||||
:key="`cat-${key}`"
|
|
||||||
:to="`/recipes/category/${key}`">
|
|
||||||
{{ categories[key] }}
|
|
||||||
</v-tab>
|
|
||||||
</v-tabs>
|
|
||||||
<v-btn v-else
|
|
||||||
icon
|
|
||||||
@click="$router.go(-1)"
|
|
||||||
><v-icon>arrow_back</v-icon></v-btn>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
|
|
||||||
import { RecipeCategories } from '../App'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
data: () => ({
|
|
||||||
categories: RecipeCategories,
|
|
||||||
}),
|
|
||||||
computed: {
|
|
||||||
showTabs: function() {
|
|
||||||
return !(this.$route.path.startsWith("/recipes/id"))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
</style>
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-select v-if="type == 'choice'"
|
<v-select v-if="type == 'choice'"
|
||||||
:label="label"
|
:label="label"
|
||||||
placeholder="----"
|
|
||||||
:items="choices"
|
:items="choices"
|
||||||
item-text="display_name"
|
item-text="display_name"
|
||||||
:value="value"
|
:value="value"
|
||||||
|
|||||||
27
frontend/src/components/planning/Day.vue
Normal file
27
frontend/src/components/planning/Day.vue
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<template>
|
||||||
|
<v-card>
|
||||||
|
<v-card-title>
|
||||||
|
<span class="headline">{{ day }}</span>
|
||||||
|
</v-card-title>
|
||||||
|
<v-divider light></v-divider>
|
||||||
|
<v-card-text>
|
||||||
|
<span>Recette n°1</span>
|
||||||
|
<span>Recette n°2</span>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-actions>
|
||||||
|
<v-btn icon><v-icon>delete</v-icon></v-btn>
|
||||||
|
</v-card-actions>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: ['day', 'meals'],
|
||||||
|
data () {
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style>
|
||||||
|
|
||||||
|
</style>
|
||||||
19
frontend/src/components/planning/Meal.vue
Normal file
19
frontend/src/components/planning/Meal.vue
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<template>
|
||||||
|
<v-card-text v-if="item">
|
||||||
|
<p class="body-2 ma-0 text-xs-center" v-for="(dish, id) in item.dishes" :key="id">
|
||||||
|
{{ dish.name }}
|
||||||
|
</p>
|
||||||
|
</v-card-text>
|
||||||
|
<v-card-text v-else>()</v-card-text>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['item'],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
65
frontend/src/components/planning/Options.vue
Normal file
65
frontend/src/components/planning/Options.vue
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<template>
|
||||||
|
<v-container grid-list-lg>
|
||||||
|
<v-layout wrap>
|
||||||
|
<v-flex>
|
||||||
|
<h6 class="title mb-3">Meal Options</h6>
|
||||||
|
<v-card :flat="!isMealUsed">
|
||||||
|
<v-container>
|
||||||
|
<v-layout>
|
||||||
|
<v-card-title>
|
||||||
|
<h6 class="title">
|
||||||
|
Repas du { date }
|
||||||
|
</h6>
|
||||||
|
</v-card-title>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-flex shrink>
|
||||||
|
<v-switch
|
||||||
|
v-model="isMealUsed"
|
||||||
|
label="Used ?"
|
||||||
|
color="light-green">
|
||||||
|
</v-switch>
|
||||||
|
</v-flex>
|
||||||
|
</v-layout>
|
||||||
|
</v-container>
|
||||||
|
<v-card-text>
|
||||||
|
Contenu
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-flex>
|
||||||
|
<v-flex>
|
||||||
|
<h6 class="title mb-3">Qui mange ?</h6>
|
||||||
|
<v-card flat>
|
||||||
|
<v-card-text>Liste des personnes</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-flex>
|
||||||
|
<v-flex>
|
||||||
|
<h6 class="title mb-3">Règles spéciales</h6>
|
||||||
|
<v-card :flat="!isIngredientListValid" hover class="pa-2" >
|
||||||
|
<v-switch v-model="isIngredientsRuleUsed"
|
||||||
|
label="Ingrédients à utiliser">
|
||||||
|
</v-switch>
|
||||||
|
<v-textarea label="Liste"></v-textarea>
|
||||||
|
</v-card>
|
||||||
|
<v-card :flat="!isNutritionalRuleUsed" hover class="pa-2 mt-2">
|
||||||
|
<v-switch v-model="isNutritionalRuleUsed"
|
||||||
|
label="Valeurs nutrionnelles"></v-switch>
|
||||||
|
</v-card>
|
||||||
|
</v-flex>
|
||||||
|
</v-layout>
|
||||||
|
</v-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
isMealUsed: false,
|
||||||
|
isNutritionalRuleUsed: false,
|
||||||
|
isIngredientsRuleUsed: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isIngredientListValid () { return true; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
65
frontend/src/components/planning/Week.vue
Normal file
65
frontend/src/components/planning/Week.vue
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<template>
|
||||||
|
<v-card class="pa-2">
|
||||||
|
<v-calendar
|
||||||
|
start="2019-04-22"
|
||||||
|
type="week"
|
||||||
|
locale="fr-fr"
|
||||||
|
first-interval=2
|
||||||
|
interval-count=4
|
||||||
|
interval-minutes=240
|
||||||
|
interval-height=150
|
||||||
|
:show-interval-label="(i) => false"
|
||||||
|
>
|
||||||
|
<template v-slot:interval="{ date, hour }">
|
||||||
|
<v-card flat>
|
||||||
|
<v-toolbar flat dense>
|
||||||
|
<v-toolbar-item>{{ intervals_heading[indexFromHour(hour)] }}</v-toolbar-item>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
|
<v-btn icon flat color="light-green"><v-icon color="grey">more_horiz</v-icon></v-btn>
|
||||||
|
</v-toolbar>
|
||||||
|
<MealSlot v-if="mealsByDay[date]" :item="mealsByDay[date][indexFromHour(hour)]" />
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
</v-calendar>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import MealSlot from './Meal'
|
||||||
|
const IntervalsHeading = ['Petit-déjeuner', 'Déjeuner', 'Goûter', 'Diner']
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['meals'],
|
||||||
|
components: { MealSlot },
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
intervals_heading: IntervalsHeading,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// Maps interval's hour to index of a meal inside 'day' array
|
||||||
|
// /!\ Tighly coupled to interval-* values
|
||||||
|
indexFromHour (hour) {
|
||||||
|
return hour / 4 - 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
// Arrange meals as a 4-sized array for each day
|
||||||
|
mealsByDay () {
|
||||||
|
var result = {};
|
||||||
|
for (var idx in this.meals) {
|
||||||
|
var meal = this.meals[idx]
|
||||||
|
var day = meal.day
|
||||||
|
var meal_idx = Number(meal.kind)
|
||||||
|
if (!(day in result)) {
|
||||||
|
result[day] = [null, null, null, null]
|
||||||
|
}
|
||||||
|
result[day][meal_idx] = meal
|
||||||
|
}
|
||||||
|
console.log('computed', result)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -72,6 +72,11 @@
|
|||||||
},
|
},
|
||||||
markUpdated () {
|
markUpdated () {
|
||||||
this.updated = true
|
this.updated = true
|
||||||
|
},
|
||||||
|
resetFields () {
|
||||||
|
this.item.ingredient = {}
|
||||||
|
this.item.amount = null
|
||||||
|
this.item.unit = null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
|||||||
@@ -1,26 +1,36 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-card class="pa-1">
|
<v-layout column>
|
||||||
|
<v-card>
|
||||||
|
<v-card-title>
|
||||||
|
<strong>Ingrédients</strong>
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<ingredient-details
|
||||||
|
v-for="ingdt in edited_ingdts"
|
||||||
|
:item="ingdt"
|
||||||
|
:fields="fields"
|
||||||
|
:editing="editing"
|
||||||
|
@delete="deleteItem"
|
||||||
|
@save="saveItem"
|
||||||
|
/>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
<!-- New ingredient in editing mode -->
|
||||||
|
<v-card hover v-if="editing">
|
||||||
<v-card-title>
|
<v-card-title>
|
||||||
<strong>Ingrédients</strong>
|
Nouveau
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
|
<v-card-text class="py-0">
|
||||||
<ingredient-details
|
<ingredient-details
|
||||||
v-for="ingdt in edited_ingdts"
|
:key="addItemKey"
|
||||||
:item="ingdt"
|
|
||||||
:fields="fields"
|
|
||||||
:editing="editing"
|
|
||||||
@delete="deleteItem"
|
|
||||||
@save="saveItem"
|
|
||||||
/>
|
|
||||||
<!-- New ingredient in editing mode -->
|
|
||||||
<p v-if="editing">
|
|
||||||
(+) <ingredient-details
|
|
||||||
:item="newItem()"
|
:item="newItem()"
|
||||||
:fields="fields"
|
:fields="fields"
|
||||||
:editing="editing"
|
:editing="editing"
|
||||||
@create="createItem"
|
@create="createItem"
|
||||||
/>
|
></ingredient-details>
|
||||||
</p>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
</v-layout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -28,9 +38,9 @@
|
|||||||
import Ingredient from './Ingredient'
|
import Ingredient from './Ingredient'
|
||||||
function NullIngredient (recipe_id) {
|
function NullIngredient (recipe_id) {
|
||||||
this.recipe = recipe_id
|
this.recipe = recipe_id
|
||||||
this.ingredient = { name: "" }
|
this.ingredient = { name: null }
|
||||||
this.amount = 0
|
this.amount = null
|
||||||
this.unit = ""
|
this.unit = null
|
||||||
this.display = ""
|
this.display = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,6 +53,12 @@
|
|||||||
return {
|
return {
|
||||||
fields: {},
|
fields: {},
|
||||||
edited_ingdts: [],
|
edited_ingdts: [],
|
||||||
|
newItemKey: 0,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
addItemKey () {
|
||||||
|
return `add-item-${this.newItemKey}`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
@@ -70,6 +86,7 @@
|
|||||||
.then(data => {
|
.then(data => {
|
||||||
this.edited_ingdts.push(data)
|
this.edited_ingdts.push(data)
|
||||||
//TODO: reset additionnal Ingredient
|
//TODO: reset additionnal Ingredient
|
||||||
|
this.newItemKey += 1
|
||||||
})
|
})
|
||||||
.catch(err => comp.errors.push(JSON.stringify(err)))
|
.catch(err => comp.errors.push(JSON.stringify(err)))
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,8 +5,36 @@ Has read/edit modes
|
|||||||
-->
|
-->
|
||||||
<template>
|
<template>
|
||||||
<v-container fluid>
|
<v-container fluid>
|
||||||
<v-layout wrap>
|
<v-layout wrap align-items-center>
|
||||||
<v-flex xs10> <!-- Heading -->
|
<v-flex shrink> <!-- Buttons -->
|
||||||
|
<v-layout column>
|
||||||
|
<!-- Add to planning -->
|
||||||
|
<v-bottom-sheet v-show="!editing"
|
||||||
|
v-model="sheetSelectSlot"
|
||||||
|
>
|
||||||
|
<template v-slot:activator>
|
||||||
|
<v-btn fab dark small
|
||||||
|
color="success"
|
||||||
|
><v-icon>add</v-icon></v-btn>
|
||||||
|
</template>
|
||||||
|
<!-- Put a meal picker here... -->
|
||||||
|
<v-card elevation="10" style="min-height: 300px;">
|
||||||
|
<v-card-text>
|
||||||
|
Content of bottom sheet
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</v-bottom-sheet>
|
||||||
|
<!-- Edit mode -->
|
||||||
|
<v-btn fab dark small
|
||||||
|
color="error"
|
||||||
|
@click="switchEdit"
|
||||||
|
>
|
||||||
|
<v-icon v-if="editing">save</v-icon>
|
||||||
|
<v-icon v-else>edit</v-icon>
|
||||||
|
</v-btn>
|
||||||
|
</v-layout>
|
||||||
|
</v-flex>
|
||||||
|
<v-flex> <!-- Heading -->
|
||||||
<v-alert v-for="(error,idx) in errors"
|
<v-alert v-for="(error,idx) in errors"
|
||||||
:key="`alert-${idx}`"
|
:key="`alert-${idx}`"
|
||||||
:value="true">
|
:value="true">
|
||||||
@@ -31,51 +59,17 @@ Has read/edit modes
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</v-flex>
|
</v-flex>
|
||||||
<v-flex xs2> <!-- Buttons -->
|
|
||||||
<!-- Add to planning -->
|
|
||||||
<v-bottom-sheet v-show="!editing"
|
|
||||||
v-model="sheetSelectSlot"
|
|
||||||
>
|
|
||||||
<template v-slot:activator>
|
|
||||||
<v-btn
|
|
||||||
fab dark
|
|
||||||
color="success"
|
|
||||||
><v-icon>add</v-icon></v-btn>
|
|
||||||
</template>
|
|
||||||
<!-- Put a meal picker here... -->
|
|
||||||
<v-card elevation="10" style="min-height: 300px;">
|
|
||||||
<v-card-text>
|
|
||||||
Content of bottom sheet
|
|
||||||
</v-card-text>
|
|
||||||
</v-card>
|
|
||||||
</v-bottom-sheet>
|
|
||||||
<!-- Edit mode -->
|
|
||||||
<v-fab-transition>
|
|
||||||
<v-btn
|
|
||||||
fab dark
|
|
||||||
:small="!editing"
|
|
||||||
color="error"
|
|
||||||
@click="switchEdit"
|
|
||||||
>
|
|
||||||
<v-icon v-if="editing">save</v-icon>
|
|
||||||
<v-icon v-else>edit</v-icon>
|
|
||||||
</v-btn>
|
|
||||||
</v-fab-transition>
|
|
||||||
</v-flex>
|
|
||||||
<v-flex xs12 md4>
|
|
||||||
<IngredientList v-if="!isLoading"
|
|
||||||
:editing="editing"
|
|
||||||
:recipe="item.id"
|
|
||||||
:ingredients="item.ingredients"
|
|
||||||
/>
|
|
||||||
</v-flex>
|
|
||||||
</v-layout>
|
</v-layout>
|
||||||
|
<IngredientList v-if="!isLoading"
|
||||||
|
:editing="editing"
|
||||||
|
:recipe="item.id"
|
||||||
|
:ingredients="item.ingredients"
|
||||||
|
/>
|
||||||
</v-container>
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import RField from '../RessourceField'
|
import RField from '../RessourceField'
|
||||||
import { RecipeCategories } from '../../App'
|
|
||||||
import api from '../../api.js'
|
import api from '../../api.js'
|
||||||
import NotFound from '../../routes/NotFound'
|
import NotFound from '../../routes/NotFound'
|
||||||
const IngredientList = import('./IngredientList')
|
const IngredientList = import('./IngredientList')
|
||||||
@@ -98,22 +92,12 @@ export default {
|
|||||||
RField,
|
RField,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
// Transform categories for v-select component
|
|
||||||
var select_items = []
|
|
||||||
Object.keys(RecipeCategories)
|
|
||||||
.map(function(key) {
|
|
||||||
select_items.push({
|
|
||||||
text: RecipeCategories[key],
|
|
||||||
value: key,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
sheetSelectSlot: false,
|
sheetSelectSlot: false,
|
||||||
editing: false,
|
editing: false,
|
||||||
select_items,
|
select_items: [],
|
||||||
categories: RecipeCategories,
|
categories: [],
|
||||||
errors: [],
|
errors: [],
|
||||||
// Will be populated by initRecipeItem method
|
// Will be populated by initRecipeItem method
|
||||||
item: new NullRecipe,
|
item: new NullRecipe,
|
||||||
@@ -165,16 +149,16 @@ export default {
|
|||||||
.then(() => this.isLoading = false)
|
.then(() => this.isLoading = false)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateRecip: function(form_data) {
|
|
||||||
console.log(form_data);
|
|
||||||
var messages = [];
|
|
||||||
return messages;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
api.recipes.options()
|
api.recipes.options()
|
||||||
.then(data => this.fields = data.actions.POST)
|
.then(data => {
|
||||||
.then(() => console.log(this.fields))
|
this.fields = data.actions.POST
|
||||||
|
// Transform categories into selectable
|
||||||
|
var cats = this.fields.category.choices
|
||||||
|
Object.keys(cats)
|
||||||
|
.map( (key) => this.categories[key] = cats[key].display_name )
|
||||||
|
})
|
||||||
},
|
},
|
||||||
beforeRouteEnter (to, from, next) {
|
beforeRouteEnter (to, from, next) {
|
||||||
next(vm => {
|
next(vm => {
|
||||||
|
|||||||
@@ -1,18 +1,31 @@
|
|||||||
/* List view of recipes */
|
/* List view of recipes */
|
||||||
<template>
|
<template>
|
||||||
<v-list>
|
<v-card>
|
||||||
<template v-for="(item, idx) in items">
|
<!-- Categories list -->
|
||||||
<v-list-tile
|
<v-tabs fixed-tabs slider-color="light-green" v-model="active" :mandatory="false">
|
||||||
:key="item.title"
|
<template v-for="(name, id) in categories">
|
||||||
:to="`/recipes/id/${item.id}`">
|
<v-tab
|
||||||
<v-list-tile-content>
|
:key="id" >
|
||||||
{{item.name}}
|
{{ name }}
|
||||||
</v-list-tile-content>
|
</v-tab>
|
||||||
</v-list-tile>
|
<v-tab-item :key="id" transition="slide-x-transition">
|
||||||
<v-divider v-if="idx < (items.length - 1)"
|
<v-card flat>
|
||||||
:key="idx"></v-divider>
|
<v-list>
|
||||||
</template>
|
<template v-for="(item, idx) in content[id]">
|
||||||
</v-list>
|
<v-list-tile
|
||||||
|
:key="item.title"
|
||||||
|
:to="`/recipes/id/${item.id}`">
|
||||||
|
<v-list-tile-content>
|
||||||
|
{{item.name}}
|
||||||
|
</v-list-tile-content>
|
||||||
|
</v-list-tile>
|
||||||
|
</template>
|
||||||
|
</v-list>
|
||||||
|
</v-card>
|
||||||
|
</v-tab-item>
|
||||||
|
</template>
|
||||||
|
</v-tabs>
|
||||||
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@@ -21,10 +34,12 @@ import api from '../../api.js'
|
|||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
active: null,
|
||||||
|
categories: {},
|
||||||
// Recipes from db grouped by categories id
|
// Recipes from db grouped by categories id
|
||||||
content: {
|
content: {
|
||||||
0: [], 1: [], 2: [], 3: []
|
0: [], 1: [], 2: [], 3: []
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -32,7 +47,7 @@ export default {
|
|||||||
return this.content[this.$route.params.cat];
|
return this.content[this.$route.params.cat];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created: function() {
|
mounted: function() {
|
||||||
api.recipes.list()
|
api.recipes.list()
|
||||||
.then(data => {
|
.then(data => {
|
||||||
for (var idx in data) {
|
for (var idx in data) {
|
||||||
@@ -40,6 +55,17 @@ export default {
|
|||||||
this.content[parseInt(obj.category)].push(obj);
|
this.content[parseInt(obj.category)].push(obj);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.then(() => console.log('list gathered !'))
|
||||||
|
|
||||||
|
api.recipes.options()
|
||||||
|
.then(opts => {
|
||||||
|
let cats = opts.actions.POST.category.choices
|
||||||
|
this.categories = {}
|
||||||
|
for (var idx in cats) {
|
||||||
|
let cat = cats[idx]
|
||||||
|
this.categories[parseInt(cat.value)] = cat.display_name
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import Planning from './routes/Planning'
|
|||||||
|
|
||||||
import RecipeDetails from './components/recipes/RecipeDetails'
|
import RecipeDetails from './components/recipes/RecipeDetails'
|
||||||
import RecipeList from './components/recipes/RecipeList'
|
import RecipeList from './components/recipes/RecipeList'
|
||||||
import CategoryTabs from './components/CategoryTabs'
|
|
||||||
|
|
||||||
Vue.use(Router)
|
Vue.use(Router)
|
||||||
|
|
||||||
@@ -20,17 +19,16 @@ const router = new Router({
|
|||||||
},
|
},
|
||||||
|
|
||||||
{ path: '/recipes',
|
{ path: '/recipes',
|
||||||
components: { default: Recipes,
|
component: Recipes,
|
||||||
extension: CategoryTabs, },
|
|
||||||
meta: { title: "Recettes",
|
meta: { title: "Recettes",
|
||||||
icon:"book" },
|
icon:"book" },
|
||||||
children: [
|
children: [
|
||||||
|
{ path: '',
|
||||||
|
component: RecipeList,
|
||||||
|
meta: {}},
|
||||||
{ path: 'id/:id',
|
{ path: 'id/:id',
|
||||||
component: RecipeDetails,
|
component: RecipeDetails,
|
||||||
meta: { subtitle: "Détails", } },
|
meta: { subtitle: "Détails", } },
|
||||||
{ path: 'category/:cat',
|
|
||||||
component: RecipeList,
|
|
||||||
meta: { subtitle: "Liste", } },
|
|
||||||
{ path: '*',
|
{ path: '*',
|
||||||
component: NotFound }
|
component: NotFound }
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,91 +1,38 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-list two-line overflow-y>
|
<v-container>
|
||||||
<template v-for="(item, index) in items">
|
<v-alert :value="!(error == null)">{{ error }}</v-alert>
|
||||||
<v-subheader v-if="item.header"
|
<WeekView :meals="meals" />
|
||||||
:key="item.header"
|
<OptionsView />
|
||||||
>{{item.header}}</v-subheader>
|
</v-container>
|
||||||
<v-divider v-else-if="item.divider"></v-divider>
|
</template>
|
||||||
<v-list-tile v-else
|
|
||||||
:key="index">
|
<script>
|
||||||
<v-list-tile-content>
|
import WeekView from '../components/planning/Week'
|
||||||
<v-list-tile-sub-title
|
import OptionsView from '../components/planning/Options'
|
||||||
>{{ item.subtitle }}</v-list-tile-sub-title>
|
import api from '../api.js'
|
||||||
<v-list-tile-title
|
|
||||||
>{{ item.title }}</v-list-tile-title>
|
export default {
|
||||||
</v-list-tile-content>
|
components: { WeekView, OptionsView },
|
||||||
<v-list-tile-action v-show="item.title">
|
data () {
|
||||||
<v-btn icon color="primary" small
|
return {
|
||||||
:to="`/recipes/id/${item.id}`"
|
meals: [],
|
||||||
><v-icon>remove_red_eye</v-icon></v-btn>
|
error: null,
|
||||||
</v-list-tile-action>
|
}
|
||||||
<v-list-tile-action>
|
},
|
||||||
<v-btn icon color="warning" small>
|
methods: {
|
||||||
<v-icon>
|
fetchPlanning () {
|
||||||
{{ item.title ? 'delete' : 'add' }}
|
api.meals.list()
|
||||||
</v-icon>
|
.then(data => {
|
||||||
</v-btn>
|
this.meals = data
|
||||||
</v-list-tile-action>
|
})
|
||||||
</v-list-tile>
|
.catch(err => this.error = "Error fetching planning" + JSON.stringify(err))
|
||||||
</template>
|
},
|
||||||
</v-list>
|
},
|
||||||
</template>
|
created () {
|
||||||
|
this.fetchPlanning();
|
||||||
<script>
|
}
|
||||||
|
|
||||||
const MockPlanning = {
|
}
|
||||||
"Lundi": {
|
</script>
|
||||||
"Midi": "Raclette",
|
|
||||||
"Soir": "Soupe",
|
|
||||||
},
|
|
||||||
"Mardi": {
|
|
||||||
"Midi": null,
|
|
||||||
"Soir": null,
|
|
||||||
},
|
|
||||||
"Mercredi": {
|
|
||||||
"Midi": null,
|
|
||||||
"Soir": null,
|
|
||||||
},
|
|
||||||
"Jeudi": {
|
|
||||||
"Midi": null,
|
|
||||||
"Soir": null,
|
|
||||||
},
|
|
||||||
"Vendredi": {
|
|
||||||
"Midi": null,
|
|
||||||
"Soir": null,
|
|
||||||
},
|
|
||||||
"Samedi": {
|
|
||||||
"Midi": null,
|
|
||||||
"Soir": null,
|
|
||||||
},
|
|
||||||
"Dimanche": {
|
|
||||||
"Midi": null,
|
|
||||||
"Soir": null,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
var items = [];
|
|
||||||
|
|
||||||
for (var day in MockPlanning) {
|
|
||||||
items.push({ divider: true })
|
|
||||||
items.push({ header: day });
|
|
||||||
for (var meal in MockPlanning[day]) {
|
|
||||||
items.push({ id: 0, subtitle: meal, title: MockPlanning[day][meal] });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
items,
|
|
||||||
toolActions: [
|
|
||||||
{ icon: "find_replace", color: "success" },
|
|
||||||
],
|
|
||||||
planning: MockPlanning,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<router-view></router-view>
|
<keep-alive>
|
||||||
</template>
|
<router-view :key="$route.fullPath"></router-view>
|
||||||
|
</keep-alive>
|
||||||
<script>
|
</template>
|
||||||
|
|
||||||
export default {
|
<script>
|
||||||
data: () => ({
|
export default {
|
||||||
}),
|
data () {
|
||||||
}
|
return {}
|
||||||
|
},
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"status":"done","publicPath":"http://localhost:8080/","chunks":{"null":[{"name":"0.js","publicPath":"http://localhost:8080/0.js","path":"/home/artus/Documents/cookAssistant/frontend/dist/0.js"}],"app":[{"name":"app.js","publicPath":"http://localhost:8080/app.js","path":"/home/artus/Documents/cookAssistant/frontend/dist/app.js"},{"name":"app.bb93e89c95f5e29b766f.hot-update.js","publicPath":"http://localhost:8080/app.bb93e89c95f5e29b766f.hot-update.js","path":"/home/artus/Documents/cookAssistant/frontend/dist/app.bb93e89c95f5e29b766f.hot-update.js"}]}}
|
{"status":"done","publicPath":"http://localhost:8080/","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 tag <v-card> has no matching end tag.\n\n 12 | >\n 13 | <template v-slot:interval=\"{ date, hour }\">\n 14 | <v-card>\n | ^^^^^^^^\n 15 | <v-toolbar>\n 16 | <v-toolbar-title>{{ intervals_heading[indexFromHour(hour)] }}</v-toolbar-title>\n","chunks":{"null":[{"name":"0.js","publicPath":"http://localhost:8080/0.js","path":"/home/artus/Documents/cookAssistant/frontend/dist/0.js"}],"app":[{"name":"app.js","publicPath":"http://localhost:8080/app.js","path":"/home/artus/Documents/cookAssistant/frontend/dist/app.js"},{"name":"app.5ec28f6394212113d31d.hot-update.js","publicPath":"http://localhost:8080/app.5ec28f6394212113d31d.hot-update.js","path":"/home/artus/Documents/cookAssistant/frontend/dist/app.5ec28f6394212113d31d.hot-update.js"}]}}
|
||||||
@@ -1,12 +1,20 @@
|
|||||||
from django.db import models
|
from django.db import models
|
||||||
from recipe_book.models import Recipe
|
from recipe_book.models import Recipe
|
||||||
# Create your models here.
|
# Create your models here.
|
||||||
|
|
||||||
class WeekPlanning(models.Model):
|
class Meal(models.Model):
|
||||||
pass
|
day = models.DateField()
|
||||||
|
kind = models.SmallIntegerField(choices=(
|
||||||
|
(0, 'Petit-déjeuner'),
|
||||||
class DayMeals(models.Model):
|
(1, 'Déjeuner'),
|
||||||
midi = models.ForeignKey(
|
(2, 'Goûter'),
|
||||||
Recipe,
|
(3, 'Diner'),
|
||||||
on_delete=models.CASCADE )
|
))
|
||||||
|
dishes = models.ManyToManyField(
|
||||||
|
'recipe_book.Recipe',
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
constraints = [
|
||||||
|
models.UniqueConstraint(fields=['day', 'kind'], name="unique_constraint"),
|
||||||
|
]
|
||||||
|
|||||||
8
planning/serializers.py
Normal file
8
planning/serializers.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
from rest_framework import serializers as ser
|
||||||
|
from .models import Meal
|
||||||
|
|
||||||
|
class MealSerializer(ser.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Meal
|
||||||
|
fields = ('id', 'day', 'kind', 'dishes')
|
||||||
|
depth = 1
|
||||||
@@ -1,3 +1,9 @@
|
|||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
|
from rest_framework import viewsets
|
||||||
# Create your views here.
|
from .serializers import MealSerializer
|
||||||
|
from .models import Meal
|
||||||
|
|
||||||
|
# Create your views here.
|
||||||
|
class MealViewSet(viewsets.ModelViewSet):
|
||||||
|
queryset = Meal.objects.all()
|
||||||
|
serializer_class = MealSerializer
|
||||||
|
|||||||
Reference in New Issue
Block a user