sandwitches 2.2.0__py3-none-any.whl → 2.3.1__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.
Files changed (32) hide show
  1. sandwitches/admin.py +23 -2
  2. sandwitches/api.py +22 -1
  3. sandwitches/forms.py +49 -0
  4. sandwitches/management/__init__.py +0 -0
  5. sandwitches/management/commands/__init__.py +0 -0
  6. sandwitches/management/commands/reset_daily_orders.py +14 -0
  7. sandwitches/migrations/0007_historicalrecipe_price_recipe_price_order.py +86 -0
  8. sandwitches/migrations/0008_historicalrecipe_daily_orders_count_and_more.py +36 -0
  9. sandwitches/migrations/0009_historicalrecipe_is_approved_recipe_is_approved.py +22 -0
  10. sandwitches/models.py +63 -1
  11. sandwitches/settings.py +1 -0
  12. sandwitches/tasks.py +74 -0
  13. sandwitches/templates/admin/admin_base.html +4 -0
  14. sandwitches/templates/admin/dashboard.html +125 -61
  15. sandwitches/templates/admin/order_list.html +30 -0
  16. sandwitches/templates/admin/partials/dashboard_charts.html +90 -0
  17. sandwitches/templates/admin/partials/order_rows.html +28 -0
  18. sandwitches/templates/admin/rating_list.html +2 -0
  19. sandwitches/templates/admin/recipe_form.html +26 -0
  20. sandwitches/templates/admin/recipe_list.html +65 -15
  21. sandwitches/templates/base.html +12 -0
  22. sandwitches/templates/components/navbar.html +10 -5
  23. sandwitches/templates/components/recipe_header.html +17 -0
  24. sandwitches/templates/components/side_menu.html +4 -0
  25. sandwitches/templates/partials/recipe_list.html +7 -0
  26. sandwitches/templates/profile.html +95 -0
  27. sandwitches/templates/recipe_form.html +15 -1
  28. sandwitches/urls.py +9 -0
  29. sandwitches/views.py +178 -21
  30. {sandwitches-2.2.0.dist-info → sandwitches-2.3.1.dist-info}/METADATA +1 -1
  31. {sandwitches-2.2.0.dist-info → sandwitches-2.3.1.dist-info}/RECORD +32 -22
  32. {sandwitches-2.2.0.dist-info → sandwitches-2.3.1.dist-info}/WHEEL +0 -0
@@ -127,37 +127,147 @@
127
127
 
128
128
 
129
129
 
130
- <!-- Charts Section -->
130
+ <!-- Charts Section -->
131
131
 
132
- <div class="s12 m6">
133
132
 
134
- <article class="round border padding">
135
133
 
136
- <h6 class="bold mb-1">{% trans "Recipes Over Time (Last 30 Days)" %}</h6>
134
+ <div class="s12">
137
135
 
138
- <canvas id="recipeChart" style="width:100%; max-height:300px;"></canvas>
139
136
 
140
- </article>
141
137
 
142
- </div>
138
+ {% include "admin/partials/dashboard_charts.html" %}
139
+
140
+
141
+
142
+ </div>
143
+
144
+
145
+
146
+
147
+
148
+
149
+
150
+ {% if pending_recipes %}
151
+
152
+
153
+
154
+ <div class="s12">
155
+
156
+
157
+
158
+ <h5 class="bold error-text">{% trans "Pending Approvals" %}</h5>
159
+
160
+
161
+
162
+ <table class="border striped no-space">
163
+
164
+
165
+
166
+ <thead>
167
+
168
+
169
+
170
+ <tr>
171
+
172
+
173
+
174
+ <th>{% trans "Title" %}</th>
175
+
176
+
177
+
178
+ <th>{% trans "Uploader" %}</th>
179
+
180
+
181
+
182
+ <th>{% trans "Created At" %}</th>
183
+
184
+
185
+
186
+ <th class="right-align">{% trans "Actions" %}</th>
187
+
188
+
189
+
190
+ </tr>
191
+
192
+
193
+
194
+ </thead>
195
+
196
+
197
+
198
+ <tbody>
199
+
200
+
201
+
202
+ {% for recipe in pending_recipes %}
203
+
204
+
205
+
206
+ <tr class="pointer" onclick="location.href='{% url 'admin_recipe_edit' recipe.pk %}'">
207
+
208
+
209
+
210
+ <td>{{ recipe.title }}</td>
211
+
212
+
213
+
214
+ <td>{{ recipe.uploaded_by.username|default:"-" }}</td>
215
+
216
+
217
+
218
+ <td>{{ recipe.created_at|date:"SHORT_DATETIME_FORMAT" }}</td>
219
+
220
+
221
+
222
+ <td class="right-align">
223
+
224
+
225
+
226
+ <a href="{% url 'admin_recipe_approve' recipe.pk %}" class="button circle transparent" onclick="event.stopPropagation();" title="{% trans 'Approve' %}"><i>check</i></a>
227
+
228
+
229
+
230
+ <a href="{% url 'admin_recipe_edit' recipe.pk %}" class="button circle transparent" onclick="event.stopPropagation();"><i>edit</i></a>
231
+
232
+
233
+
234
+ </td>
235
+
236
+
237
+
238
+ </tr>
239
+
240
+
241
+
242
+ {% endfor %}
243
+
244
+
245
+
246
+ </tbody>
247
+
248
+
249
+
250
+ </table>
251
+
252
+
253
+
254
+ </div>
255
+
256
+
257
+
258
+ {% endif %}
143
259
 
