meal plan WIP

This commit is contained in:
vabene1111
2020-01-17 16:02:14 +01:00
parent 2afec837a4
commit 7449380434
13 changed files with 226 additions and 20 deletions

View File

@ -1,16 +1,27 @@
from dal_select2.widgets import ModelSelect2
from django import forms from django import forms
from django.forms import widgets from django.forms import widgets, SelectDateWidget
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from emoji_picker.widgets import EmojiPickerTextInput from emoji_picker.widgets import EmojiPickerTextInput
from .models import * from .models import *
class SelectWidget(widgets.Select):
class Media:
js = ('custom/js/form_select.js',)
class MultiSelectWidget(widgets.SelectMultiple): class MultiSelectWidget(widgets.SelectMultiple):
class Media: class Media:
js = ('custom/js/form_multiselect.js',) js = ('custom/js/form_multiselect.js',)
# yes there are some stupid browsers that still dont support this but i dont support people using these browsers
class DateWidget(forms.DateInput):
input_type = 'date'
class ExternalRecipeForm(forms.ModelForm): class ExternalRecipeForm(forms.ModelForm):
file_path = forms.CharField(disabled=True, required=False) file_path = forms.CharField(disabled=True, required=False)
storage = forms.ModelChoiceField(queryset=Storage.objects.all(), disabled=True, required=False) storage = forms.ModelChoiceField(queryset=Storage.objects.all(), disabled=True, required=False)
@ -87,12 +98,6 @@ class StorageForm(forms.ModelForm):
} }
class RecipeBookForm(forms.ModelForm):
class Meta:
model = RecipeBook
fields = ('name',)
class RecipeBookEntryForm(forms.ModelForm): class RecipeBookEntryForm(forms.ModelForm):
prefix = 'bookmark' prefix = 'bookmark'
@ -125,3 +130,17 @@ class ImportRecipeForm(forms.ModelForm):
'file_uid': _('File ID'), 'file_uid': _('File ID'),
} }
widgets = {'keywords': MultiSelectWidget} widgets = {'keywords': MultiSelectWidget}
class RecipeBookForm(forms.ModelForm):
class Meta:
model = RecipeBook
fields = ('name',)
class MealPlanForm(forms.ModelForm):
class Meta:
model = MealPlan
fields = ('recipe', 'meal', 'note', 'date')
widgets = {'recipe': SelectWidget, 'date': DateWidget}

View File

@ -1,6 +1,6 @@
from dal import autocomplete from dal import autocomplete
from cookbook.models import Keyword, RecipeIngredients from cookbook.models import Keyword, RecipeIngredients, Recipe
class KeywordAutocomplete(autocomplete.Select2QuerySetView): class KeywordAutocomplete(autocomplete.Select2QuerySetView):
@ -27,3 +27,16 @@ class IngredientsAutocomplete(autocomplete.Select2QuerySetView):
qs = qs.filter(name__istartswith=self.q) qs = qs.filter(name__istartswith=self.q)
return qs return qs
class RecipeAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
if not self.request.user.is_authenticated:
return Recipe.objects.none()
qs = Recipe.objects.all()
if self.q:
qs = qs.filter(name__icontains=self.q)
return qs

View File

