sandwitches 2.3.3__tar.gz → 2.4.1__tar.gz
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-2.3.3 → sandwitches-2.4.1}/PKG-INFO +1 -1
- {sandwitches-2.3.3 → sandwitches-2.4.1}/pyproject.toml +1 -1
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/admin.py +10 -2
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/forms.py +66 -6
- sandwitches-2.4.1/src/sandwitches/migrations/0012_rename_is_community_made_historicalrecipe_is_approved_and_more.py +22 -0
- sandwitches-2.4.1/src/sandwitches/migrations/0013_cartitem.py +55 -0
- sandwitches-2.4.1/src/sandwitches/migrations/0014_ensure_groups_exist.py +22 -0
- sandwitches-2.4.1/src/sandwitches/migrations/0015_order_completed_alter_order_status_and_more.py +56 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/models.py +35 -2
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/admin/admin_base.html +4 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/admin/dashboard.html +1 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/admin/partials/order_rows.html +1 -1
- sandwitches-2.4.1/src/sandwitches/templates/admin/recipe_approval_list.html +56 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/admin/recipe_form.html +115 -20
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/admin/recipe_list.html +1 -1
- sandwitches-2.4.1/src/sandwitches/templates/cart.html +102 -0
- sandwitches-2.4.1/src/sandwitches/templates/community.html +238 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/components/navbar.html +6 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/components/recipe_header.html +3 -3
- sandwitches-2.4.1/src/sandwitches/templates/order_detail.html +68 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/partials/recipe_list.html +11 -3
- sandwitches-2.4.1/src/sandwitches/templates/profile.html +185 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/urls.py +13 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/views.py +204 -17
- sandwitches-2.3.3/src/sandwitches/templates/community.html +0 -141
- sandwitches-2.3.3/src/sandwitches/templates/profile.html +0 -95
- {sandwitches-2.3.3 → sandwitches-2.4.1}/README.md +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/__init__.py +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/api.py +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/asgi.py +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/feeds.py +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/locale/nl/LC_MESSAGES/django.mo +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/locale/nl/LC_MESSAGES/django.po +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/management/__init__.py +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/management/commands/__init__.py +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/management/commands/reset_daily_orders.py +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/migrations/0001_initial.py +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/migrations/0002_historicalrecipe_servings_recipe_servings.py +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/migrations/0003_setting.py +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/migrations/0004_alter_setting_ai_api_key_and_more.py +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/migrations/0005_rating_comment.py +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/migrations/0006_historicalrecipe_is_highlighted_and_more.py +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/migrations/0007_historicalrecipe_price_recipe_price_order.py +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/migrations/0008_historicalrecipe_daily_orders_count_and_more.py +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/migrations/0009_historicalrecipe_is_approved_recipe_is_approved.py +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/migrations/0010_rename_is_approved_historicalrecipe_is_community_made_and_more.py +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/migrations/0011_alter_historicalrecipe_is_community_made_and_more.py +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/migrations/__init__.py +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/settings.py +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/storage.py +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/tasks.py +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/admin/confirm_delete.html +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/admin/order_list.html +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/admin/partials/dashboard_charts.html +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/admin/rating_list.html +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/admin/tag_form.html +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/admin/tag_list.html +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/admin/task_detail.html +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/admin/task_list.html +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/admin/user_form.html +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/admin/user_list.html +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/base.html +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/base_beer.html +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/components/carousel_scripts.html +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/components/favorites_search_form.html +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/components/footer.html +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/components/ingredients_scripts.html +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/components/ingredients_section.html +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/components/instructions_section.html +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/components/language_dialog.html +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/components/rating_section.html +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/components/search_form.html +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/components/search_scripts.html +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/components/side_menu.html +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/components/user_menu.html +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/detail.html +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/favorites.html +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/index.html +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/login.html +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/setup.html +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/signup.html +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templatetags/__init__.py +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templatetags/custom_filters.py +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templatetags/markdown_extras.py +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/utils.py +0 -0
- {sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/wsgi.py +0 -0
|
@@ -84,7 +84,15 @@ class RatingAdmin(ImportExportModelAdmin):
|
|
|
84
84
|
@admin.register(Order)
|
|
85
85
|
class OrderAdmin(ImportExportModelAdmin):
|
|
86
86
|
resource_classes = [OrderResource]
|
|
87
|
-
list_display = (
|
|
88
|
-
|
|
87
|
+
list_display = (
|
|
88
|
+
"id",
|
|
89
|
+
"user",
|
|
90
|
+
"recipe",
|
|
91
|
+
"status",
|
|
92
|
+
"completed",
|
|
93
|
+
"total_price",
|
|
94
|
+
"created_at",
|
|
95
|
+
)
|
|
96
|
+
list_filter = ("status", "completed", "created_at")
|
|
89
97
|
search_fields = ("user__username", "recipe__title")
|
|
90
98
|
readonly_fields = ("total_price", "created_at", "updated_at")
|
|
@@ -86,6 +86,8 @@ class UserSignupForm(UserCreationForm, BaseUserFormMixin):
|
|
|
86
86
|
|
|
87
87
|
|
|
88
88
|
class UserProfileForm(forms.ModelForm):
|
|
89
|
+
image_data = forms.CharField(widget=forms.HiddenInput(), required=False)
|
|
90
|
+
|
|
89
91
|
class Meta:
|
|
90
92
|
model = User
|
|
91
93
|
fields = (
|
|
@@ -96,8 +98,25 @@ class UserProfileForm(forms.ModelForm):
|
|
|
96
98
|
"bio",
|
|
97
99
|
)
|
|
98
100
|
|
|
101
|
+
def save(self, commit=True):
|
|
102
|
+
user = super().save(commit=False)
|
|
103
|
+
image_data = self.cleaned_data.get("image_data")
|
|
104
|
+
if image_data and image_data.startswith("data:image"):
|
|
105
|
+
import base64
|
|
106
|
+
from django.core.files.base import ContentFile
|
|
107
|
+
|
|
108
|
+
format, imgstr = image_data.split(";base64,")
|
|
109
|
+
ext = format.split("/")[-1]
|
|
110
|
+
data = ContentFile(base64.b64decode(imgstr), name=f"avatar.{ext}")
|
|
111
|
+
user.avatar = data
|
|
112
|
+
if commit:
|
|
113
|
+
user.save()
|
|
114
|
+
return user
|
|
115
|
+
|
|
99
116
|
|
|
100
117
|
class UserEditForm(forms.ModelForm):
|
|
118
|
+
image_data = forms.CharField(widget=forms.HiddenInput(), required=False)
|
|
119
|
+
|
|
101
120
|
class Meta:
|
|
102
121
|
model = User
|
|
103
122
|
fields = (
|
|
@@ -112,6 +131,21 @@ class UserEditForm(forms.ModelForm):
|
|
|
112
131
|
"bio",
|
|
113
132
|
)
|
|
114
133
|
|
|
134
|
+
def save(self, commit=True):
|
|
135
|
+
user = super().save(commit=False)
|
|
136
|
+
image_data = self.cleaned_data.get("image_data")
|
|
137
|
+
if image_data and image_data.startswith("data:image"):
|
|
138
|
+
import base64
|
|
139
|
+
from django.core.files.base import ContentFile
|
|
140
|
+
|
|
141
|
+
format, imgstr = image_data.split(";base64,")
|
|
142
|
+
ext = format.split("/")[-1]
|
|
143
|
+
data = ContentFile(base64.b64decode(imgstr), name=f"avatar.{ext}")
|
|
144
|
+
user.avatar = data
|
|
145
|
+
if commit:
|
|
146
|
+
user.save()
|
|
147
|
+
return user
|
|
148
|
+
|
|
115
149
|
|
|
116
150
|
class TagForm(forms.ModelForm):
|
|
117
151
|
class Meta:
|
|
@@ -126,6 +160,7 @@ class RecipeForm(forms.ModelForm):
|
|
|
126
160
|
widget=forms.TextInput(attrs={"placeholder": _("e.g. spicy, vegan, quick")}),
|
|
127
161
|
)
|
|
128
162
|
rotation = forms.IntegerField(widget=forms.HiddenInput(), initial=0, required=False)
|
|
163
|
+
image_data = forms.CharField(widget=forms.HiddenInput(), required=False)
|
|
129
164
|
|
|
130
165
|
class Meta:
|
|
131
166
|
model = Recipe
|
|
@@ -138,7 +173,7 @@ class RecipeForm(forms.ModelForm):
|
|
|
138
173
|
"instructions",
|
|
139
174
|
"price",
|
|
140
175
|
"is_highlighted",
|
|
141
|
-
"
|
|
176
|
+
"is_approved",
|
|
142
177
|
"max_daily_orders",
|
|
143
178
|
]
|
|
144
179
|
widgets = {
|
|
@@ -153,11 +188,22 @@ class RecipeForm(forms.ModelForm):
|
|
|
153
188
|
)
|
|
154
189
|
|
|
155
190
|
def save(self, commit=True):
|
|
156
|
-
recipe = super().save(commit=
|
|
191
|
+
recipe = super().save(commit=False)
|
|
192
|
+
|
|
193
|
+
# Handle base64 image data from cropper
|
|
194
|
+
image_data = self.cleaned_data.get("image_data")
|
|
195
|
+
if image_data and image_data.startswith("data:image"):
|
|
196
|
+
import base64
|
|
197
|
+
from django.core.files.base import ContentFile
|
|
157
198
|
|
|
158
|
-
|
|
199
|
+
format, imgstr = image_data.split(";base64,")
|
|
200
|
+
ext = format.split("/")[-1]
|
|
201
|
+
data = ContentFile(base64.b64decode(imgstr), name=f"recipe_image.{ext}")
|
|
202
|
+
recipe.image = data
|
|
203
|
+
|
|
204
|
+
# Handle rotation if an image exists and rotation is requested (fallback for simple rotation)
|
|
159
205
|
rotation = self.cleaned_data.get("rotation", 0)
|
|
160
|
-
if rotation != 0 and recipe.image:
|
|
206
|
+
if rotation != 0 and recipe.image and not image_data:
|
|
161
207
|
try:
|
|
162
208
|
from PIL import Image as PILImage
|
|
163
209
|
|
|
@@ -169,9 +215,9 @@ class RecipeForm(forms.ModelForm):
|
|
|
169
215
|
print(f"Error rotating image: {e}")
|
|
170
216
|
|
|
171
217
|
if commit:
|
|
218
|
+
recipe.save()
|
|
172
219
|
recipe.set_tags_from_string(self.cleaned_data.get("tags_string", ""))
|
|
173
220
|
else:
|
|
174
|
-
# We'll need to handle this in the view if commit=False
|
|
175
221
|
self.save_m2m = lambda: recipe.set_tags_from_string(
|
|
176
222
|
self.cleaned_data.get("tags_string", "")
|
|
177
223
|
)
|
|
@@ -184,6 +230,7 @@ class UserRecipeSubmissionForm(forms.ModelForm):
|
|
|
184
230
|
label=_("Tags (comma separated)"),
|
|
185
231
|
widget=forms.TextInput(attrs={"placeholder": _("e.g. spicy, vegan, quick")}),
|
|
186
232
|
)
|
|
233
|
+
image_data = forms.CharField(widget=forms.HiddenInput(), required=False)
|
|
187
234
|
|
|
188
235
|
class Meta:
|
|
189
236
|
model = Recipe
|
|
@@ -201,8 +248,21 @@ class UserRecipeSubmissionForm(forms.ModelForm):
|
|
|
201
248
|
}
|
|
202
249
|
|
|
203
250
|
def save(self, commit=True):
|
|
204
|
-
recipe = super().save(commit=
|
|
251
|
+
recipe = super().save(commit=False)
|
|
252
|
+
|
|
253
|
+
# Handle base64 image data from cropper
|
|
254
|
+
image_data = self.cleaned_data.get("image_data")
|
|
255
|
+
if image_data and image_data.startswith("data:image"):
|
|
256
|
+
import base64
|
|
257
|
+
from django.core.files.base import ContentFile
|
|
258
|
+
|
|
259
|
+
format, imgstr = image_data.split(";base64,")
|
|
260
|
+
ext = format.split("/")[-1]
|
|
261
|
+
data = ContentFile(base64.b64decode(imgstr), name=f"recipe_image.{ext}")
|
|
262
|
+
recipe.image = data
|
|
263
|
+
|
|
205
264
|
if commit:
|
|
265
|
+
recipe.save()
|
|
206
266
|
recipe.set_tags_from_string(self.cleaned_data.get("tags_string", ""))
|
|
207
267
|
else:
|
|
208
268
|
self.save_m2m = lambda: recipe.set_tags_from_string(
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Generated by Django 6.0.1 on 2026-01-25 10:53
|
|
2
|
+
|
|
3
|
+
from django.db import migrations
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
dependencies = [
|
|
8
|
+
("sandwitches", "0011_alter_historicalrecipe_is_community_made_and_more"),
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
operations = [
|
|
12
|
+
migrations.RenameField(
|
|
13
|
+
model_name="historicalrecipe",
|
|
14
|
+
old_name="is_community_made",
|
|
15
|
+
new_name="is_approved",
|
|
16
|
+
),
|
|
17
|
+
migrations.RenameField(
|
|
18
|
+
model_name="recipe",
|
|
19
|
+
old_name="is_community_made",
|
|
20
|
+
new_name="is_approved",
|
|
21
|
+
),
|
|
22
|
+
]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Generated by Django 6.0.1 on 2026-01-25 11:12
|
|
2
|
+
|
|
3
|
+
import django.db.models.deletion
|
|
4
|
+
from django.conf import settings
|
|
5
|
+
from django.db import migrations, models
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Migration(migrations.Migration):
|
|
9
|
+
dependencies = [
|
|
10
|
+
(
|
|
11
|
+
"sandwitches",
|
|
12
|
+
"0012_rename_is_community_made_historicalrecipe_is_approved_and_more",
|
|
13
|
+
),
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
operations = [
|
|
17
|
+
migrations.CreateModel(
|
|
18
|
+
name="CartItem",
|
|
19
|
+
fields=[
|
|
20
|
+
(
|
|
21
|
+
"id",
|
|
22
|
+
models.BigAutoField(
|
|
23
|
+
auto_created=True,
|
|
24
|
+
primary_key=True,
|
|
25
|
+
serialize=False,
|
|
26
|
+
verbose_name="ID",
|
|
27
|
+
),
|
|
28
|
+
),
|
|
29
|
+
("quantity", models.PositiveIntegerField(default=1)),
|
|
30
|
+
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
31
|
+
("updated_at", models.DateTimeField(auto_now=True)),
|
|
32
|
+
(
|
|
33
|
+
"recipe",
|
|
34
|
+
models.ForeignKey(
|
|
35
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
36
|
+
related_name="cart_items",
|
|
37
|
+
to="sandwitches.recipe",
|
|
38
|
+
),
|
|
39
|
+
),
|
|
40
|
+
(
|
|
41
|
+
"user",
|
|
42
|
+
models.ForeignKey(
|
|
43
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
44
|
+
related_name="cart_items",
|
|
45
|
+
to=settings.AUTH_USER_MODEL,
|
|
46
|
+
),
|
|
47
|
+
),
|
|
48
|
+
],
|
|
49
|
+
options={
|
|
50
|
+
"verbose_name": "Cart Item",
|
|
51
|
+
"verbose_name_plural": "Cart Items",
|
|
52
|
+
"unique_together": {("user", "recipe")},
|
|
53
|
+
},
|
|
54
|
+
),
|
|
55
|
+
]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from django.db import migrations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def create_groups(apps, schema_editor):
|
|
5
|
+
Group = apps.get_model("auth", "Group")
|
|
6
|
+
Group.objects.get_or_create(name="admin")
|
|
7
|
+
Group.objects.get_or_create(name="community")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def remove_groups(apps, schema_editor):
|
|
11
|
+
Group = apps.get_model("auth", "Group")
|
|
12
|
+
Group.objects.filter(name__in=["admin", "community"]).delete()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Migration(migrations.Migration):
|
|
16
|
+
dependencies = [
|
|
17
|
+
("sandwitches", "0013_cartitem"),
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
operations = [
|
|
21
|
+
migrations.RunPython(create_groups, remove_groups),
|
|
22
|
+
]
|
sandwitches-2.4.1/src/sandwitches/migrations/0015_order_completed_alter_order_status_and_more.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Generated by Django 6.0.1 on 2026-01-27 08:33
|
|
2
|
+
|
|
3
|
+
import django.core.validators
|
|
4
|
+
from django.db import migrations, models
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Migration(migrations.Migration):
|
|
8
|
+
dependencies = [
|
|
9
|
+
("sandwitches", "0014_ensure_groups_exist"),
|
|
10
|
+
]
|
|
11
|
+
|
|
12
|
+
operations = [
|
|
13
|
+
migrations.AddField(
|
|
14
|
+
model_name="order",
|
|
15
|
+
name="completed",
|
|
16
|
+
field=models.BooleanField(default=False),
|
|
17
|
+
),
|
|
18
|
+
migrations.AlterField(
|
|
19
|
+
model_name="order",
|
|
20
|
+
name="status",
|
|
21
|
+
field=models.CharField(
|
|
22
|
+
choices=[
|
|
23
|
+
("PENDING", "Pending"),
|
|
24
|
+
("PREPARING", "Preparing"),
|
|
25
|
+
("MADE", "Made"),
|
|
26
|
+
("SHIPPED", "Shipped"),
|
|
27
|
+
("COMPLETED", "Completed"),
|
|
28
|
+
("CANCELLED", "Cancelled"),
|
|
29
|
+
],
|
|
30
|
+
default="PENDING",
|
|
31
|
+
max_length=20,
|
|
32
|
+
),
|
|
33
|
+
),
|
|
34
|
+
migrations.AlterField(
|
|
35
|
+
model_name="setting",
|
|
36
|
+
name="email",
|
|
37
|
+
field=models.EmailField(
|
|
38
|
+
blank=True,
|
|
39
|
+
max_length=254,
|
|
40
|
+
null=True,
|
|
41
|
+
validators=[django.core.validators.EmailValidator()],
|
|
42
|
+
),
|
|
43
|
+
),
|
|
44
|
+
migrations.AlterField(
|
|
45
|
+
model_name="user",
|
|
46
|
+
name="email",
|
|
47
|
+
field=models.EmailField(
|
|
48
|
+
max_length=254, validators=[django.core.validators.EmailValidator()]
|
|
49
|
+
),
|
|
50
|
+
),
|
|
51
|
+
migrations.AlterField(
|
|
52
|
+
model_name="user",
|
|
53
|
+
name="username",
|
|
54
|
+
field=models.CharField(max_length=150, unique=True),
|
|
55
|
+
),
|
|
56
|
+
]
|
|
@@ -11,6 +11,7 @@ import logging
|
|
|
11
11
|
from django.urls import reverse
|
|
12
12
|
from solo.models import SingletonModel
|
|
13
13
|
from django.core.exceptions import ValidationError
|
|
14
|
+
from django.core.validators import EmailValidator
|
|
14
15
|
|
|
15
16
|
from imagekit.models import ImageSpecField
|
|
16
17
|
from imagekit.processors import ResizeToFill
|
|
@@ -21,7 +22,7 @@ hashed_storage = HashedFilenameStorage()
|
|
|
21
22
|
class Setting(SingletonModel):
|
|
22
23
|
site_name = models.CharField(max_length=255, default="Sandwitches")
|
|
23
24
|
site_description = models.TextField(blank=True, null=True)
|
|
24
|
-
email = models.EmailField(blank=True, null=True)
|
|
25
|
+
email = models.EmailField(blank=True, null=True, validators=[EmailValidator()])
|
|
25
26
|
ai_connection_point = models.URLField(blank=True, null=True)
|
|
26
27
|
ai_model = models.CharField(max_length=255, blank=True, null=True)
|
|
27
28
|
ai_api_key = models.CharField(max_length=255, blank=True, null=True)
|
|
@@ -34,6 +35,8 @@ class Setting(SingletonModel):
|
|
|
34
35
|
|
|
35
36
|
|
|
36
37
|
class User(AbstractUser):
|
|
38
|
+
username = models.CharField(max_length=150, unique=True)
|
|
39
|
+
email = models.EmailField(validators=[EmailValidator()])
|
|
37
40
|
avatar = models.ImageField(upload_to="avatars", blank=True, null=True)
|
|
38
41
|
avatar_thumbnail = ImageSpecField(
|
|
39
42
|
source="avatar",
|
|
@@ -132,7 +135,7 @@ class Recipe(models.Model):
|
|
|
132
135
|
)
|
|
133
136
|
tags = models.ManyToManyField(Tag, blank=True, related_name="recipes")
|
|
134
137
|
is_highlighted = models.BooleanField(default=False)
|
|
135
|
-
|
|
138
|
+
is_approved = models.BooleanField(default=False)
|
|
136
139
|
max_daily_orders = models.PositiveIntegerField(
|
|
137
140
|
null=True, blank=True, verbose_name="Max daily orders"
|
|
138
141
|
)
|
|
@@ -230,6 +233,9 @@ class Rating(models.Model):
|
|
|
230
233
|
class Order(models.Model):
|
|
231
234
|
STATUS_CHOICES = (
|
|
232
235
|
("PENDING", "Pending"),
|
|
236
|
+
("PREPARING", "Preparing"),
|
|
237
|
+
("MADE", "Made"),
|
|
238
|
+
("SHIPPED", "Shipped"),
|
|
233
239
|
("COMPLETED", "Completed"),
|
|
234
240
|
("CANCELLED", "Cancelled"),
|
|
235
241
|
)
|
|
@@ -239,6 +245,7 @@ class Order(models.Model):
|
|
|
239
245
|
)
|
|
240
246
|
recipe = models.ForeignKey(Recipe, related_name="orders", on_delete=models.CASCADE)
|
|
241
247
|
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="PENDING")
|
|
248
|
+
completed = models.BooleanField(default=False)
|
|
242
249
|
total_price = models.DecimalField(max_digits=6, decimal_places=2)
|
|
243
250
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
244
251
|
updated_at = models.DateTimeField(auto_now=True)
|
|
@@ -278,3 +285,29 @@ class Order(models.Model):
|
|
|
278
285
|
|
|
279
286
|
def __str__(self):
|
|
280
287
|
return f"Order #{self.pk} - {self.user} - {self.recipe}"
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
class CartItem(models.Model):
|
|
291
|
+
user = models.ForeignKey(
|
|
292
|
+
settings.AUTH_USER_MODEL, related_name="cart_items", on_delete=models.CASCADE
|
|
293
|
+
)
|
|
294
|
+
recipe = models.ForeignKey(
|
|
295
|
+
Recipe, related_name="cart_items", on_delete=models.CASCADE
|
|
296
|
+
)
|
|
297
|
+
quantity = models.PositiveIntegerField(default=1)
|
|
298
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
299
|
+
updated_at = models.DateTimeField(auto_now=True)
|
|
300
|
+
|
|
301
|
+
class Meta:
|
|
302
|
+
unique_together = ("user", "recipe")
|
|
303
|
+
verbose_name = "Cart Item"
|
|
304
|
+
verbose_name_plural = "Cart Items"
|
|
305
|
+
|
|
306
|
+
def __str__(self):
|
|
307
|
+
return f"{self.user.username}'s cart: {self.recipe.title} (x{self.quantity})"
|
|
308
|
+
|
|
309
|
+
@property
|
|
310
|
+
def total_price(self):
|
|
311
|
+
if self.recipe.price:
|
|
312
|
+
return self.recipe.price * self.quantity
|
|
313
|
+
return 0
|
|
@@ -56,6 +56,10 @@
|
|
|
56
56
|
<i>restaurant</i>
|
|
57
57
|
<span>{% trans "Recipes" %}</span>
|
|
58
58
|
</a>
|
|
59
|
+
<a href="{% url 'admin_recipe_approval_list' %}" class="{% if request.resolver_match.url_name == 'admin_recipe_approval_list' %}active{% endif %}">
|
|
60
|
+
<i>how_to_reg</i>
|
|
61
|
+
<span>{% trans "Approvals" %}</span>
|
|
62
|
+
</a>
|
|
59
63
|
<a href="{% url 'admin_user_list' %}" class="{% if request.resolver_match.url_name == 'admin_user_list' %}active{% endif %}">
|
|
60
64
|
<i>people</i>
|
|
61
65
|
<span>{% trans "Users" %}</span>
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
{{ block.super }}
|
|
8
8
|
<link href="{% static "dist/main.css" %}" rel="stylesheet">
|
|
9
9
|
<script src="{% static "dist/main.js" %}" defer></script>
|
|
10
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script><!-- Chart.js TODO -->
|
|
10
11
|
{% endblock %}
|
|
11
12
|
|
|
12
13
|
{% block content %}
|
{sandwitches-2.3.3 → sandwitches-2.4.1}/src/sandwitches/templates/admin/partials/order_rows.html
RENAMED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
<td>{{ order.recipe.title }}</td>
|
|
16
16
|
<td>{{ order.total_price }} €</td>
|
|
17
17
|
<td>
|
|
18
|
-
<span class="chip {% if order.status == 'PENDING' %}surface-variant{% elif order.status == 'COMPLETED' %}primary{%
|
|
18
|
+
<span class="chip {% if order.status == 'PENDING' %}surface-variant{% elif order.status == 'COMPLETED' %}primary{% elif order.status == 'CANCELLED' %}error{% else %}secondary{% endif %}">
|
|
19
19
|
{{ order.get_status_display }}
|
|
20
20
|
</span>
|
|
21
21
|
</td>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{% extends "admin/admin_base.html" %}
|
|
2
|
+
{% load i18n %}
|
|
3
|
+
|
|
4
|
+
{% block admin_title %}{% trans "Pending Approvals" %}{% endblock %}
|
|
5
|
+
|
|
6
|
+
{% block content %}
|
|
7
|
+
<div class="row align-center mb-2">
|
|
8
|
+
<h5 class="max bold">{% trans "Recipes Awaiting Approval" %}</h5>
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
<table class="border striped no-space">
|
|
12
|
+
<thead>
|
|
13
|
+
<tr>
|
|
14
|
+
<th class="min">{% trans "Image" %}</th>
|
|
15
|
+
<th class="max">{% trans "Title" %}</th>
|
|
16
|
+
<th>{% trans "Uploader" %}</th>
|
|
17
|
+
<th>{% trans "Created" %}</th>
|
|
18
|
+
<th class="right-align">{% trans "Actions" %}</th>
|
|
19
|
+
</tr>
|
|
20
|
+
</thead>
|
|
21
|
+
<tbody>
|
|
22
|
+
{% for recipe in recipes %}
|
|
23
|
+
<tr class="pointer" onclick="location.href='{% url 'admin_recipe_edit' recipe.pk %}'">
|
|
24
|
+
<td class="min">
|
|
25
|
+
{% if recipe.image %}
|
|
26
|
+
<img src="{{ recipe.image_thumbnail.url }}" class="admin-thumb round">
|
|
27
|
+
{% else %}
|
|
28
|
+
<div class="admin-thumb round gray1 middle-align center-align">
|
|
29
|
+
<i class="extra">restaurant</i>
|
|
30
|
+
</div>
|
|
31
|
+
{% endif %}
|
|
32
|
+
</td>
|
|
33
|
+
<td class="max">
|
|
34
|
+
<b>{{ recipe.title }}</b>
|
|
35
|
+
</td>
|
|
36
|
+
<td>{{ recipe.uploaded_by.username|default:"-" }}</td>
|
|
37
|
+
<td>{{ recipe.created_at|date:"SHORT_DATETIME_FORMAT" }}</td>
|
|
38
|
+
<td class="right-align">
|
|
39
|
+
<a href="{% url 'admin_recipe_approve' recipe.pk %}" class="button tiny primary round" onclick="event.stopPropagation();">
|
|
40
|
+
<i>check</i>
|
|
41
|
+
<span>{% trans "Approve" %}</span>
|
|
42
|
+
</a>
|
|
43
|
+
<a href="{% url 'admin_recipe_edit' recipe.pk %}" class="button circle transparent" onclick="event.stopPropagation();" title="{% trans 'Edit' %}"><i>edit</i></a>
|
|
44
|
+
<a href="{% url 'admin_recipe_delete' recipe.pk %}" class="button circle transparent" onclick="event.stopPropagation();" title="{% trans 'Delete' %}"><i>delete</i></a>
|
|
45
|
+
</td>
|
|
46
|
+
</tr>
|
|
47
|
+
{% empty %}
|
|
48
|
+
<tr>
|
|
49
|
+
<td colspan="5" class="center-align padding">
|
|
50
|
+
{% trans "No recipes pending approval." %}
|
|
51
|
+
</td>
|
|
52
|
+
</tr>
|
|
53
|
+
{% endfor %}
|
|
54
|
+
</tbody>
|
|
55
|
+
</table>
|
|
56
|
+
{% endblock %}
|