144
- <div class="s12 m6">
145
260
 
146
- <article class="round border padding">
147
261
 
148
- <h6 class="bold mb-1">{% trans "Average Rating Over Time (Last 30 Days)" %}</h6>
149
262
 
150
- <canvas id="ratingChart" style="width:100%; max-height:300px;"></canvas>
151
263
 
152
- </article>
153
264
 
154
- </div>
155
265
 
266
+ <div class="s12">
156
267
 
157
268
 
158
- <div class="s12">
159
269
 
160
- <h5 class="bold">{% trans "Recent Recipes" %}</h5>
270
+ <h5 class="bold">{% trans "Recent Recipes" %}</h5>
161
271
 
162
272
  <table class="border striped no-space">
163
273
 
@@ -213,50 +323,4 @@
213
323
 
214
324
  {% block admin_scripts %}
215
325
  <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
216
- <script>
217
- document.addEventListener('DOMContentLoaded', function() {
218
- const primaryColor = getComputedStyle(document.documentElement).getPropertyValue('--primary').trim() || '#5d4037';
219
- const secondaryColor = getComputedStyle(document.documentElement).getPropertyValue('--secondary').trim() || '#ff7a18';
220
-
221
- // Recipe Chart
222
- new Chart(document.getElementById('recipeChart'), {
223
- type: 'line',
224
- data: {
225
- labels: {{ recipe_labels|safe }},
226
- datasets: [{
227
- label: '{% trans "Recipes Created" %}',
228
- data: {{ recipe_counts|safe }},
229
- borderColor: primaryColor,
230
- backgroundColor: primaryColor + '33',
231
- fill: true,
232
- tension: 0.4
233
- }]
234
- },
235
- options: {
236
- responsive: true,
237
- plugins: { legend: { display: false } },
238
- scales: { y: { beginAtZero: true, ticks: { stepSize: 1 } } }
239
- }
240
- });
241
-
242
- // Rating Chart
243
- new Chart(document.getElementById('ratingChart'), {
244
- type: 'bar',
245
- data: {
246
- labels: {{ rating_labels|safe }},
247
- datasets: [{
248
- label: '{% trans "Avg Rating" %}',
249
- data: {{ rating_avgs|safe }},
250
- backgroundColor: secondaryColor,
251
- borderRadius: 4
252
- }]
253
- },
254
- options: {
255
- responsive: true,
256
- plugins: { legend: { display: false } },
257
- scales: { y: { min: 0, max: 10 } }
258
- }
259
- });
260
- });
261
- </script>
262
326
  {% endblock %}
