loads of work...

This commit is contained in:
2019-04-28 15:41:00 +02:00
parent 87d683d4af
commit d31b2dd348
21 changed files with 447 additions and 318 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
__pycache__ __pycache__
db.sqlite3 db.sqlite3
run_dev.sh run_dev.sh
**/migrations/

View File

@@ -18,6 +18,7 @@ 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 planning import views as planning_views
from rest_framework_nested import routers from rest_framework_nested import routers
router = routers.DefaultRouter() router = routers.DefaultRouter()
@@ -32,6 +33,8 @@ amounts_router.register(
base_name="recipe-ingdt-amounts" base_name="recipe-ingdt-amounts"
) )
router.register(r'planning', planning_views.MealViewSet)
urlpatterns = [ urlpatterns = [
path('admin/', admin.site.urls), path('admin/', admin.site.urls),

View File

@@ -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,//
} }
}, },

View File

@@ -151,4 +151,5 @@ export default {
ingredients: mountNestedEndPoint( ingredients: mountNestedEndPoint(
`${baseUrl}/recips/*/amounts/` `${baseUrl}/recips/*/amounts/`
), ),
meals: mountEndPoint(`${baseUrl}/planning/`),
} }

View File

@@ -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>

View File

@@ -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"

View 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>

View 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>

View 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>

View 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>

View File

@@ -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: {

View File

@@ -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)))
}, },

View File

@@ -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 => {

View File

@@ -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>

View File

@@ -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 }
] ]

View File

@@ -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>
<v-list-tile v-else
:key="index">
<v-list-tile-content>
<v-list-tile-sub-title
>{{ item.subtitle }}</v-list-tile-sub-title>
<v-list-tile-title
>{{ item.title }}</v-list-tile-title>
</v-list-tile-content>
<v-list-tile-action v-show="item.title">
<v-btn icon color="primary" small
:to="`/recipes/id/${item.id}`"
><v-icon>remove_red_eye</v-icon></v-btn>
</v-list-tile-action>
<v-list-tile-action>
<v-btn icon color="warning" small>
<v-icon>
{{ item.title ? 'delete' : 'add' }}
</v-icon>
</v-btn>
</v-list-tile-action>
</v-list-tile>
</template>
</v-list>
</template> </template>
<script> <script>
import WeekView from '../components/planning/Week'
const MockPlanning = { import OptionsView from '../components/planning/Options'
"Lundi": { import api from '../api.js'
"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 { export default {
data() { components: { WeekView, OptionsView },
var items = []; data () {
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 { return {
items, meals: [],
toolActions: [ error: null,
{ icon: "find_replace", color: "success" },
],
planning: MockPlanning,
} }
},
methods: {
fetchPlanning () {
api.meals.list()
.then(data => {
this.meals = data
})
.catch(err => this.error = "Error fetching planning" + JSON.stringify(err))
},
},
created () {
this.fetchPlanning();
} }
} }
</script> </script>
<style scoped>
</style>

View File

@@ -1,14 +1,13 @@
<template> <template>
<router-view></router-view> <keep-alive>
<router-view :key="$route.fullPath"></router-view>
</keep-alive>
</template> </template>
<script> <script>
export default {
export default { data () {
data: () => ({ return {}
}), },
} }
</script> </script>
<style scoped>
</style>

View File

@@ -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"}]}}

View File

@@ -2,11 +2,19 @@ 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'),
(1, 'Déjeuner'),
(2, 'Goûter'),
(3, 'Diner'),
))
dishes = models.ManyToManyField(
'recipe_book.Recipe',
)
class Meta:
class DayMeals(models.Model): constraints = [
midi = models.ForeignKey( models.UniqueConstraint(fields=['day', 'kind'], name="unique_constraint"),
Recipe, ]
on_delete=models.CASCADE )

8
planning/serializers.py Normal file
View 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

View File

@@ -1,3 +1,9 @@
from django.shortcuts import render from django.shortcuts import render
from rest_framework import viewsets
from .serializers import MealSerializer
from .models import Meal
# Create your views here. # Create your views here.
class MealViewSet(viewsets.ModelViewSet):
queryset = Meal.objects.all()
serializer_class = MealSerializer