sandwitches 1.4.2__py3-none-any.whl → 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sandwitches/__init__.py +6 -0
- sandwitches/admin.py +21 -2
- sandwitches/api.py +112 -6
- sandwitches/feeds.py +23 -0
- sandwitches/forms.py +110 -7
- sandwitches/locale/nl/LC_MESSAGES/django.mo +0 -0
- sandwitches/locale/nl/LC_MESSAGES/django.po +784 -134
- sandwitches/migrations/0001_initial.py +255 -2
- sandwitches/migrations/0002_historicalrecipe_servings_recipe_servings.py +27 -0
- sandwitches/migrations/0003_setting.py +35 -0
- sandwitches/migrations/0004_alter_setting_ai_api_key_and_more.py +37 -0
- sandwitches/migrations/0005_rating_comment.py +17 -0
- sandwitches/models.py +48 -4
- sandwitches/settings.py +14 -5
- sandwitches/storage.py +44 -12
- sandwitches/templates/admin/admin_base.html +118 -0
- sandwitches/templates/admin/confirm_delete.html +23 -0
- sandwitches/templates/admin/dashboard.html +262 -0
- sandwitches/templates/admin/rating_list.html +38 -0
- sandwitches/templates/admin/recipe_form.html +184 -0
- sandwitches/templates/admin/recipe_list.html +64 -0
- sandwitches/templates/admin/tag_form.html +30 -0
- sandwitches/templates/admin/tag_list.html +37 -0
- sandwitches/templates/admin/task_detail.html +91 -0
- sandwitches/templates/admin/task_list.html +41 -0
- sandwitches/templates/admin/user_form.html +37 -0
- sandwitches/templates/admin/user_list.html +60 -0
- sandwitches/templates/base.html +80 -1
- sandwitches/templates/base_beer.html +57 -0
- sandwitches/templates/components/favorites_search_form.html +85 -0
- sandwitches/templates/components/footer.html +14 -0
- sandwitches/templates/components/ingredients_scripts.html +50 -0
- sandwitches/templates/components/ingredients_section.html +11 -0
- sandwitches/templates/components/instructions_section.html +9 -0
- sandwitches/templates/components/language_dialog.html +26 -0
- sandwitches/templates/components/navbar.html +27 -0
- sandwitches/templates/components/rating_section.html +66 -0
- sandwitches/templates/components/recipe_header.html +32 -0
- sandwitches/templates/components/search_form.html +106 -0
- sandwitches/templates/components/search_scripts.html +98 -0
- sandwitches/templates/components/side_menu.html +35 -0
- sandwitches/templates/components/user_menu.html +10 -0
- sandwitches/templates/detail.html +167 -110
- sandwitches/templates/favorites.html +42 -0
- sandwitches/templates/index.html +28 -61
- sandwitches/templates/partials/recipe_list.html +87 -0
- sandwitches/templates/recipe_form.html +119 -0
- sandwitches/templates/setup.html +1 -1
- sandwitches/templates/signup.html +114 -31
- sandwitches/templatetags/custom_filters.py +15 -0
- sandwitches/urls.py +56 -0
- sandwitches/utils.py +222 -0
- sandwitches/views.py +503 -14
- sandwitches-2.0.0.dist-info/METADATA +104 -0
- sandwitches-2.0.0.dist-info/RECORD +62 -0
- sandwitches/migrations/0002_historicalrecipe.py +0 -61
- sandwitches/migrations/0003_rating.py +0 -57
- sandwitches/migrations/0004_add_uploaded_by.py +0 -25
- sandwitches/migrations/0005_historicalrecipe_uploaded_by.py +0 -27
- sandwitches/migrations/0006_profile.py +0 -48
- sandwitches/migrations/0007_alter_rating_score.py +0 -23
- sandwitches/migrations/0008_delete_profile.py +0 -15
- sandwitches/templates/base_pico.html +0 -260
- sandwitches/templates/form.html +0 -16
- sandwitches-1.4.2.dist-info/METADATA +0 -25
- sandwitches-1.4.2.dist-info/RECORD +0 -35
- {sandwitches-1.4.2.dist-info → sandwitches-2.0.0.dist-info}/WHEEL +0 -0
|
@@ -1,121 +1,178 @@
|
|
|
1
|
-
{% extends "
|
|
1
|
+
{% extends "base_beer.html" %}
|
|
2
2
|
{% block title %}{{ recipe.title }} — Sandwitch{% endblock %}
|
|
3
|
+
|
|
4
|
+
{% block extra_head %}
|
|
5
|
+
{{ block.super }}
|
|
6
|
+
<style>
|
|
7
|
+
@media only screen and (min-width: 992px) {
|
|
8
|
+
main.container {
|
|
9
|
+
padding-left: 10%;
|
|
10
|
+
padding-right: 10%;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
.portion-selector {
|
|
14
|
+
display: flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
gap: 10px;
|
|
17
|
+
margin-bottom: 20px;
|
|
18
|
+
}
|
|
19
|
+
.portion-selector input[type="number"] {
|
|
20
|
+
width: 60px;
|
|
21
|
+
text-align: center;
|
|
22
|
+
}
|
|
23
|
+
</style>
|
|
24
|
+
{% load custom_filters %} {# Load your custom filters #}
|
|
25
|
+
<script type="application/ld+json">
|
|
26
|
+
{
|
|
27
|
+
"@context": "https://schema.org",
|
|
28
|
+
"@type": "Recipe",
|
|
29
|
+
"name": "{{ recipe.title }}",
|
|
30
|
+
{% if recipe.image %}
|
|
31
|
+
"image": "{{ request.scheme }}://{{ request.get_host }}{{ recipe.image_medium.url }}",
|
|
32
|
+
{% endif %}
|
|
33
|
+
"author": {
|
|
34
|
+
"@type": "Person",
|
|
35
|
+
"name": "{% if recipe.uploaded_by %}{{ recipe.uploaded_by.get_full_name|default:recipe.uploaded_by.username }}{% else %}Anonymous{% endif %}"
|
|
36
|
+
},
|
|
37
|
+
"datePublished": "{{ recipe.created_at|date:"Y-m-d" }}",
|
|
38
|
+
"description": "{{ recipe.description|striptags|truncatechars:200 }}",
|
|
39
|
+
{% if recipe.tags.all %}
|
|
40
|
+
"recipeCategory": [{% for tag in recipe.tags.all %}"{{ tag.name }}"{% if not forloop.last %},{% endif %}{% endfor %}],
|
|
41
|
+
{% endif %}
|
|
42
|
+
"recipeIngredient": [
|
|
43
|
+
{% for ingredient in recipe.ingredients|linebreaksbr|striptags|split:"\n" %}
|
|
44
|
+
"{{ ingredient|escapejs }}"{% if not forloop.last %},{% endif %}
|
|
45
|
+
{% endfor %}
|
|
46
|
+
],
|
|
47
|
+
"recipeInstructions": {
|
|
48
|
+
"@type": "ItemList",
|
|
49
|
+
"itemListElement": [
|
|
50
|
+
{% for instruction in recipe.instructions|linebreaksbr|striptags|split:"\n" %}
|
|
51
|
+
{
|
|
52
|
+
"@type": "HowToStep",
|
|
53
|
+
"text": "{{ instruction|escapejs }}"
|
|
54
|
+
}{% if not forloop.last %},{% endif %}
|
|
55
|
+
{% endfor %}
|
|
56
|
+
]
|
|
57
|
+
},
|
|
58
|
+
{% if avg_rating > 0 %}
|
|
59
|
+
"aggregateRating": {
|
|
60
|
+
"@type": "AggregateRating",
|
|
61
|
+
"ratingValue": "{{ avg_rating|floatformat:1 }}",
|
|
62
|
+
"reviewCount": "{{ rating_count }}"
|
|
63
|
+
}
|
|
64
|
+
{% endif %}
|
|
65
|
+
}
|
|
66
|
+
</script>
|
|
67
|
+
{% endblock %}
|
|
68
|
+
|
|
3
69
|
{% block content %}
|
|
4
70
|
|
|
5
|
-
{% load i18n markdown_extras %}
|
|
6
|
-
|
|
7
|
-
|
|
71
|
+
{% load i18n markdown_extras %} {# Keep existing load tags #}
|
|
72
|
+
|
|
73
|
+
<div class="space"></div>
|
|
74
|
+
|
|
75
|
+
<nav>
|
|
76
|
+
<a href="{% url 'index' %}" class="button transparent circle">
|
|
77
|
+
<i>arrow_back</i>
|
|
78
|
+
</a>
|
|
79
|
+
<h5 class="max ml-2">{% trans "Back to all" %}</h5>
|
|
8
80
|
</nav>
|
|
9
81
|
|
|
82
|
+
<div class="large-space"></div>
|
|
83
|
+
|
|
10
84
|
<div class="grid">
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
85
|
+
<!-- Left Column: Image and Main Info -->
|
|
86
|
+
<div class="s12 m12 l5">
|
|
87
|
+
<article class="round no-padding elevate">
|
|
88
|
+
{% if recipe.image %}
|
|
14
89
|
<img src="{{ recipe.image_large.url }}"
|
|
15
90
|
srcset="{{ recipe.image_medium.url }} 700w, {{ recipe.image_large.url }} 1200w"
|
|
16
|
-
sizes="(max-width: 768px) 95vw,
|
|
91
|
+
sizes="(max-width: 768px) 95vw, 600px"
|
|
17
92
|
alt="{{ recipe.title }}"
|
|
18
|
-
loading="lazy"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
93
|
+
loading="lazy"
|
|
94
|
+
class="responsive top-round">
|
|
95
|
+
{% else %}
|
|
96
|
+
<div class="primary medium-height top-round middle-align center-align" style="height:300px;">
|
|
97
|
+
<i class="extra">lunch_dining</i>
|
|
98
|
+
</div>
|
|
99
|
+
{% endif %}
|
|
100
|
+
<div class="padding">
|
|
101
|
+
{% include "components/recipe_header.html" %}
|
|
102
|
+
|
|
103
|
+
<div class="space"></div>
|
|
104
|
+
<div class="divider"></div>
|
|
105
|
+
<div class="space"></div>
|
|
106
|
+
|
|
107
|
+
<h6 class="bold">{% trans "Tags" %}</h6>
|
|
108
|
+
<div class="row wrap">
|
|
109
|
+
{% for tag in recipe.tags.all %}
|
|
110
|
+
<a href="{% url 'index' %}?tag={{ tag.name|urlencode }}" class="chip round surface">{{ tag.name }}</a>
|
|
111
|
+
{% endfor %}
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<div class="space"></div>
|
|
115
|
+
<div class="divider"></div>
|
|
116
|
+
<div class="space"></div>
|
|
117
|
+
|
|
118
|
+
<div class="row wrap">
|
|
119
|
+
{% if recipe.favorited_by.all %}
|
|
120
|
+
<span class="tiny-text">
|
|
121
|
+
{% if recipe.favorited_by.count == 1 %}
|
|
122
|
+
{% trans "Liked by" %} {{ recipe.favorited_by.first.username }}
|
|
123
|
+
{% else %}
|
|
124
|
+
{% trans "Liked by" %} {{ recipe.favorited_by.first.username }} {% trans "and" %} {{ recipe.favorited_by.count|add:"-1" }} {% trans "others" %}
|
|
125
|
+
{% endif %}
|
|
126
|
+
</span>
|
|
127
|
+
{% endif %}
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
<div class="space"></div>
|
|
131
|
+
<div class="divider"></div>
|
|
132
|
+
<div class="space"></div>
|
|
133
|
+
|
|
134
|
+
{% include "components/rating_section.html" %}
|
|
135
|
+
|
|
136
|
+
{% if user.is_authenticated and user.is_staff %}
|
|
137
|
+
<div class="space"></div>
|
|
138
|
+
<a href="/admin/sandwitches/recipe/{{ recipe.pk }}/change/" class="button transparent border round width-100 center-align">
|
|
139
|
+
<i>edit</i>
|
|
140
|
+
<span>{% trans "Edit Recipe" %}</span>
|
|
141
|
+
</a>
|
|
142
|
+
{% endif %}
|
|
143
|
+
</div>
|
|
144
|
+
</article>
|
|
145
|
+
</div>
|
|
146
|
+
|
|
147
|
+
<!-- Right Column: Details -->
|
|
148
|
+
<div class="s12 m12 l7">
|
|
149
|
+
<div class="padding">
|
|
150
|
+
|
|
151
|
+
<h5 class="primary-text">{% trans "Description" %}</h5>
|
|
152
|
+
<div class="large-text">
|
|
153
|
+
{% if recipe.description %}
|
|
154
|
+
{{ recipe.description|convert_markdown|safe }}
|
|
155
|
+
{% else %}
|
|
156
|
+
<p class="italic">{% trans "No description yet." %}</p>
|
|
157
|
+
{% endif %}
|
|
158
|
+
</div>
|
|
159
|
+
|
|
160
|
+
<div class="large-space"></div>
|
|
161
|
+
|
|
162
|
+
{% include "components/ingredients_section.html" %}
|
|
163
|
+
|
|
164
|
+
<div class="large-space"></div>
|
|
165
|
+
|
|
166
|
+
{% include "components/instructions_section.html" %}
|
|
54
167
|
</div>
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
<div style="margin-top:1rem;">
|
|
58
|
-
<h4>{% trans "Rating" %}</h4>
|
|
59
|
-
{% if rating_count %}
|
|
60
|
-
<p>{% trans "Average:" %} {{ avg_rating|floatformat:1 }} ({{ rating_count }} {% trans "vote" %}{% if rating_count %}s{% endif %})</p>
|
|
61
|
-
{% else %}
|
|
62
|
-
<p>{% trans "No ratings yet." %}</p>
|
|
63
|
-
{% endif %}
|
|
64
|
-
|
|
65
|
-
{% if user.is_authenticated %}
|
|
66
|
-
{% if user_rating %}
|
|
67
|
-
<p>{% trans "Your rating:" %} {{ user_rating.score }}</p>
|
|
68
|
-
{% else %}
|
|
69
|
-
<form method="post" action="{% url 'recipe_rate' pk=recipe.pk %}">
|
|
70
|
-
{% csrf_token %}
|
|
71
|
-
|
|
72
|
-
<fieldset>
|
|
73
|
-
<legend>{% trans "What would you rate this sandwich?" %}</legend>
|
|
74
|
-
|
|
75
|
-
{% if rating_form.score.errors %}
|
|
76
|
-
<div class="card-panel" role="alert">
|
|
77
|
-
<ul>
|
|
78
|
-
{% for err in rating_form.score.errors %}
|
|
79
|
-
<li>{{ err }}</li>
|
|
80
|
-
{% endfor %}
|
|
81
|
-
</ul>
|
|
82
|
-
</div>
|
|
83
|
-
{% endif %}
|
|
84
|
-
|
|
85
|
-
<div class="row">
|
|
86
|
-
<div class="col-sm-12">
|
|
87
|
-
<label for="{{ rating_form.score.id_for_label }}">
|
|
88
|
-
{% trans "Score (0-10):" %} <span id="score-output">{{ rating_form.score.value|default:"5.0" }}</span>
|
|
89
|
-
</label>
|
|
90
|
-
<input
|
|
91
|
-
type="range"
|
|
92
|
-
name="{{ rating_form.score.name }}"
|
|
93
|
-
id="{{ rating_form.score.id_for_label }}"
|
|
94
|
-
min="0"
|
|
95
|
-
max="10"
|
|
96
|
-
step="0.1"
|
|
97
|
-
value="{{ rating_form.score.value|default:'5.0' }}"
|
|
98
|
-
oninput="document.getElementById('score-output').innerText = parseFloat(this.value).toFixed(1)"
|
|
99
|
-
style="width: 100%;"
|
|
100
|
-
>
|
|
101
|
-
</div>
|
|
102
|
-
</div>
|
|
103
|
-
</fieldset>
|
|
104
|
-
<p style="margin-top:0.5rem;">
|
|
105
|
-
<button type="submit">{% if user_rating %}{% trans "Update" %}{% else %}{% trans "Rate" %}{% endif %}</button>
|
|
106
|
-
</p>
|
|
107
|
-
</form>
|
|
108
|
-
{% endif %}
|
|
109
|
-
{% else %}
|
|
110
|
-
<p><a href="{% url 'admin:login' %}">{% trans "Log in" %}</a> {% trans "to rate this recipe." %}</p>
|
|
111
|
-
{% endif %}
|
|
112
|
-
</div>
|
|
113
|
-
|
|
114
|
-
{% if user.is_authenticated and user.is_staff %}
|
|
115
|
-
<footer>
|
|
116
|
-
<a href="/admin/sandwitches/recipe/{{ recipe.pk }}/change/">{% trans "Edit" %}</a>
|
|
117
|
-
</footer>
|
|
118
|
-
{% endif %}
|
|
119
|
-
</article>
|
|
168
|
+
</div>
|
|
120
169
|
</div>
|
|
121
|
-
{% endblock %}
|
|
170
|
+
{% endblock %}
|
|
171
|
+
|
|
172
|
+
{% block extra_scripts %}
|
|
173
|
+
|
|
174
|
+
{{ block.super }}
|
|
175
|
+
|
|
176
|
+
{% include "components/ingredients_scripts.html" %}
|
|
177
|
+
|
|
178
|
+
{% endblock %}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{% extends "base_beer.html" %}
|
|
2
|
+
{% load i18n static %}
|
|
3
|
+
|
|
4
|
+
{% block title %}{% trans 'Favorites' %}{% endblock %}
|
|
5
|
+
|
|
6
|
+
{% block extra_head %}
|
|
7
|
+
{{ block.super }}
|
|
8
|
+
<style>
|
|
9
|
+
@media only screen and (min-width: 992px) {
|
|
10
|
+
main.container {
|
|
11
|
+
padding-left: 10%;
|
|
12
|
+
padding-right: 10%;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
</style>
|
|
16
|
+
{% endblock %}
|
|
17
|
+
|
|
18
|
+
{% block content %}
|
|
19
|
+
<div class="large-space"></div>
|
|
20
|
+
|
|
21
|
+
<article class="round primary s12 m12 l12">
|
|
22
|
+
<div class="row align-center">
|
|
23
|
+
<div class="max">
|
|
24
|
+
<h4 class="upper">{% trans 'Your Favorites' %}</h4>
|
|
25
|
+
<p class="large-text">{% trans 'The recipes you love the most.' %}</p>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
</article>
|
|
29
|
+
|
|
30
|
+
<div class="space"></div>
|
|
31
|
+
|
|
32
|
+
{% include "components/favorites_search_form.html" %}
|
|
33
|
+
|
|
34
|
+
<div class="large-space"></div>
|
|
35
|
+
|
|
36
|
+
{% include "partials/recipe_list.html" %}
|
|
37
|
+
|
|
38
|
+
{% endblock %}
|
|
39
|
+
|
|
40
|
+
{% block page_scripts %}
|
|
41
|
+
{% include "components/search_scripts.html" %}
|
|
42
|
+
{% endblock %}
|
sandwitches/templates/index.html
CHANGED
|
@@ -1,75 +1,42 @@
|
|
|
1
|
-
{% extends "
|
|
1
|
+
{% extends "base_beer.html" %}
|
|
2
|
+
{% load i18n static %}
|
|
2
3
|
|
|
3
4
|
{% block title %}Sandwitches{% endblock %}
|
|
4
5
|
|
|
6
|
+
{% block extra_head %}
|
|
7
|
+
{{ block.super }}
|
|
8
|
+
<style>
|
|
9
|
+
@media only screen and (min-width: 992px) {
|
|
10
|
+
main.container {
|
|
11
|
+
padding-left: 10%;
|
|
12
|
+
padding-right: 10%;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
</style>
|
|
16
|
+
{% endblock %}
|
|
17
|
+
|
|
5
18
|
{% block content %}
|
|
6
|
-
<div class="
|
|
7
|
-
|
|
8
|
-
|
|
19
|
+
<div class="large-space"></div>
|
|
20
|
+
|
|
21
|
+
<article class="round secondary s12 m12 l12">
|
|
22
|
+
<div class="row align-center">
|
|
23
|
+
<div class="max">
|
|
24
|
+
<h4 class="upper">{% trans 'Sandwitches' %}</h4>
|
|
25
|
+
<p class="large-text">{% trans 'Sandwiches so good, they haunt you!' %}</p>
|
|
26
|
+
</div>
|
|
9
27
|
</div>
|
|
10
|
-
|
|
11
|
-
<form role="search" onsubmit="return false;">
|
|
12
|
-
<input id="search" type="search" placeholder="{% trans "Search by title or tag" %}" aria-label="{% trans "Search" %}">
|
|
13
|
-
</form>
|
|
14
|
-
</div>
|
|
15
|
-
</div>
|
|
16
|
-
|
|
17
|
-
<section class="grid">
|
|
18
|
-
{% for recipe in recipes %}
|
|
19
|
-
<article class="card" onclick="location.href='{% url 'recipe_detail' recipe.slug %}';" style="cursor:pointer;">
|
|
20
|
-
{% if recipe.image %}
|
|
21
|
-
<figure class="recipe-figure">
|
|
22
|
-
<img src="{{ recipe.image_medium.url }}"
|
|
23
|
-
srcset="{{ recipe.image_thumbnail.url }} 150w, {{ recipe.image_medium.url }} 700w"
|
|
24
|
-
sizes="(max-width: 640px) 90vw, 45vw"
|
|
25
|
-
alt="{{ recipe.title }}"
|
|
26
|
-
loading="lazy">
|
|
27
|
-
</figure>
|
|
28
|
-
{% endif %}
|
|
28
|
+
</article>
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
<header class="card-title">{{ recipe.title }}</header>
|
|
30
|
+
<div class="space"></div>
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
{% for tag in recipe.tags.all %}
|
|
35
|
-
<span class="chip{% if tag|length < 8 %} chip--accent{% endif %}">{{ tag.name }}</span>
|
|
36
|
-
{% endfor %}
|
|
37
|
-
</div>
|
|
38
|
-
</div>
|
|
32
|
+
{% include "components/search_form.html" %}
|
|
39
33
|
|
|
40
|
-
|
|
41
|
-
<footer>
|
|
42
|
-
<a href="/admin/sandwitches/recipe/{{ recipe.pk }}/change/" rel="noopener">{% trans "Edit" %}</a>
|
|
43
|
-
</footer>
|
|
44
|
-
{% endif %}
|
|
45
|
-
</article>
|
|
34
|
+
<div class="large-space"></div>
|
|
46
35
|
|
|
47
|
-
|
|
48
|
-
<article class="card">
|
|
49
|
-
<div class="card-body">
|
|
50
|
-
<p>{% trans "No sandwitches yet, please stay tuned." %}</p>
|
|
51
|
-
</div>
|
|
52
|
-
</article>
|
|
53
|
-
{% endfor %}
|
|
54
|
-
</section>
|
|
36
|
+
{% include "partials/recipe_list.html" %}
|
|
55
37
|
|
|
56
38
|
{% endblock %}
|
|
57
39
|
|
|
58
40
|
{% block page_scripts %}
|
|
59
|
-
|
|
60
|
-
document.addEventListener('DOMContentLoaded', function() {
|
|
61
|
-
const search = document.getElementById('search');
|
|
62
|
-
if (!search) return;
|
|
63
|
-
search.addEventListener('input', function() {
|
|
64
|
-
const q = this.value.toLowerCase().trim();
|
|
65
|
-
document.querySelectorAll('section.grid article.card').forEach(card => {
|
|
66
|
-
const title = (card.querySelector('.card-title')?.textContent || '').toLowerCase();
|
|
67
|
-
const tags = Array.from(card.querySelectorAll('.tag, .chip')).map(t => t.textContent.toLowerCase());
|
|
68
|
-
const descr = (card.querySelector('.card-body p')?.textContent || '').toLowerCase();
|
|
69
|
-
const match = !q || title.includes(q) || descr.includes(q) || tags.some(t => t.includes(q));
|
|
70
|
-
card.style.display = match ? '' : 'none';
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
</script>
|
|
41
|
+
{% include "components/search_scripts.html" %}
|
|
75
42
|
{% endblock %}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{% load i18n %}
|
|
2
|
+
<div class="grid justify-center" id="recipe-grid">
|
|
3
|
+
{% for recipe in recipes %}
|
|
4
|
+
<div class="s12 m6 l4 xl3 recipe-card">
|
|
5
|
+
<article class="round no-padding elevate">
|
|
6
|
+
<div style="position: relative;">
|
|
7
|
+
{% if recipe.image %}
|
|
8
|
+
<img src="{{ recipe.image_medium.url }}" class="responsive top-round" style="height:220px; width:100%; object-fit:cover; cursor:pointer;" loading="lazy" alt="{{ recipe.title }}" onclick="location.href='{% url 'recipe_detail' recipe.slug %}';">
|
|
9
|
+
{% else %}
|
|
10
|
+
<div class="primary medium-height top-round middle-align center-align" style="height:220px; cursor:pointer;" onclick="location.href='{% url 'recipe_detail' recipe.slug %}';">
|
|
11
|
+
<i class="extra">lunch_dining</i>
|
|
12
|
+
</div>
|
|
13
|
+
{% endif %}
|
|
14
|
+
|
|
15
|
+
{% if user.is_authenticated %}
|
|
16
|
+
<div class="absolute top left padding">
|
|
17
|
+
<a href="{% url 'toggle_favorite' recipe.pk %}" class="button circle surface white-text">
|
|
18
|
+
<i class="{% if recipe in user.favorites.all %}primary-text{% else %}black-text{% endif %}">
|
|
19
|
+
{% if recipe in user.favorites.all %}favorite{% else %}favorite_border{% endif %}
|
|
20
|
+
</i>
|
|
21
|
+
</a>
|
|
22
|
+
</div>
|
|
23
|
+
{% endif %}
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div class="padding">
|
|
27
|
+
<h5 class="truncate pointer" onclick="location.href='{% url 'recipe_detail' recipe.slug %}';">{{ recipe.title }}</h5>
|
|
28
|
+
|
|
29
|
+
<div class="row align-center">
|
|
30
|
+
{% if recipe.uploaded_by %}
|
|
31
|
+
<a href="{% url 'index' %}?uploader={{ recipe.uploaded_by.username|urlencode }}" class="row align-center" style="color: inherit; text-decoration: none;">
|
|
32
|
+
{% if recipe.uploaded_by.avatar %}
|
|
33
|
+
<img src="{{ recipe.uploaded_by.avatar.url }}" class="circle tiny" alt="{{ recipe.uploaded_by.username }}">
|
|
34
|
+
{% else %}
|
|
35
|
+
<i class="tiny circle surface">person</i>
|
|
36
|
+
{% endif %}
|
|
37
|
+
<span class="tiny-text left-margin">{{ recipe.uploaded_by.username }}</span>
|
|
38
|
+
</a>
|
|
39
|
+
{% else %}
|
|
40
|
+
<i class="tiny circle surface">person_off</i>
|
|
41
|
+
<span class="tiny-text left-margin">{% trans "Unknown" %}</span>
|
|
42
|
+
{% endif %}
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<div class="space"></div>
|
|
46
|
+
|
|
47
|
+
<div class="row wrap">
|
|
48
|
+
{% if recipe.favorited_by.all %}
|
|
49
|
+
<span class="tiny-text">
|
|
50
|
+
{% if recipe.favorited_by.count == 1 %}
|
|
51
|
+
{% trans "Liked by" %} {{ recipe.favorited_by.first.username }}
|
|
52
|
+
{% else %}
|
|
53
|
+
{% trans "Liked by" %} {{ recipe.favorited_by.first.username }} {% trans "and" %} {{ recipe.favorited_by.count|add:"-1" }} {% trans "others" %}
|
|
54
|
+
{% endif %}
|
|
55
|
+
</span>
|
|
56
|
+
{% endif %}
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<div class="space"></div>
|
|
60
|
+
|
|
61
|
+
<div class="row wrap">
|
|
62
|
+
{% for tag in recipe.tags.all %}
|
|
63
|
+
<a href="{% url 'index' %}?tag={{ tag.name|urlencode }}" class="chip small round surface margin-bottom">{{ tag.name }}</a>
|
|
64
|
+
{% endfor %}
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<div class="padding pt-0">
|
|
69
|
+
<a href="{% url 'recipe_detail' recipe.slug %}" class="button fill round responsive">
|
|
70
|
+
<span>{% trans 'View Recipe' %}</span>
|
|
71
|
+
<i class="suffix">arrow_forward</i>
|
|
72
|
+
</a>
|
|
73
|
+
</div>
|
|
74
|
+
</article>
|
|
75
|
+
</div>
|
|
76
|
+
{% empty %}
|
|
77
|
+
<div class="s12 center-align">
|
|
78
|
+
<div class="large-space"></div>
|
|
79
|
+
<i class="extra gray-text">no_food</i>
|
|
80
|
+
<h5 class="gray-text">{% trans "No sandwitches found." %}</h5>
|
|
81
|
+
<p>{% trans "Be the first to create one!" %}</p>
|
|
82
|
+
{% if user.is_authenticated %}
|
|
83
|
+
<a href="{% url 'admin:sandwitches_recipe_add' %}" class="button primary round">{% trans "Add Recipe" %}</a>
|
|
84
|
+
{% endif %}
|
|
85
|
+
</div>
|
|
86
|
+
{% endfor %}
|
|
87
|
+
</div>
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
{% extends "base_beer.html" %}
|
|
2
|
+
{% load i18n %}
|
|
3
|
+
|
|
4
|
+
{% block title %}{% trans "Edit Recipe" %}{% endblock %}
|
|
5
|
+
|
|
6
|
+
{% block content %}
|
|
7
|
+
<div class="large-space"></div>
|
|
8
|
+
<article class="round s12 m10 l8 offset-m1 offset-l2 elevate">
|
|
9
|
+
<div class="padding">
|
|
10
|
+
<h4 class="center-align">
|
|
11
|
+
{% if recipe.pk %}
|
|
12
|
+
{% trans "Edit Recipe:" %} {{ recipe.title }}
|
|
13
|
+
{% else %}
|
|
14
|
+
{% trans "New Recipe" %}
|
|
15
|
+
{% endif %}
|
|
16
|
+
</h4>
|
|
17
|
+
<div class="space"></div>
|
|
18
|
+
|
|
19
|
+
<form method="post" enctype="multipart/form-data">
|
|
20
|
+
{% csrf_token %}
|
|
21
|
+
|
|
22
|
+
<div class="grid">
|
|
23
|
+
<div class="s12">
|
|
24
|
+
<div class="field label border round">
|
|
25
|
+
{{ form.title }}
|
|
26
|
+
<label>{% trans "Title" %}</label>
|
|
27
|
+
</div>
|
|
28
|
+
{% if form.title.errors %}
|
|
29
|
+
<p class="error-text tiny-text no-margin">{{ form.title.errors.0 }}</p>
|
|
30
|
+
{% endif %}
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<div class="s12">
|
|
34
|
+
<div class="field textarea label border round" style="height: auto;">
|
|
35
|
+
{{ form.description }}
|
|
36
|
+
<label>{% trans "Description" %}</label>
|
|
37
|
+
</div>
|
|
38
|
+
{% if form.description.errors %}
|
|
39
|
+
<p class="error-text tiny-text no-margin">{{ form.description.errors.0 }}</p>
|
|
40
|
+
{% endif %}
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<div class="s12 m6">
|
|
44
|
+
<div class="field textarea label border round">
|
|
45
|
+
{{ form.ingredients }}
|
|
46
|
+
<label>{% trans "Ingredients" %}</label>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<div class="s12 m6">
|
|
51
|
+
<div class="field textarea label border round">
|
|
52
|
+
{{ form.instructions }}
|
|
53
|
+
<label>{% trans "Instructions" %}</label>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<!-- Image Upload with Preview -->
|
|
58
|
+
<div class="s12 m6">
|
|
59
|
+
<div class="field label border round">
|
|
60
|
+
{{ form.image }}
|
|
61
|
+
<label>{% trans "Image" %}</label>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
<div class="s12 m6 center-align relative">
|
|
65
|
+
<div class="padding border round dashed surface-variant" style="min-height: 150px; display: flex; align-items: center; justify-content: center;">
|
|
66
|
+
<img id="image-preview" src="{% if recipe.image %}{{ recipe.image.url }}{% endif %}" class="responsive round" style="max-height: 200px; {% if not recipe.image %}display:none;{% endif %}">
|
|
67
|
+
<span id="image-placeholder" class="gray-text" {% if recipe.image %}style="display:none;"{% endif %}>{% trans "Image Preview" %}</span>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<div class="s12">
|
|
72
|
+
<div class="field label border round">
|
|
73
|
+
{{ form.tags }}
|
|
74
|
+
<label>{% trans "Tags (comma separated)" %}</label>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<div class="large-space"></div>
|
|
80
|
+
|
|
81
|
+
<div class="row right-align">
|
|
82
|
+
<!-- Fallback to index if referer is missing, or history.back -->
|
|
83
|
+
<a href="javascript:history.back()" class="button transparent border round">{% trans "Cancel" %}</a>
|
|
84
|
+
<button type="submit" class="button primary round">
|
|
85
|
+
<i class="front">save</i>
|
|
86
|
+
<span>{% trans "Save Recipe" %}</span>
|
|
87
|
+
</button>
|
|
88
|
+
</div>
|
|
89
|
+
</form>
|
|
90
|
+
</div>
|
|
91
|
+
</article>
|
|
92
|
+
<div class="large-space"></div>
|
|
93
|
+
|
|
94
|
+
{% endblock %}
|
|
95
|
+
|
|
96
|
+
{% block page_scripts %}
|
|
97
|
+
<script>
|
|
98
|
+
// Image Preview Logic
|
|
99
|
+
var imageInput = document.querySelector('input[type="file"]');
|
|
100
|
+
if (imageInput) {
|
|
101
|
+
imageInput.onchange = function (evt) {
|
|
102
|
+
var tgt = evt.target || window.event.srcElement,
|
|
103
|
+
files = tgt.files;
|
|
104
|
+
|
|
105
|
+
if (FileReader && files && files.length) {
|
|
106
|
+
var fr = new FileReader();
|
|
107
|
+
fr.onload = function () {
|
|
108
|
+
var preview = document.getElementById('image-preview');
|
|
109
|
+
var placeholder = document.getElementById('image-placeholder');
|
|
110
|
+
preview.src = fr.result;
|
|
111
|
+
preview.style.display = 'block';
|
|
112
|
+
if(placeholder) placeholder.style.display = 'none';
|
|
113
|
+
}
|
|
114
|
+
fr.readAsDataURL(files[0]);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
</script>
|
|
119
|
+
{% endblock %}
|
sandwitches/templates/setup.html
CHANGED