@@ -0,0 +1,30 @@
1
+ {% extends "admin/admin_base.html" %}
2
+ {% load i18n %}
3
+
4
+ {% block admin_title %}{% trans "Orders" %}{% endblock %}
5
+
6
+ {% block content %}
7
+ <div class="padding">
8
+ <div class="row align-center">
9
+ <h5 class="max">{% trans "Orders" %}</h5>
10
+ </div>
11
+
12
+ <div class="space"></div>
13
+
14
+ <table class="border">
15
+ <thead>
16
+ <tr>
17
+ <th>ID</th>
18
+ <th>{% trans "User" %}</th>
19
+ <th>{% trans "Recipe" %}</th>
20
+ <th>{% trans "Price" %}</th>
21
+ <th>{% trans "Status" %}</th>
22
+ <th>{% trans "Date" %}</th>
23
+ </tr>
24
+ </thead>
25
+ <tbody hx-get="{% url 'admin_order_list' %}" hx-trigger="every 5s">
26
+ {% include "admin/partials/order_rows.html" %}
27
+ </tbody>
28
+ </table>
29
+ </div>
30
+ {% endblock %}
@@ -0,0 +1,90 @@
1
+ {% load i18n %}
2
+ <div class="grid" hx-get="." hx-trigger="every 30s" hx-swap="outerHTML">
3
+ <div class="s12 m4">
4
+ <article class="round border padding">
5
+ <h6 class="bold mb-1">{% trans "Recipes Over Time" %}</h6>
6
+ <canvas id="recipeChart" style="width:100%; max-height:300px;"></canvas>
7
+ </article>
8
+ </div>
9
+ <div class="s12 m4">
10
+ <article class="round border padding">
11
+ <h6 class="bold mb-1">{% trans "Orders Over Time" %}</h6>
12
+ <canvas id="orderChart" style="width:100%; max-height:300px;"></canvas>
13
+ </article>
14
+ </div>
15
+ <div class="s12 m4">
16
+ <article class="round border padding">
17
+ <h6 class="bold mb-1">{% trans "Avg Rating" %}</h6>
18
+ <canvas id="ratingChart" style="width:100%; max-height:300px;"></canvas>
19
+ </article>
20
+ </div>
21
+
22
+ <script>
23
+ (function() {
24
+ const primaryColor = getComputedStyle(document.documentElement).getPropertyValue('--primary').trim() || '#5d4037';
25
+ const secondaryColor = getComputedStyle(document.documentElement).getPropertyValue('--secondary').trim() || '#ff7a18';
26
+ const tertiaryColor = getComputedStyle(document.documentElement).getPropertyValue('--tertiary').trim() || '#4caf50';
27
+
28
+ // Recipe Chart
29
+ new Chart(document.getElementById('recipeChart'), {
30
+ type: 'line',
31
+ data: {
32
+ labels: {{ recipe_labels|safe }},
33
+ datasets: [{
34
+ label: '{% trans "Recipes" %}',
35
+ data: {{ recipe_counts|safe }},
36
+ borderColor: primaryColor,
37
+ backgroundColor: primaryColor + '33',
38
+ fill: true,
39
+ tension: 0.4
40
+ }]
41
+ },
42
+ options: {
43
+ responsive: true,
44
+ plugins: { legend: { display: false } },
45
+ scales: { y: { beginAtZero: true, ticks: { stepSize: 1 } } }
46
+ }
47
+ });
48
+
49
+ // Order Chart
50
+ new Chart(document.getElementById('orderChart'), {
51
+ type: 'line',
52
+ data: {
53
+ labels: {{ order_labels|safe }},
54
+ datasets: [{
55
+ label: '{% trans "Orders" %}',
56
+ data: {{ order_counts|safe }},
57
+ borderColor: tertiaryColor,
58
+ backgroundColor: tertiaryColor + '33',
59
+ fill: true,
60
+ tension: 0.4
61
+ }]
62
+ },
63
+ options: {
64
+ responsive: true,
65
+ plugins: { legend: { display: false } },
66
+ scales: { y: { beginAtZero: true, ticks: { stepSize: 1 } } }
67
+ }
68
+ });
69
+
70
+ // Rating Chart
71
+ new Chart(document.getElementById('ratingChart'), {
72
+ type: 'bar',
73
+ data: {
74
+ labels: {{ rating_labels|safe }},
75
+ datasets: [{
76
+ label: '{% trans "Avg Rating" %}',
77
+ data: {{ rating_avgs|safe }},
78
+ backgroundColor: secondaryColor,
79
+ borderRadius: 4
80
+ }]
81
+ },
82
+ options: {
83
+ responsive: true,
84
+ plugins: { legend: { display: false } },
85
+ scales: { y: { min: 0, max: 10 } }
86
+ }
87
+ });
88
+ })();
89
+ </script>
90
+ </div>
@@ -0,0 +1,28 @@
1
+ {% load i18n %}
2
+ {% for order in orders %}
3
+ <tr>
4
+ <td>{{ order.id }}</td>
5
+ <td>
6
+ <div class="row align-center">
7
+ {% if order.user.avatar %}
8
+ <img src="{{ order.user.avatar.url }}" class="circle tiny">
9
+ {% else %}
10
+ <i class="circle tiny">person</i>
11
+ {% endif %}
12
+ <span class="max">{{ order.user.username }}</span>
13
+ </div>
14
+ </td>
15
+ <td>{{ order.recipe.title }}</td>
16
+ <td>{{ order.total_price }} €</td>
17
+ <td>
18
+ <span class="chip {% if order.status == 'PENDING' %}surface-variant{% elif order.status == 'COMPLETED' %}primary{% else %}error{% endif %}">
19
+ {{ order.get_status_display }}
20
+ </span>
21
+ </td>
22
+ <td>{{ order.created_at|date:"d/m/Y H:i" }}</td>
23
+ </tr>
24
+ {% empty %}
25
+ <tr>
26
+ <td colspan="6" class="center-align">{% trans "No orders found." %}</td>
27
+ </tr>
28
+ {% endfor %}
@@ -12,6 +12,7 @@
12
12
  <th>{% trans "Recipe" %}</th>
