meal plan WIP
This commit is contained in:
@ -1,16 +1,27 @@
|
||||
from dal_select2.widgets import ModelSelect2
|
||||
from django import forms
|
||||
from django.forms import widgets
|
||||
from django.forms import widgets, SelectDateWidget
|
||||
from django.utils.translation import gettext as _
|
||||
from emoji_picker.widgets import EmojiPickerTextInput
|
||||
|
||||
from .models import *
|
||||
|
||||
|
||||
class SelectWidget(widgets.Select):
|
||||
class Media:
|
||||
js = ('custom/js/form_select.js',)
|
||||
|
||||
|
||||
class MultiSelectWidget(widgets.SelectMultiple):
|
||||
class Media:
|
||||
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):
|
||||
file_path = forms.CharField(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):
|
||||
prefix = 'bookmark'
|
||||
|
||||
@ -125,3 +130,17 @@ class ImportRecipeForm(forms.ModelForm):
|
||||
'file_uid': _('File ID'),
|
||||
}
|
||||
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}
|
||||
|
@ -1,6 +1,6 @@
|
||||
from dal import autocomplete
|
||||
|
||||
from cookbook.models import Keyword, RecipeIngredients
|
||||
from cookbook.models import Keyword, RecipeIngredients, Recipe
|
||||
|
||||
|
||||
class KeywordAutocomplete(autocomplete.Select2QuerySetView):
|
||||
@ -27,3 +27,16 @@ class IngredientsAutocomplete(autocomplete.Select2QuerySetView):
|
||||
qs = qs.filter(name__istartswith=self.q)
|
||||
|
||||
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
|
||||
|
27
cookbook/migrations/0008_mealplan.py
Normal file
27
cookbook/migrations/0008_mealplan.py
Normal 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)),
|
||||
],
|
||||
),
|
||||
]
|
@ -1,5 +1,5 @@
|
||||
from django.contrib.auth.models import User
|
||||
|
||||
from django.utils.translation import gettext as _
|
||||
from django.db import models
|
||||
|
||||
|
||||
@ -115,3 +115,17 @@ class RecipeBookEntry(models.Model):
|
||||
|
||||
def __str__(self):
|
||||
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()
|
||||
|
3
cookbook/static/custom/js/form_select.js
Normal file
3
cookbook/static/custom/js/form_select.js
Normal file
@ -0,0 +1,3 @@
|
||||
$(document).ready(function () {
|
||||
$('.selectwidget').select2();
|
||||
});
|
@ -75,6 +75,9 @@
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{% url 'view_books' %}"><i class="fas fa-bookmark"></i> {% trans 'Books' %}</a>
|
||||
</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">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" data-toggle="dropdown"
|
||||
aria-haspopup="true" aria-expanded="false">
|
||||
|
76
cookbook/templates/meal_plan.html
Normal file
76
cookbook/templates/meal_plan.html
Normal 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 %}
|
@ -7,6 +7,7 @@ from cookbook.helper import dal
|
||||
urlpatterns = [
|
||||
path('', views.index, name='index'),
|
||||
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'),
|
||||
|
||||
@ -15,6 +16,7 @@ urlpatterns = [
|
||||
path('new/keyword/', new.KeywordCreate.as_view(), name='new_keyword'),
|
||||
path('new/storage/', new.StorageCreate.as_view(), name='new_storage'),
|
||||
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/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/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/plan/<int:pk>/', edit.MealPlanUpdate.as_view(), name='edit_plan'),
|
||||
|
||||
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/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/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/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_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('dal/keyword/', dal.KeywordAutocomplete.as_view(), name='dal_keyword'),
|
||||
|
@ -13,9 +13,9 @@ from django.urls import reverse_lazy, reverse
|
||||
from django.utils.translation import gettext as _
|
||||
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, \
|
||||
RecipeBookEntry
|
||||
RecipeBookEntry, MealPlan
|
||||
from cookbook.provider.dropbox import Dropbox
|
||||
from cookbook.provider.nextcloud import Nextcloud
|
||||
|
||||
@ -225,6 +225,22 @@ class RecipeBookUpdate(LoginRequiredMixin, UpdateView):
|
||||
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):
|
||||
model = Recipe
|
||||
form_class = ExternalRecipeForm
|
||||
@ -374,3 +390,14 @@ class RecipeBookEntryDelete(LoginRequiredMixin, DeleteView):
|
||||
context = super(RecipeBookEntryDelete, self).get_context_data(**kwargs)
|
||||
context['title'] = _("Bookmarks")
|
||||
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
|
||||
|
@ -8,8 +8,8 @@ from django.utils.translation import gettext as _
|
||||
from django.views.generic import CreateView
|
||||
|
||||
from cookbook.forms import ImportRecipeForm, RecipeImport, KeywordForm, Storage, StorageForm, InternalRecipeForm, \
|
||||
RecipeBookForm
|
||||
from cookbook.models import Keyword, Recipe, RecipeBook
|
||||
RecipeBookForm, MealPlanForm
|
||||
from cookbook.models import Keyword, Recipe, RecipeBook, MealPlan
|
||||
|
||||
|
||||
class RecipeCreate(LoginRequiredMixin, CreateView):
|
||||
@ -109,3 +109,21 @@ class RecipeBookCreate(LoginRequiredMixin, CreateView):
|
||||
context = super(RecipeBookCreate, self).get_context_data(**kwargs)
|
||||
context['title'] = _("Recipe Book")
|
||||
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
|
||||
|
@ -1,8 +1,6 @@
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
from django.urls import reverse
|
||||
from django_tables2 import RequestConfig
|
||||
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()})
|
||||
|
||||
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})
|
||||
|
@ -72,8 +72,7 @@ ROOT_URLCONF = 'recipes.urls'
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [os.path.join(BASE_DIR, 'templates')]
|
||||
,
|
||||
'DIRS': [os.path.join(BASE_DIR, 'templates')],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
|
@ -13,6 +13,6 @@ markdown
|
||||
simplejson
|
||||
lxml
|
||||
webdavclient3
|
||||
python-dotenv==0.10.3
|
||||
python-dotenv
|
||||
psycopg2-binary
|
||||
gunicorn==19.7.1
|
||||
gunicorn
|
||||
|
Reference in New Issue
Block a user