recipe rating
This commit is contained in:
27
cookbook/migrations/0042_cooklog.py
Normal file
27
cookbook/migrations/0042_cooklog.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Generated by Django 3.0.5 on 2020-05-02 14:47
|
||||||
|
|
||||||
|
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', '0041_auto_20200502_1446'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='CookLog',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('rating', models.IntegerField(null=True)),
|
||||||
|
('servings', models.IntegerField(default=0)),
|
||||||
|
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
('recipe', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.Recipe')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
@ -232,3 +232,13 @@ class MealPlan(models.Model):
|
|||||||
return self.title
|
return self.title
|
||||||
return str(self.recipe)
|
return str(self.recipe)
|
||||||
|
|
||||||
|
|
||||||
|
class CookLog(models.Model):
|
||||||
|
recipe = models.ForeignKey(Recipe, on_delete=models.CASCADE)
|
||||||
|
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
|
rating = models.IntegerField(null=True)
|
||||||
|
servings = models.IntegerField(default=0)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.recipe.name
|
||||||
|
@ -26,7 +26,7 @@ class RecipeTableSmall(tables.Table):
|
|||||||
|
|
||||||
|
|
||||||
class RecipeTable(tables.Table):
|
class RecipeTable(tables.Table):
|
||||||
edit = tables.TemplateColumn("<a href='{% url 'edit_recipe' record.id %}' >" + _('Edit') + "</a>")
|
edit = tables.TemplateColumn("<a style='color: inherit' href='{% url 'edit_recipe' record.id %}' >" + _('Edit') + "</a>")
|
||||||
name = tables.LinkColumn('view_recipe', args=[A('id')])
|
name = tables.LinkColumn('view_recipe', args=[A('id')])
|
||||||
all_tags = tables.Column(
|
all_tags = tables.Column(
|
||||||
attrs={'td': {'class': 'd-none d-lg-table-cell'}, 'th': {'class': 'd-none d-lg-table-cell'}})
|
attrs={'td': {'class': 'd-none d-lg-table-cell'}, 'th': {'class': 'd-none d-lg-table-cell'}})
|
||||||
|
78
cookbook/templates/include/log_cooking.html
Normal file
78
cookbook/templates/include/log_cooking.html
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
<div class="modal" tabindex="-1" role="dialog" id="id_modal_cook_log">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">{% trans 'Log Recipe Cooking' %}</h5>
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>{% trans 'All fields are optional and can be left empty.' %}</p>
|
||||||
|
<form>
|
||||||
|
|
||||||
|
<label for="id_log_servings">{% trans 'Servings' %} </label>
|
||||||
|
<input class="form-control" type="number" id="id_log_servings">
|
||||||
|
<br/>
|
||||||
|
<label for="id_log_rating">{% trans 'Rating' %} - <span id="id_rating_show">0/5</span></label>
|
||||||
|
<input type="range" class="custom-range" min="0" max="5" id="id_log_rating" name="log_rating"
|
||||||
|
value="0">
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-dismiss="modal">{% trans 'Close' %}</button>
|
||||||
|
<button type="button" class="btn btn-primary" onclick="logCook()">{% trans 'Save' %}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="application/javascript">
|
||||||
|
let modal = $('#id_modal_cook_log')
|
||||||
|
let rating = $('#id_log_rating')
|
||||||
|
|
||||||
|
function openCookLogModal(id) {
|
||||||
|
modal.data('recipe_id', id)
|
||||||
|
modal.modal('show')
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO there is definitely a nicer way to do this than this ugly shit
|
||||||
|
function logCook() {
|
||||||
|
let id = modal.data('recipe_id');
|
||||||
|
|
||||||
|
let url = "{% url 'api_log_cooking' recipe_id=12345 %}".replace(/12345/, id);
|
||||||
|
|
||||||
|
let val_servings = $('#id_log_servings').val()
|
||||||
|
if (val_servings !== '' && val_servings !== 0) {
|
||||||
|
url += '?s=' + val_servings
|
||||||
|
}
|
||||||
|
|
||||||
|
let val_rating = rating.val()
|
||||||
|
if (val_rating !== '' && val_rating !== 0) {
|
||||||
|
if (val_servings !== '' && val_servings !== 0) {
|
||||||
|
url += '&'
|
||||||
|
}else {
|
||||||
|
url += '?'
|
||||||
|
}
|
||||||
|
url += 'r=' + val_rating
|
||||||
|
}
|
||||||
|
|
||||||
|
let request = new XMLHttpRequest();
|
||||||
|
request.onreadystatechange = function () {
|
||||||
|
|
||||||
|
};
|
||||||
|
request.open("GET", url, true);
|
||||||
|
request.send();
|
||||||
|
|
||||||
|
modal.modal('hide')
|
||||||
|
}
|
||||||
|
|
||||||
|
rating.on("input", () => {
|
||||||
|
$('#id_rating_show').html(rating.val() + '/5')
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
@ -1,79 +0,0 @@
|
|||||||
{% load i18n %}
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.loader {
|
|
||||||
border: 16px solid #f3f3f3; /* Light grey */
|
|
||||||
border-top: 16px solid #3498db; /* Blue */
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 120px;
|
|
||||||
height: 120px;
|
|
||||||
animation: spin 2s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
0% {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="modal" tabindex="-1" role="dialog" id="modal_recipe">
|
|
||||||
<div class="modal-dialog" role="document">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h5 class="modal-title">{% trans 'Recipe' %}</h5>
|
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body" style="text-align: center">
|
|
||||||
<div class="loader" id="div_loader"></div>
|
|
||||||
<a href="" id="a_recipe_open" target="_blank" onclick="afterClick()" style="font-size: 250%"></a>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="modal-footer">
|
|
||||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">{% trans 'Close' %}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
|
||||||
function openRecipe(id) {
|
|
||||||
var link = $('#a_recipe_open');
|
|
||||||
link.hide();
|
|
||||||
$('#div_loader').show();
|
|
||||||
|
|
||||||
var url = "{% url 'api_get_external_file_link' recipe_id=12345 %}".replace(/12345/, id);
|
|
||||||
|
|
||||||
link.text("{% trans 'Open Recipe' %}");
|
|
||||||
$('#modal_recipe').modal('show');
|
|
||||||
|
|
||||||
var xhttp = new XMLHttpRequest();
|
|
||||||
xhttp.onreadystatechange = function () {
|
|
||||||
if (this.readyState === 4 && this.status === 200) {
|
|
||||||
if (/^((?!chrome|android).)*safari/i.test(navigator.userAgent)) {
|
|
||||||
link.attr("href", this.responseText);
|
|
||||||
link.show();
|
|
||||||
} else {
|
|
||||||
window.open(this.responseText);
|
|
||||||
$('#modal_recipe').modal('hide');
|
|
||||||
}
|
|
||||||
|
|
||||||
$('#div_loader').hide();
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
xhttp.open("GET", url, true);
|
|
||||||
xhttp.send();
|
|
||||||
}
|
|
||||||
|
|
||||||
function afterClick() {
|
|
||||||
$('#modal_recipe').modal('hide');
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -85,4 +85,5 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% include 'include/log_cooking.html' %}
|
||||||
{% endblock %}
|
{% endblock %}
|
@ -45,6 +45,7 @@
|
|||||||
<span class="badge badge-info">{% trans 'External' %} </span>
|
<span class="badge badge-info">{% trans 'External' %} </span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<span class="badge badge-light">{{ row.cells.edit }}</span>
|
<span class="badge badge-light">{{ row.cells.edit }}</span>
|
||||||
|
<span class="badge badge-warning"><a href="#" style="color: inherit" onclick="openCookLogModal({{ row.record.pk }})">{% trans 'Log' %}</a></span>
|
||||||
</small></p>
|
</small></p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -39,8 +39,8 @@ urlpatterns = [
|
|||||||
|
|
||||||
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/get_recipe_file/<int:recipe_id>/', api.get_recipe_file, name='api_get_recipe_file'),
|
path('api/get_recipe_file/<int:recipe_id>/', api.get_recipe_file, name='api_get_recipe_file'),
|
||||||
|
|
||||||
path('api/sync_all/', api.sync_all, name='api_sync'),
|
path('api/sync_all/', api.sync_all, name='api_sync'),
|
||||||
|
path('api/log_cooking/<int:recipe_id>/', api.log_cooking, name='api_log_cooking'),
|
||||||
|
|
||||||
path('dal/keyword/', dal.KeywordAutocomplete.as_view(), name='dal_keyword'),
|
path('dal/keyword/', dal.KeywordAutocomplete.as_view(), name='dal_keyword'),
|
||||||
path('dal/ingredient/', dal.IngredientsAutocomplete.as_view(), name='dal_ingredient'),
|
path('dal/ingredient/', dal.IngredientsAutocomplete.as_view(), name='dal_ingredient'),
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
from annoying.decorators import ajax_request
|
||||||
|
from annoying.functions import get_object_or_None
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from cookbook.helper.permission_helper import group_required
|
from cookbook.helper.permission_helper import group_required
|
||||||
from cookbook.models import Recipe, Sync, Storage
|
from cookbook.models import Recipe, Sync, Storage, CookLog
|
||||||
from cookbook.provider.dropbox import Dropbox
|
from cookbook.provider.dropbox import Dropbox
|
||||||
from cookbook.provider.nextcloud import Nextcloud
|
from cookbook.provider.nextcloud import Nextcloud
|
||||||
|
|
||||||
@ -64,3 +68,22 @@ def sync_all(request):
|
|||||||
else:
|
else:
|
||||||
messages.add_message(request, messages.ERROR, _('Error synchronizing with Storage'))
|
messages.add_message(request, messages.ERROR, _('Error synchronizing with Storage'))
|
||||||
return redirect('list_recipe_import')
|
return redirect('list_recipe_import')
|
||||||
|
|
||||||
|
|
||||||
|
@group_required('user')
|
||||||
|
@ajax_request
|
||||||
|
def log_cooking(request, recipe_id):
|
||||||
|
recipe = get_object_or_None(Recipe, id=recipe_id)
|
||||||
|
if recipe:
|
||||||
|
log = CookLog.objects.create(created_by=request.user, recipe=recipe)
|
||||||
|
servings = request.GET['s'] if 's' in request.GET else None
|
||||||
|
if servings and re.match(r'^([1-9])+$', servings):
|
||||||
|
log.servings = int(servings)
|
||||||
|
|
||||||
|
rating = request.GET['r'] if 'r' in request.GET else None
|
||||||
|
if rating and re.match(r'^([1-9])+$', rating):
|
||||||
|
log.rating = int(rating)
|
||||||
|
log.save()
|
||||||
|
return {'msg': 'updated successfully'}
|
||||||
|
|
||||||
|
return {'error': 'recipe does not exist'}
|
||||||
|
Reference in New Issue
Block a user