13
13
  <th>{% trans "User" %}</th>
14
14
  <th class="min center-align">{% trans "Score" %}</th>
15
+ <th>{% trans "Comment" %}</th>
15
16
  <th>{% trans "Updated" %}</th>
16
17
  <th class="right-align">{% trans "Actions" %}</th>
17
18
  </tr>
@@ -27,6 +28,7 @@
27
28
  <span>{{ r.score }}</span>
28
29
  </div>
29
30
  </td>
31
+ <td>{{ r.comment|default:"-"|truncatechars:50 }}</td>
30
32
  <td>{{ r.updated_at|date:"SHORT_DATETIME_FORMAT" }}</td>
31
33
  <td class="right-align">
32
34
  <a href="{% url 'admin_rating_delete' r.pk %}" class="button circle transparent" title="{% trans 'Delete' %}"><i>delete</i></a>
@@ -60,6 +60,32 @@
60
60
  <label>{{ form.uploaded_by.label }}</label>
61
61
  {% if form.uploaded_by.errors %}<span class="error">{{ form.uploaded_by.errors|striptags }}</span>{% endif %}
62
62
  </div>
63
+
64
+ <div class="field label border round mt-1">
65
+ {{ form.price }}
66
+ <label>{{ form.price.label }}</label>
67
+ {% if form.price.errors %}<span class="error">{{ form.price.errors|striptags }}</span>{% endif %}
68
+ </div>
69
+
70
+ <div class="field label border round mt-1">
71
+ {{ form.max_daily_orders }}
72
+ <label>{{ form.max_daily_orders.label }}</label>
73
+ {% if form.max_daily_orders.errors %}<span class="error">{{ form.max_daily_orders.errors|striptags }}</span>{% endif %}
74
+ </div>
75
+
76
+ <div class="field middle-align mt-1">
77
+ <label class="checkbox">
78
+ {{ form.is_highlighted }}
79
+ <span>{{ form.is_highlighted.label }}</span>
80
+ </label>
81
+ </div>
82
+
83
+ <div class="field middle-align mt-1">
84
+ <label class="checkbox">
85
+ {{ form.is_approved }}
86
+ <span>{{ form.is_approved.label }}</span>
87
+ </label>
88
+ </div>
63
89
  </article>
64
90
 
65
91
  <!-- Description -->
@@ -12,21 +12,48 @@
12
12
  </a>
13
13
  </div>
14
14
 
