sandwitches 2.3.0__py3-none-any.whl → 2.3.2__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 (30) hide show
  1. sandwitches/api.py +22 -1
  2. sandwitches/forms.py +36 -0
  3. sandwitches/management/__init__.py +0 -0
  4. sandwitches/management/commands/__init__.py +0 -0
  5. sandwitches/management/commands/reset_daily_orders.py +14 -0
  6. sandwitches/migrations/0008_historicalrecipe_daily_orders_count_and_more.py +36 -0
  7. sandwitches/migrations/0009_historicalrecipe_is_approved_recipe_is_approved.py +22 -0
  8. sandwitches/migrations/0010_rename_is_approved_historicalrecipe_is_community_made_and_more.py +22 -0
  9. sandwitches/migrations/0011_alter_historicalrecipe_is_community_made_and_more.py +25 -0
  10. sandwitches/models.py +28 -1
  11. sandwitches/settings.py +1 -0
  12. sandwitches/tasks.py +74 -0
  13. sandwitches/templates/admin/admin_base.html +8 -4
  14. sandwitches/templates/admin/dashboard.html +132 -69
  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/{recipe_form.html → community.html} +30 -8
  23. sandwitches/templates/components/recipe_header.html +9 -0
  24. sandwitches/templates/components/side_menu.html +4 -0
  25. sandwitches/templates/profile.html +1 -1
  26. sandwitches/urls.py +8 -0
  27. sandwitches/views.py +178 -21
  28. {sandwitches-2.3.0.dist-info → sandwitches-2.3.2.dist-info}/METADATA +1 -1
  29. {sandwitches-2.3.0.dist-info → sandwitches-2.3.2.dist-info}/RECORD +30 -20
  30. {sandwitches-2.3.0.dist-info → sandwitches-2.3.2.dist-info}/WHEEL +0 -0
@@ -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_community_made }}
86
+ <span>{{ form.is_community_made.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_community_made %}
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 %}
@@ -1,18 +1,15 @@
1
1
  {% extends "base_beer.html" %}
2
2
  {% load i18n %}
3
3
 
4
- {% block title %}{% trans "Edit Recipe" %}{% endblock %}
4
+ {% block title %}{% trans "Community" %}{% endblock %}
5
5
 
6
6
  {% block content %}
7
+ <main class="responsive">
7
8
  <div class="large-space"></div>
8
9
  <article class="round s12 m10 l8 offset-m1 offset-l2 elevate">
9
10
  <div class="padding">
10
11
  <h4 class="center-align">
11
- {% if recipe.pk %}
12
- {% trans "Edit Recipe:" %} {{ recipe.title }}
13
- {% else %}
14
- {% trans "New Recipe" %}
15
- {% endif %}
12
+ {% trans "Submit a Recipe" %}
16
13
  </h4>
17
14
  <div class="space"></div>
18
15
 
@@ -68,9 +65,23 @@
68
65
  </div>
69
66
  </div>
70
67
 
68
+ <div class="s12 m6">
69
+ <div class="field label border round">
70
+ {{ form.price }}
71
+ <label>{% trans "Price" %}</label>
72
+ </div>
73
+ </div>
74
+
75
+ <div class="s12 m6">
76
+ <div class="field label border round">
77
+ {{ form.servings }}
78
+ <label>{% trans "Servings" %}</label>
79
+ </div>
80
+ </div>
81
+
71
82
  <div class="s12">
72
83
  <div class="field label border round">
73
- {{ form.tags }}
84
+ {{ form.tags_string }}
74
85
  <label>{% trans "Tags (comma separated)" %}</label>
75
86
  </div>
76
87
  </div>
@@ -83,16 +94,27 @@
83
94
  <a href="javascript:history.back()" class="button transparent border round">{% trans "Cancel" %}</a>
84
95
  <button type="submit" class="button primary round">
85
96
  <i class="front">save</i>
86
- <span>{% trans "Save Recipe" %}</span>
97
+ <span>{% trans "Submit Recipe" %}</span>
87
98
  </button>
88
99
  </div>
89
100
  </form>
90
101
  </div>
91
102
  </article>
103
+
104
+
105
+ <div class="large-space"></div>
106
+
107
+ <h4 class="center-align">{% trans "Community Recipes" %}</h4>
108
+ <div class="space"></div>
109
+
110
+ {% include "partials/recipe_list.html" %}
111
+
92
112
  <div class="large-space"></div>
93
113
 
94
114
  {% endblock %}
95
115
 
116
+ </main>
117
+
96
118
  {% block page_scripts %}
97
119
  <script>
98
120
  // Image Preview Logic
@@ -36,5 +36,14 @@
36
36
  <div class="row align-center">
37
37
  <i class="primary-text">euro_symbol</i>
38
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 %}
39
48
  </div>
40
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 'community' %}" class="padding {% if request.resolver_match.url_name == 'community' %}active{% endif %}">
19
+ <i class="extra padding">group</i>
20
+ <span class="large-text">{% trans "Community" %}</span>
21
+ </a>
18
22
  {% endif %}
19
23
  <a href="/api/docs" class="padding">
20
24
  <i class="extra padding">api</i>
@@ -1,4 +1,4 @@
1
- {% extends "base.html" %}
1
+ {% extends "base_beer.html" %}
2
2
  {% load static i18n %}
3
3
  {% block title %}{% trans "Your Profile" %}{% endblock %}
4
4
 
sandwitches/urls.py CHANGED
@@ -34,6 +34,7 @@ urlpatterns = [
34
34
  path("login/", views.CustomLoginView.as_view(), name="login"),
35
35
  path("logout/", LogoutView.as_view(next_page="index"), name="logout"),
36
36
  path("profile/", views.user_profile, name="user_profile"),
37
+ path("community/", views.community, name="community"),
37
38
  path("admin/", admin.site.urls),
38
39
  path("api/", api.urls),
39
40
  path("media/<path:file_path>", views.media, name="media"),
@@ -49,6 +50,7 @@ urlpatterns += i18n_patterns(
49
50
  path("recipes/<slug:slug>/", views.recipe_detail, name="recipe_detail"),
50
51
  path("setup/", views.setup, name="setup"),
51
52
  path("recipes/<int:pk>/rate/", views.recipe_rate, name="recipe_rate"),
53
+ path("recipes/<int:pk>/order/", views.order_recipe, name="order_recipe"),
52
54
  path("recipes/<int:pk>/favorite/", views.toggle_favorite, name="toggle_favorite"),
53
55
  path("dashboard/", views.admin_dashboard, name="admin_dashboard"),
54
56
  path("dashboard/recipes/", views.admin_recipe_list, name="admin_recipe_list"),
@@ -63,6 +65,11 @@ urlpatterns += i18n_patterns(
63
65
  views.admin_recipe_delete,
64
66
  name="admin_recipe_delete",
65
67
  ),
68
+ path(
69
+ "dashboard/recipes/<int:pk>/approve/",
70
+ views.admin_recipe_approve,
71
+ name="admin_recipe_approve",
72
+ ),
66
73
  path(
67
74
  "dashboard/recipes/<int:pk>/rotate/",
68
75
  views.admin_recipe_rotate,
@@ -99,6 +106,7 @@ urlpatterns += i18n_patterns(
99
106
  views.admin_rating_delete,
100
107
  name="admin_rating_delete",
101
108
  ),
109
+ path("dashboard/orders/", views.admin_order_list, name="admin_order_list"),
102
110
  prefix_default_language=True,
103
111
  )
104
112