@ -0,0 +1,27 @@
# Generated by Django 3.0.2 on 2020-01-17 14:55
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cookbook', '0007_auto_20191226_0852'),
]
operations = [
migrations.CreateModel(
name='MealPlan',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('meal', models.CharField(choices=[('BREAKFAST', 'Breakfast'), ('LUNCH', 'Lunch'), ('DINNER', 'Dinner'), ('OTHER', 'Other')], default='BREAKFAST', max_length=128)),
('note', models.TextField(blank=True)),
('date', models.DateField()),
('recipe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.Recipe')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@ -1,5 +1,5 @@
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.utils.translation import gettext as _
from django.db import models from django.db import models
@ -115,3 +115,17 @@ class RecipeBookEntry(models.Model):
def __str__(self): def __str__(self):
return self.recipe.name return self.recipe.name
class MealPlan(models.Model):
BREAKFAST = 'BREAKFAST'
LUNCH = 'LUNCH'
DINNER = 'DINNER'
OTHER = 'OTHER'
MEAL_TYPES = ((BREAKFAST, _('Breakfast')), (LUNCH, _('Lunch')), (DINNER, _('Dinner')), (OTHER, _('Other')),)
user = models.ForeignKey(User, on_delete=models.CASCADE)
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
meal = models.CharField(choices=MEAL_TYPES, max_length=128, default=BREAKFAST)
note = models.TextField(blank=True)
date = models.DateField()

View File

@ -0,0 +1,3 @@
$(document).ready(function () {
$('.selectwidget').select2();
});

View File

@ -75,6 +75,9 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{% url 'view_books' %}"><i class="fas fa-bookmark"></i> {% trans 'Books' %}</a> <a class="nav-link" href="{% url 'view_books' %}"><i class="fas fa-bookmark"></i> {% trans 'Books' %}</a>
</li> </li>
<li class="nav-item">
<a class="nav-link" href="{% url 'view_plan' %}"><i class="fas fa-calendar"></i> {% trans 'Meal-Plan' %}</a>
</li>
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" data-toggle="dropdown" <a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" data-toggle="dropdown"
aria-haspopup="true" aria-expanded="false"> aria-haspopup="true" aria-expanded="false">

View File

@ -0,0 +1,76 @@
{% extends "base.html" %}
{% load i18n %}
{% block title %}{% trans 'Meal-Plan' %}{% endblock %}
{% block extra_head %}
{{ form.media }}
{% endblock %}
{% block content %}
<h3>
{% trans 'Meal-Plan' %} <a href="{% url 'new_plan' %}"><i class="fas fa-plus-circle"></i></a>
</h3>
<div class="row">
<div class="col-md-12">
<form>
<label>{% trans 'Week' %}
<input id="id_week" class="form-control" type="week" onchange="document.forms[0].submit()">
</label>
</form>
</div>
</div>
<br/>
<div class="row">
<div class="col-md-12 table-responsive">
<table class="table table-bordered">
<tr style="text-align: center">
<th id="th_day_1">?</th>
<th id="th_day_2">?</th>
<th id="th_day_3">?</th>
<th id="th_day_4">?</th>
<th id="th_day_5">?</th>
<th id="th_day_6">?</th>
<th id="th_day_7">?</th>
</tr>
<tr>
<td colspan="7" style="text-align: center"><h5>{% trans 'Breakfast' %}</h5></td>
</tr>
<tr>
<td id="td_breakfast_1"></td>
<td id="td_breakfast_2"></td>
<td id="td_breakfast_3"></td>
<td id="td_breakfast_4"></td>
<td id="td_breakfast_5"></td>
<td id="td_breakfast_6"></td>
<td id="td_breakfast_7"></td>
</tr>
<tr>
<td colspan="7" style="text-align: center"><h5>{% trans 'Lunch' %}</h5></td>
</tr>
<tr>
</tr>
<tr>
<td colspan="7" style="text-align: center"><h5>{% trans 'Dinner' %}</h5></td>
</tr>
<tr>
</tr>
<tr>
<td colspan="7" style="text-align: center"><h5>{% trans 'Other' %}</h5></td>
</tr>
<tr>
</tr>
</table>
</div>
</div>
{% endblock %}

View File

@ -7,6 +7,7 @@ from cookbook.helper import dal
urlpatterns = [ urlpatterns = [
path('', views.index, name='index'), path('', views.index, name='index'),
path('books', views.books, name='view_books'), path('books', views.books, name='view_books'),
path('plan', views.meal_plan, name='view_plan'),
path('view/recipe/<int:pk>', views.recipe_view, name='view_recipe'), path('view/recipe/<int:pk>', views.recipe_view, name='view_recipe'),
@ -15,6 +16,7 @@ urlpatterns = [
path('new/keyword/', new.KeywordCreate.as_view(), name='new_keyword'), path('new/keyword/', new.KeywordCreate.as_view(), name='new_keyword'),
path('new/storage/', new.StorageCreate.as_view(), name='new_storage'), path('new/storage/', new.StorageCreate.as_view(), name='new_storage'),
path('new/book/', new.RecipeBookCreate.as_view(), name='new_book'), path('new/book/', new.RecipeBookCreate.as_view(), name='new_book'),
path('new/plan/', new.MealPlanCreate.as_view(), name='new_plan'),
path('list/keyword', lists.keyword, name='list_keyword'), path('list/keyword', lists.keyword, name='list_keyword'),
path('list/import_log', lists.sync_log, name='list_import_log'), path('list/import_log', lists.sync_log, name='list_import_log'),
@ -34,6 +36,7 @@ urlpatterns = [
path('edit/storage/<int:pk>/', edit.edit_storage, name='edit_storage'), path('edit/storage/<int:pk>/', edit.edit_storage, name='edit_storage'),
path('edit/comment/<int:pk>/', edit.CommentUpdate.as_view(), name='edit_comment'), path('edit/comment/<int:pk>/', edit.CommentUpdate.as_view(), name='edit_comment'),
path('edit/recipe-book/<int:pk>/', edit.RecipeBookUpdate.as_view(), name='edit_recipe_book'), path('edit/recipe-book/<int:pk>/', edit.RecipeBookUpdate.as_view(), name='edit_recipe_book'),
path('edit/plan/<int:pk>/', edit.MealPlanUpdate.as_view(), name='edit_plan'),
path('redirect/delete/<slug:name>/<int:pk>/', edit.delete_redirect, name='redirect_delete'), path('redirect/delete/<slug:name>/<int:pk>/', edit.delete_redirect, name='redirect_delete'),
@ -46,6 +49,7 @@ urlpatterns = [
path('delete/comment/<int:pk>/', edit.CommentDelete.as_view(), name='delete_comment'), path('delete/comment/<int:pk>/', edit.CommentDelete.as_view(), name='delete_comment'),
path('delete/recipe-book/<int:pk>/', edit.RecipeBookDelete.as_view(), name='delete_recipe_book'), path('delete/recipe-book/<int:pk>/', edit.RecipeBookDelete.as_view(), name='delete_recipe_book'),
path('delete/recipe-book-entry/<int:pk>/', edit.RecipeBookEntryDelete.as_view(), name='delete_recipe_book_entry'), path('delete/recipe-book-entry/<int:pk>/', edit.RecipeBookEntryDelete.as_view(), name='delete_recipe_book_entry'),
path('delete/plan/<int:pk>/', edit.MealPlanDelete.as_view(), name='delete_plan'),
path('data/sync', data.sync, name='data_sync'), # TODO move to generic "new" view path('data/sync', data.sync, name='data_sync'), # TODO move to generic "new" view
path('data/batch/edit', data.batch_edit, name='data_batch_edit'), path('data/batch/edit', data.batch_edit, name='data_batch_edit'),
@ -56,7 +60,6 @@ urlpatterns = [
path('api/get_file_link/<int:recipe_id>/', api.get_file_link, name='api_get_file_link'), path('api/get_file_link/<int:recipe_id>/', api.get_file_link, name='api_get_file_link'),
path('api/get_external_file_link/<int:recipe_id>/', api.get_external_file_link, name='api_get_external_file_link'), path('api/get_external_file_link/<int:recipe_id>/', api.get_external_file_link, name='api_get_external_file_link'),
path('api/sync_all/', api.sync_all, name='api_sync'), path('api/sync_all/', api.sync_all, name='api_sync'),
path('dal/keyword/', dal.KeywordAutocomplete.as_view(), name='dal_keyword'), path('dal/keyword/', dal.KeywordAutocomplete.as_view(), name='dal_keyword'),

View File

@ -13,9 +13,9 @@ from django.urls import reverse_lazy, reverse
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from django.views.generic import UpdateView, DeleteView from django.views.generic import UpdateView, DeleteView
from cookbook.forms import ExternalRecipeForm, KeywordForm, StorageForm, SyncForm, InternalRecipeForm, CommentForm from cookbook.forms import ExternalRecipeForm, KeywordForm, StorageForm, SyncForm, InternalRecipeForm, CommentForm, MealPlanForm
from cookbook.models import Recipe, Sync, Keyword, RecipeImport, Storage, Comment, RecipeIngredients, RecipeBook, \ from cookbook.models import Recipe, Sync, Keyword, RecipeImport, Storage, Comment, RecipeIngredients, RecipeBook, \
RecipeBookEntry RecipeBookEntry, MealPlan
from cookbook.provider.dropbox import Dropbox from cookbook.provider.dropbox import Dropbox
from cookbook.provider.nextcloud import Nextcloud from cookbook.provider.nextcloud import Nextcloud
@ -225,6 +225,22 @@ class RecipeBookUpdate(LoginRequiredMixin, UpdateView):
return context return context
class MealPlanUpdate(LoginRequiredMixin, UpdateView):
template_name = "generic/edit_template.html"
model = MealPlan
form_class = MealPlanForm
# TODO add msg box
def get_success_url(self):
return reverse('view_plan')
def get_context_data(self, **kwargs):
context = super(MealPlanUpdate, self).get_context_data(**kwargs)
context['title'] = _("Meal-Plan")
return context
class RecipeUpdate(LoginRequiredMixin, UpdateView): class RecipeUpdate(LoginRequiredMixin, UpdateView):
model = Recipe model = Recipe
form_class = ExternalRecipeForm form_class = ExternalRecipeForm
@ -374,3 +390,14 @@ class RecipeBookEntryDelete(LoginRequiredMixin, DeleteView):
context = super(RecipeBookEntryDelete, self).get_context_data(**kwargs) context = super(RecipeBookEntryDelete, self).get_context_data(**kwargs)
context['title'] = _("Bookmarks") context['title'] = _("Bookmarks")
return context return context
class MealPlanDelete(LoginRequiredMixin, DeleteView):
template_name = "generic/delete_template.html"
model = MealPlan
success_url = reverse_lazy('view_plan')
def get_context_data(self, **kwargs):
context = super(MealPlanDelete, self).get_context_data(**kwargs)
context['title'] = _("Meal-Plan")
return context

View File

@ -8,8 +8,8 @@ from django.utils.translation import gettext as _
from django.views.generic import CreateView from django.views.generic import CreateView
from cookbook.forms import ImportRecipeForm, RecipeImport, KeywordForm, Storage, StorageForm, InternalRecipeForm, \ from cookbook.forms import ImportRecipeForm, RecipeImport, KeywordForm, Storage, StorageForm, InternalRecipeForm, \
RecipeBookForm RecipeBookForm, MealPlanForm
from cookbook.models import Keyword, Recipe, RecipeBook from cookbook.models import Keyword, Recipe, RecipeBook, MealPlan
class RecipeCreate(LoginRequiredMixin, CreateView): class RecipeCreate(LoginRequiredMixin, CreateView):
@ -109,3 +109,21 @@ class RecipeBookCreate(LoginRequiredMixin, CreateView):
context = super(RecipeBookCreate, self).get_context_data(**kwargs) context = super(RecipeBookCreate, self).get_context_data(**kwargs)
context['title'] = _("Recipe Book") context['title'] = _("Recipe Book")
return context return context
class MealPlanCreate(LoginRequiredMixin, CreateView):
template_name = "generic/new_template.html"
model = MealPlan
form_class = MealPlanForm
success_url = reverse_lazy('view_plan')
def form_valid(self, form):
obj = form.save(commit=False)
obj.user = self.request.user
obj.save()
return HttpResponseRedirect(self.get_success_url())
def get_context_data(self, **kwargs):
context = super(MealPlanCreate, self).get_context_data(**kwargs)
context['title'] = _("Meal-Plan")
return context

View File

@ -1,8 +1,6 @@
from django.contrib import messages from django.contrib import messages
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.http import HttpResponseRedirect
from django.shortcuts import render, get_object_or_404 from django.shortcuts import render, get_object_or_404
from django.urls import reverse
from django_tables2 import RequestConfig from django_tables2 import RequestConfig
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
@ -69,3 +67,9 @@ def books(request):
book_list.append({'book': b, 'recipes': RecipeBookEntry.objects.filter(book=b).all()}) book_list.append({'book': b, 'recipes': RecipeBookEntry.objects.filter(book=b).all()})
return render(request, 'books.html', {'book_list': book_list}) return render(request, 'books.html', {'book_list': book_list})
@login_required()
def meal_plan(request):
form = MealPlanForm()
return render(request, 'meal_plan.html', {'form': form})

View File

@ -72,8 +72,7 @@ ROOT_URLCONF = 'recipes.urls'
TEMPLATES = [ TEMPLATES = [
{ {
'BACKEND': 'django.template.backends.django.DjangoTemplates', 'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')] 'DIRS': [os.path.join(BASE_DIR, 'templates')],
,
'APP_DIRS': True, 'APP_DIRS': True,
'OPTIONS': { 'OPTIONS': {
'context_processors': [ 'context_processors': [

View File

@ -13,6 +13,6 @@ markdown
simplejson simplejson
lxml lxml
webdavclient3 webdavclient3
python-dotenv==0.10.3 python-dotenv
psycopg2-binary psycopg2-binary
gunicorn==19.7.1 gunicorn