15
- <table class="border striped no-space">
16
- <thead>
17
- <tr>
18
- <th class="min">{% trans "ID" %}</th>
19
- <th class="min">{% trans "Image" %}</th>
20
- <th class="max">{% trans "Title" %}</th>
21
- <th class="min">{% trans "Rating" %}</th>
22
- <th class="m l">{% trans "Tags" %}</th>
23
- <th>{% trans "Uploader" %}</th>
24
- <th>{% trans "Created" %}</th>
25
- <th class="right-align">{% trans "Actions" %}</th>
26
- </tr>
27
- </thead>
28
- <tbody>
29
- {% for recipe in recipes %}
15
+ <table class="border striped no-space">
16
+ <thead>
17
+ <tr>
18
+ <th class="min">{% trans "ID" %}</th>
19
+ <th class="min">{% trans "Image" %}</th>
20
+ <th class="max">
21
+ <a href="?sort={% if current_sort == 'title' %}-title{% else %}title{% endif %}" class="row align-center">
22
+ {% trans "Title" %}
23
+ {% if current_sort == 'title' %}<i>arrow_upward</i>{% elif current_sort == '-title' %}<i>arrow_downward</i>{% endif %}
24
+ </a>
25
+ </th>
26
+ <th class="min">
27
+ <a href="?sort={% if current_sort == 'price' %}-price{% else %}price{% endif %}" class="row align-center">
28
+ {% trans "Price" %}
29
+ {% if current_sort == 'price' %}<i>arrow_upward</i>{% elif current_sort == '-price' %}<i>arrow_downward</i>{% endif %}
30
+ </a>
31
+ </th>
32
+ <th class="min">
33
+ <a href="?sort={% if current_sort == 'orders' %}-orders{% else %}orders{% endif %}" class="row align-center">
34
+ {% trans "Orders" %}
35
+ {% if current_sort == 'orders' %}<i>arrow_upward</i>{% elif current_sort == '-orders' %}<i>arrow_downward</i>{% endif %}
36
+ </a>
37
+ </th>
38
+ <th class="min">{% trans "Rating" %}</th>
39
+ <th class="min">{% trans "Approved" %}</th>
40
+ <th class="m l">{% trans "Tags" %}</th>
41
+ <th>
42
+ <a href="?sort={% if current_sort == 'uploader' %}-uploader{% else %}uploader{% endif %}" class="row align-center">
43
+ {% trans "Uploader" %}
44
+ {% if current_sort == 'uploader' %}<i>arrow_upward</i>{% elif current_sort == '-uploader' %}<i>arrow_downward</i>{% endif %}
45
+ </a>
46
+ </th>
47
+ <th>
48
+ <a href="?sort={% if current_sort == 'created_at' %}-created_at{% else %}created_at{% endif %}" class="row align-center">
49
+ {% trans "Created" %}
50
+ {% if current_sort == 'created_at' %}<i>arrow_upward</i>{% elif current_sort == '-created_at' %}<i>arrow_downward</i>{% endif %}
51
+ </a>
52
+ </th>
53
+ <th class="right-align">{% trans "Actions" %}</th>
54
+ </tr>
55
+ </thead>
56
+ <tbody> {% for recipe in recipes %}
30
57
  <tr class="pointer" onclick="location.href='{% url 'admin_recipe_edit' recipe.pk %}'">
31
58
  <td class="min">{{ recipe.id }}</td>
32
59
  <td class="min">
@@ -39,14 +66,37 @@
39
66
  {% endif %}
40
67
  </td>
41
68
  <td class="max">
69
+ {% if recipe.is_highlighted %}
70
+ <i class="tiny amber-text">star</i>
71
+ {% endif %}
42
72
  <b>{{ recipe.title }}</b>
43
73
  </td>
74
+ <td class="min no-wrap">
75
+ {% if recipe.price %}
76
+ {{ recipe.price }} €
77
+ {% else %}
78
+ -
79
+ {% endif %}
80
+ </td>
81
+ <td class="min no-wrap">
82
+ {{ recipe.daily_orders_count }} / {{ recipe.max_daily_orders|default:"∞" }}
83
+ </td>
44
84
  <td class="min center-align">
45
85
  <div class="row align-center">
46
86
  <i class="primary-text">star</i>
47
87
  <span>{{ recipe.avg_rating|default:0|floatformat:1 }}</span>
48
88
  </div>
49
89
  </td>
90
+ <td class="min center-align">
91
+ {% if recipe.is_approved %}
92
+ <i class="primary-text">check_circle</i>
93
+ {% else %}
94
+ <a href="{% url 'admin_recipe_approve' recipe.pk %}" class="button tiny primary round" onclick="event.stopPropagation();">
95
+ <i>check</i>
96
+ <span>{% trans "Approve" %}</span>
97
+ </a>
98
+ {% endif %}
99
+ </td>
50
100
  <td class="m l">
51
101
  {% for tag in recipe.tags.all %}
52
102
  <span class="chip tiny">{{ tag.name }}</span>
@@ -56,6 +56,18 @@
56
56
  </svg>
57
57
  </div>
58
58
  {% block navbar %}{% endblock %}
59
+
60
+ {% if messages %}
61
+ <div class="padding no-margin">
62
+ {% for message in messages %}
63
+ <div class="snackbar active {% if message.tags == 'error' %}error{% else %}primary{% endif %}">
64
+ <i>{% if message.tags == 'error' %}error{% else %}info{% endif %}</i>
65
+ <span>{{ message }}</span>
66
+ </div>
67
+ {% endfor %}
68
+ </div>
69
+ {% endif %}
70
+
59
71
  <main class="container" role="main">{% block content %}{% endblock %}</main>
60
72
  {% block footer %}{% endblock %}
61
73
  {% block extra_scripts %}{% endblock %}
@@ -15,11 +15,16 @@
15
15
  </button>
16
16
 
17
17
  {% if user.is_authenticated %}
18
- {% if user.avatar %}
19
- <img src="{{ user.avatar.url }}" class="circle" data-ui="#user-menu">
20
- {% else %}
21
- <img src="https://www.w3schools.com/howto/img_avatar.png" class="circle" data-ui="#user-menu">
22
- {% endif %}
18
+ <a href="{% url 'user_profile' %}">
19
+ {% if user.avatar %}
20
+ <img src="{{ user.avatar.url }}" class="circle">
21
+ {% else %}
22
+ <img src="https://www.w3schools.com/howto/img_avatar.png" class="circle">
23
+ {% endif %}
24
+ </a>
25
+ <button class="circle transparent" data-ui="#user-menu">
26
+ <i>more_vert</i>
27
+ </button>
23
28
  {% else %}
24
29
  <a href="{% url 'login' %}"><button class="chip transparent border white-text">{% trans "Login" %}</button></a>
25
30
  <a href="{% url 'signup' %}"><button class="chip primary">{% trans "Sign up" %}</button></a>
@@ -30,3 +30,20 @@
30
30
  </div>
31
31
  {% endif %}
32
32
  </div>
33
+
34
+ <div class="space"></div>
35
+ {% if recipe.price %}
36
+ <div class="row align-center">
37
+ <i class="primary-text">euro_symbol</i>
38
+ <h5 class="bold ml-1">{{ recipe.price }}</h5>
39
+ {% if user.is_authenticated %}
40
+ <form action="{% url 'order_recipe' recipe.pk %}" method="post" class="ml-2">
41
+ {% csrf_token %}
42
+ <button type="submit" class="button primary round">
43
+ <i>shopping_cart</i>
44
+ <span>{% trans "Order Now" %}</span>
45
+ </button>
46
+ </form>
47
+ {% endif %}
48
+ </div>
49
+ {% endif %}
@@ -15,6 +15,10 @@
15
15
  <i class="extra padding">favorite</i>
16
16
  <span class="large-text">{% trans "Favorites" %}</span>
17
17
  </a>
18
+ <a href="{% url 'submit_recipe' %}" class="padding {% if request.resolver_match.url_name == 'submit_recipe' %}active{% endif %}">
19
+ <i class="extra padding">add_circle</i>
20
+ <span class="large-text">{% trans "Submit Recipe" %}</span>
21
+ </a>
18
22
  {% endif %}
19
23
  <a href="/api/docs" class="padding">
20
24
  <i class="extra padding">api</i>
@@ -26,6 +26,13 @@
26
26
  <div class="padding">
27
27
  <h5 class="truncate pointer" onclick="location.href='{% url 'recipe_detail' recipe.slug %}';">{{ recipe.title }}</h5>
28
28
 
29
+ {% if recipe.price %}
30
+ <div class="row align-center mb-1">
31
+ <i class="small-text primary-text">euro_symbol</i>
32
+ <span class="bold">{{ recipe.price }}</span>
33
+ </div>
34
+ {% endif %}
35
+
29
36
  <div class="row align-center">
30
37
  {% if recipe.uploaded_by %}
31
38
  <a href="{% url 'index' %}?uploader={{ recipe.uploaded_by.username|urlencode }}" class="row align-center" style="color: inherit; text-decoration: none;">