sandwitches 2.1.2__tar.gz → 2.3.0__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.1.2 → sandwitches-2.3.0}/PKG-INFO +1 -1
- {sandwitches-2.1.2 → sandwitches-2.3.0}/pyproject.toml +1 -1
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/admin.py +24 -2
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/forms.py +13 -0
- sandwitches-2.3.0/src/sandwitches/migrations/0006_historicalrecipe_is_highlighted_and_more.py +22 -0
- sandwitches-2.3.0/src/sandwitches/migrations/0007_historicalrecipe_price_recipe_price_order.py +86 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/models.py +36 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/settings.py +3 -0
- sandwitches-2.3.0/src/sandwitches/templates/components/carousel_scripts.html +59 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/components/navbar.html +10 -5
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/components/recipe_header.html +8 -0
- sandwitches-2.3.0/src/sandwitches/templates/index.html +76 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/partials/recipe_list.html +7 -0
- sandwitches-2.3.0/src/sandwitches/templates/profile.html +95 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/urls.py +1 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/views.py +19 -0
- sandwitches-2.1.2/src/sandwitches/templates/index.html +0 -42
- {sandwitches-2.1.2 → sandwitches-2.3.0}/README.md +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/__init__.py +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/api.py +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/asgi.py +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/feeds.py +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/locale/nl/LC_MESSAGES/django.mo +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/locale/nl/LC_MESSAGES/django.po +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/migrations/0001_initial.py +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/migrations/0002_historicalrecipe_servings_recipe_servings.py +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/migrations/0003_setting.py +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/migrations/0004_alter_setting_ai_api_key_and_more.py +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/migrations/0005_rating_comment.py +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/migrations/__init__.py +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/storage.py +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/tasks.py +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/admin/admin_base.html +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/admin/confirm_delete.html +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/admin/dashboard.html +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/admin/rating_list.html +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/admin/recipe_form.html +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/admin/recipe_list.html +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/admin/tag_form.html +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/admin/tag_list.html +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/admin/task_detail.html +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/admin/task_list.html +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/admin/user_form.html +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/admin/user_list.html +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/base.html +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/base_beer.html +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/components/favorites_search_form.html +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/components/footer.html +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/components/ingredients_scripts.html +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/components/ingredients_section.html +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/components/instructions_section.html +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/components/language_dialog.html +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/components/rating_section.html +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/components/search_form.html +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/components/search_scripts.html +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/components/side_menu.html +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/components/user_menu.html +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/detail.html +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/favorites.html +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/login.html +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/recipe_form.html +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/setup.html +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/signup.html +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templatetags/__init__.py +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templatetags/custom_filters.py +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templatetags/markdown_extras.py +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/utils.py +0 -0
- {sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/wsgi.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from django.contrib import admin
|
|
2
2
|
from django.contrib.auth import get_user_model
|
|
3
3
|
from django.contrib.auth.admin import UserAdmin
|
|
4
|
-
from .models import Recipe, Tag, Rating, Setting
|
|
4
|
+
from .models import Recipe, Tag, Rating, Setting, Order
|
|
5
5
|
from django.utils.html import format_html
|
|
6
6
|
from import_export import resources
|
|
7
7
|
from import_export.admin import ImportExportModelAdmin
|
|
@@ -24,6 +24,11 @@ class RatingResource(resources.ModelResource):
|
|
|
24
24
|
model = Rating
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
class OrderResource(resources.ModelResource):
|
|
28
|
+
class Meta:
|
|
29
|
+
model = Order
|
|
30
|
+
|
|
31
|
+
|
|
27
32
|
User = get_user_model()
|
|
28
33
|
|
|
29
34
|
|
|
@@ -42,7 +47,15 @@ class CustomUserAdmin(UserAdmin):
|
|
|
42
47
|
@admin.register(Recipe)
|
|
43
48
|
class RecipeAdmin(ImportExportModelAdmin):
|
|
44
49
|
resource_classes = [RecipeResource]
|
|
45
|
-
list_display = (
|
|
50
|
+
list_display = (
|
|
51
|
+
"title",
|
|
52
|
+
"uploaded_by",
|
|
53
|
+
"price",
|
|
54
|
+
"created_at",
|
|
55
|
+
"is_highlighted",
|
|
56
|
+
"show_url",
|
|
57
|
+
)
|
|
58
|
+
list_editable = ("is_highlighted",)
|
|
46
59
|
readonly_fields = ("created_at", "updated_at")
|
|
47
60
|
|
|
48
61
|
def save_model(self, request, obj, form, change):
|
|
@@ -66,3 +79,12 @@ class TagAdmin(ImportExportModelAdmin):
|
|
|
66
79
|
@admin.register(Rating)
|
|
67
80
|
class RatingAdmin(ImportExportModelAdmin):
|
|
68
81
|
resource_classes = [RatingResource]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@admin.register(Order)
|
|
85
|
+
class OrderAdmin(ImportExportModelAdmin):
|
|
86
|
+
resource_classes = [OrderResource]
|
|
87
|
+
list_display = ("id", "user", "recipe", "status", "total_price", "created_at")
|
|
88
|
+
list_filter = ("status", "created_at")
|
|
89
|
+
search_fields = ("user__username", "recipe__title")
|
|
90
|
+
readonly_fields = ("total_price", "created_at", "updated_at")
|
|
@@ -85,6 +85,18 @@ class UserSignupForm(UserCreationForm, BaseUserFormMixin):
|
|
|
85
85
|
return user
|
|
86
86
|
|
|
87
87
|
|
|
88
|
+
class UserProfileForm(forms.ModelForm):
|
|
89
|
+
class Meta:
|
|
90
|
+
model = User
|
|
91
|
+
fields = (
|
|
92
|
+
"first_name",
|
|
93
|
+
"last_name",
|
|
94
|
+
"email",
|
|
95
|
+
"avatar",
|
|
96
|
+
"bio",
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
88
100
|
class UserEditForm(forms.ModelForm):
|
|
89
101
|
class Meta:
|
|
90
102
|
model = User
|
|
@@ -124,6 +136,7 @@ class RecipeForm(forms.ModelForm):
|
|
|
124
136
|
"description",
|
|
125
137
|
"ingredients",
|
|
126
138
|
"instructions",
|
|
139
|
+
"price",
|
|
127
140
|
]
|
|
128
141
|
widgets = {
|
|
129
142
|
"image": forms.FileInput(),
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Generated by Django 6.0.1 on 2026-01-17 16:48
|
|
2
|
+
|
|
3
|
+
from django.db import migrations, models
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Migration(migrations.Migration):
|
|
7
|
+
dependencies = [
|
|
8
|
+
("sandwitches", "0005_rating_comment"),
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
operations = [
|
|
12
|
+
migrations.AddField(
|
|
13
|
+
model_name="historicalrecipe",
|
|
14
|
+
name="is_highlighted",
|
|
15
|
+
field=models.BooleanField(default=False),
|
|
16
|
+
),
|
|
17
|
+
migrations.AddField(
|
|
18
|
+
model_name="recipe",
|
|
19
|
+
name="is_highlighted",
|
|
20
|
+
field=models.BooleanField(default=False),
|
|
21
|
+
),
|
|
22
|
+
]
|
sandwitches-2.3.0/src/sandwitches/migrations/0007_historicalrecipe_price_recipe_price_order.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Generated by Django 6.0.1 on 2026-01-20 10:49
|
|
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
|
+
("sandwitches", "0006_historicalrecipe_is_highlighted_and_more"),
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
operations = [
|
|
14
|
+
migrations.AddField(
|
|
15
|
+
model_name="historicalrecipe",
|
|
16
|
+
name="price",
|
|
17
|
+
field=models.DecimalField(
|
|
18
|
+
blank=True,
|
|
19
|
+
decimal_places=2,
|
|
20
|
+
max_digits=6,
|
|
21
|
+
null=True,
|
|
22
|
+
verbose_name="Price (€)",
|
|
23
|
+
),
|
|
24
|
+
),
|
|
25
|
+
migrations.AddField(
|
|
26
|
+
model_name="recipe",
|
|
27
|
+
name="price",
|
|
28
|
+
field=models.DecimalField(
|
|
29
|
+
blank=True,
|
|
30
|
+
decimal_places=2,
|
|
31
|
+
max_digits=6,
|
|
32
|
+
null=True,
|
|
33
|
+
verbose_name="Price (€)",
|
|
34
|
+
),
|
|
35
|
+
),
|
|
36
|
+
migrations.CreateModel(
|
|
37
|
+
name="Order",
|
|
38
|
+
fields=[
|
|
39
|
+
(
|
|
40
|
+
"id",
|
|
41
|
+
models.BigAutoField(
|
|
42
|
+
auto_created=True,
|
|
43
|
+
primary_key=True,
|
|
44
|
+
serialize=False,
|
|
45
|
+
verbose_name="ID",
|
|
46
|
+
),
|
|
47
|
+
),
|
|
48
|
+
(
|
|
49
|
+
"status",
|
|
50
|
+
models.CharField(
|
|
51
|
+
choices=[
|
|
52
|
+
("PENDING", "Pending"),
|
|
53
|
+
("COMPLETED", "Completed"),
|
|
54
|
+
("CANCELLED", "Cancelled"),
|
|
55
|
+
],
|
|
56
|
+
default="PENDING",
|
|
57
|
+
max_length=20,
|
|
58
|
+
),
|
|
59
|
+
),
|
|
60
|
+
("total_price", models.DecimalField(decimal_places=2, max_digits=6)),
|
|
61
|
+
("created_at", models.DateTimeField(auto_now_add=True)),
|
|
62
|
+
("updated_at", models.DateTimeField(auto_now=True)),
|
|
63
|
+
(
|
|
64
|
+
"recipe",
|
|
65
|
+
models.ForeignKey(
|
|
66
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
67
|
+
related_name="orders",
|
|
68
|
+
to="sandwitches.recipe",
|
|
69
|
+
),
|
|
70
|
+
),
|
|
71
|
+
(
|
|
72
|
+
"user",
|
|
73
|
+
models.ForeignKey(
|
|
74
|
+
on_delete=django.db.models.deletion.CASCADE,
|
|
75
|
+
related_name="orders",
|
|
76
|
+
to=settings.AUTH_USER_MODEL,
|
|
77
|
+
),
|
|
78
|
+
),
|
|
79
|
+
],
|
|
80
|
+
options={
|
|
81
|
+
"verbose_name": "Order",
|
|
82
|
+
"verbose_name_plural": "Orders",
|
|
83
|
+
"ordering": ("-created_at",),
|
|
84
|
+
},
|
|
85
|
+
),
|
|
86
|
+
]
|
|
@@ -89,6 +89,9 @@ class Recipe(models.Model):
|
|
|
89
89
|
ingredients = models.TextField(blank=True)
|
|
90
90
|
instructions = models.TextField(blank=True)
|
|
91
91
|
servings = models.IntegerField(default=1, validators=[MinValueValidator(1)])
|
|
92
|
+
price = models.DecimalField(
|
|
93
|
+
max_digits=6, decimal_places=2, null=True, blank=True, verbose_name="Price (€)"
|
|
94
|
+
)
|
|
92
95
|
uploaded_by = models.ForeignKey(
|
|
93
96
|
settings.AUTH_USER_MODEL,
|
|
94
97
|
related_name="recipes",
|
|
@@ -127,6 +130,7 @@ class Recipe(models.Model):
|
|
|
127
130
|
options={"quality": 95},
|
|
128
131
|
)
|
|
129
132
|
tags = models.ManyToManyField(Tag, blank=True, related_name="recipes")
|
|
133
|
+
is_highlighted = models.BooleanField(default=False)
|
|
130
134
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
131
135
|
updated_at = models.DateTimeField(auto_now=True)
|
|
132
136
|
history = HistoricalRecords()
|
|
@@ -215,3 +219,35 @@ class Rating(models.Model):
|
|
|
215
219
|
|
|
216
220
|
def __str__(self):
|
|
217
221
|
return f"{self.recipe} — {self.score} by {self.user}"
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
class Order(models.Model):
|
|
225
|
+
STATUS_CHOICES = (
|
|
226
|
+
("PENDING", "Pending"),
|
|
227
|
+
("COMPLETED", "Completed"),
|
|
228
|
+
("CANCELLED", "Cancelled"),
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
user = models.ForeignKey(
|
|
232
|
+
settings.AUTH_USER_MODEL, related_name="orders", on_delete=models.CASCADE
|
|
233
|
+
)
|
|
234
|
+
recipe = models.ForeignKey(Recipe, related_name="orders", on_delete=models.CASCADE)
|
|
235
|
+
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="PENDING")
|
|
236
|
+
total_price = models.DecimalField(max_digits=6, decimal_places=2)
|
|
237
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
238
|
+
updated_at = models.DateTimeField(auto_now=True)
|
|
239
|
+
|
|
240
|
+
class Meta:
|
|
241
|
+
ordering = ("-created_at",)
|
|
242
|
+
verbose_name = "Order"
|
|
243
|
+
verbose_name_plural = "Orders"
|
|
244
|
+
|
|
245
|
+
def save(self, *args, **kwargs):
|
|
246
|
+
if not self.recipe.price: # ty:ignore[possibly-missing-attribute]
|
|
247
|
+
raise ValueError("Cannot order a recipe without a price.")
|
|
248
|
+
if not self.total_price:
|
|
249
|
+
self.total_price = self.recipe.price # ty:ignore[possibly-missing-attribute]
|
|
250
|
+
super().save(*args, **kwargs)
|
|
251
|
+
|
|
252
|
+
def __str__(self):
|
|
253
|
+
return f"Order #{self.pk} - {self.user} - {self.recipe}"
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
(function() {
|
|
3
|
+
const carousel = document.getElementById('highlighted-carousel');
|
|
4
|
+
const scrollAmount = 320; // Approximately card width + margin
|
|
5
|
+
let autoScrollInterval;
|
|
6
|
+
|
|
7
|
+
window.scrollCarousel = function(direction) {
|
|
8
|
+
if (carousel) {
|
|
9
|
+
const currentScroll = carousel.scrollLeft;
|
|
10
|
+
const maxScroll = carousel.scrollWidth - carousel.clientWidth;
|
|
11
|
+
|
|
12
|
+
let newScroll = currentScroll + (direction * scrollAmount);
|
|
13
|
+
|
|
14
|
+
// Loop functionality for manual navigation?
|
|
15
|
+
// Maybe just stop at ends or loop? Let's just scroll.
|
|
16
|
+
carousel.scrollBy({ left: direction * scrollAmount, behavior: 'smooth' });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function autoScroll() {
|
|
21
|
+
if (!carousel) return;
|
|
22
|
+
|
|
23
|
+
const currentScroll = carousel.scrollLeft;
|
|
24
|
+
const maxScroll = carousel.scrollWidth - carousel.clientWidth;
|
|
25
|
+
|
|
26
|
+
// Tolerance for float calculation
|
|
27
|
+
if (currentScroll >= maxScroll - 10) {
|
|
28
|
+
// Loop back to start
|
|
29
|
+
carousel.scrollTo({ left: 0, behavior: 'smooth' });
|
|
30
|
+
} else {
|
|
31
|
+
carousel.scrollBy({ left: scrollAmount, behavior: 'smooth' });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function startAutoScroll() {
|
|
36
|
+
stopAutoScroll(); // Clear any existing
|
|
37
|
+
autoScrollInterval = setInterval(autoScroll, 5000); // 5 seconds
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function stopAutoScroll() {
|
|
41
|
+
if (autoScrollInterval) {
|
|
42
|
+
clearInterval(autoScrollInterval);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (carousel) {
|
|
47
|
+
// Start auto-scroll
|
|
48
|
+
startAutoScroll();
|
|
49
|
+
|
|
50
|
+
// Pause on interaction
|
|
51
|
+
carousel.addEventListener('mouseenter', stopAutoScroll);
|
|
52
|
+
carousel.addEventListener('mouseleave', startAutoScroll);
|
|
53
|
+
|
|
54
|
+
// Touch events for mobile
|
|
55
|
+
carousel.addEventListener('touchstart', stopAutoScroll);
|
|
56
|
+
carousel.addEventListener('touchend', startAutoScroll);
|
|
57
|
+
}
|
|
58
|
+
})();
|
|
59
|
+
</script>
|
|
@@ -15,11 +15,16 @@
|
|
|
15
15
|
</button>
|
|
16
16
|
|
|
17
17
|
{% if user.is_authenticated %}
|
|
18
|
-
{%
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{% extends "base_beer.html" %}
|
|
2
|
+
{% load i18n static %}
|
|
3
|
+
|
|
4
|
+
{% block title %}Sandwitches{% 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 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>
|
|
27
|
+
</div>
|
|
28
|
+
</article>
|
|
29
|
+
|
|
30
|
+
<div class="space"></div>
|
|
31
|
+
|
|
32
|
+
{% include "components/search_form.html" %}
|
|
33
|
+
|
|
34
|
+
{% if highlighted_recipes %}
|
|
35
|
+
<div class="large-space"></div>
|
|
36
|
+
<h4 class="upper center-align">{% trans 'Highlights' %}</h4>
|
|
37
|
+
<div class="small-space"></div>
|
|
38
|
+
<div class="relative">
|
|
39
|
+
<div class="row scroll snap no-scrollbar" id="highlighted-carousel" style="gap: 20px; flex-wrap: nowrap;">
|
|
40
|
+
{% for recipe in highlighted_recipes %}
|
|
41
|
+
<a href="{{ recipe.get_absolute_url }}">
|
|
42
|
+
<article class="round no-padding elevate" style="min-width: 300px; width: 300px; flex-shrink: 0; scroll-snap-align: start;">
|
|
43
|
+
{% if recipe.image %}
|
|
44
|
+
<img src="{{ recipe.image_medium.url }}" class="responsive top-round" alt="{{ recipe.title }}" style="height: 200px; object-fit: cover;">
|
|
45
|
+
{% else %}
|
|
46
|
+
<div class="primary medium-height top-round middle-align center-align" style="height:200px;">
|
|
47
|
+
<i class="extra">lunch_dining</i>
|
|
48
|
+
</div>
|
|
49
|
+
{% endif %}
|
|
50
|
+
<div class="padding">
|
|
51
|
+
<h5>{{ recipe.title }}</h5>
|
|
52
|
+
</div>
|
|
53
|
+
</article>
|
|
54
|
+
</a>
|
|
55
|
+
{% endfor %}
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<button class="circle surface absolute left middle-align elevate" onclick="scrollCarousel(-1)" style="top: 50%; transform: translateY(-50%); left: -20px; z-index: 10;">
|
|
59
|
+
<i>chevron_left</i>
|
|
60
|
+
</button>
|
|
61
|
+
<button class="circle surface absolute right middle-align elevate" onclick="scrollCarousel(1)" style="top: 50%; transform: translateY(-50%); right: -20px; z-index: 10;">
|
|
62
|
+
<i>chevron_right</i>
|
|
63
|
+
</button>
|
|
64
|
+
</div>
|
|
65
|
+
{% endif %}
|
|
66
|
+
|
|
67
|
+
<div class="large-space"></div>
|
|
68
|
+
|
|
69
|
+
{% include "partials/recipe_list.html" %}
|
|
70
|
+
|
|
71
|
+
{% endblock %}
|
|
72
|
+
|
|
73
|
+
{% block page_scripts %}
|
|
74
|
+
{% include "components/search_scripts.html" %}
|
|
75
|
+
{% include "components/carousel_scripts.html" %}
|
|
76
|
+
{% endblock %}
|
|
@@ -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;">
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
{% extends "base.html" %}
|
|
2
|
+
{% load static i18n %}
|
|
3
|
+
{% block title %}{% trans "Your Profile" %}{% endblock %}
|
|
4
|
+
|
|
5
|
+
{% block content %}
|
|
6
|
+
<div class="large-space"></div>
|
|
7
|
+
|
|
8
|
+
<div class="grid">
|
|
9
|
+
<div class="s12 m10 l8 xl6 middle-align center-align" style="margin: 0 auto;">
|
|
10
|
+
<article class="round elevate">
|
|
11
|
+
<div class="padding">
|
|
12
|
+
<h4 class="center-align primary-text">{% trans "Edit your profile" %}</h4>
|
|
13
|
+
</div>
|
|
14
|
+
|
|
15
|
+
<form method="post" enctype="multipart/form-data" novalidate>
|
|
16
|
+
{% csrf_token %}
|
|
17
|
+
|
|
18
|
+
{% if form.non_field_errors %}
|
|
19
|
+
<div class="padding error surface round mb">
|
|
20
|
+
{% for err in form.non_field_errors %}
|
|
21
|
+
<div class="row align-center">
|
|
22
|
+
<i class="error-text">warning</i>
|
|
23
|
+
<span class="error-text">{{ err }}</span>
|
|
24
|
+
</div>
|
|
25
|
+
{% endfor %}
|
|
26
|
+
</div>
|
|
27
|
+
{% endif %}
|
|
28
|
+
|
|
29
|
+
<div class="grid">
|
|
30
|
+
<div class="s12 m6">
|
|
31
|
+
<div class="field label border round {% if form.first_name.errors %}error{% endif %}">
|
|
32
|
+
<input type="text" name="{{ form.first_name.name }}" id="{{ form.first_name.id_for_label }}" value="{{ form.first_name.value|default:'' }}">
|
|
33
|
+
<label>{% trans "First name" %}</label>
|
|
34
|
+
{% if form.first_name.errors %}
|
|
35
|
+
<span class="helper error-text">{{ form.first_name.errors.0 }}</span>
|
|
36
|
+
{% endif %}
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
<div class="s12 m6">
|
|
40
|
+
<div class="field label border round {% if form.last_name.errors %}error{% endif %}">
|
|
41
|
+
<input type="text" name="{{ form.last_name.name }}" id="{{ form.last_name.id_for_label }}" value="{{ form.last_name.value|default:'' }}">
|
|
42
|
+
<label>{% trans "Last name" %}</label>
|
|
43
|
+
{% if form.last_name.errors %}
|
|
44
|
+
<span class="helper error-text">{{ form.last_name.errors.0 }}</span>
|
|
45
|
+
{% endif %}
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<div class="field label border round {% if form.email.errors %}error{% endif %}">
|
|
51
|
+
<input type="email" name="{{ form.email.name }}" id="{{ form.email.id_for_label }}" value="{{ form.email.value|default:'' }}">
|
|
52
|
+
<label>{% trans "Email" %}</label>
|
|
53
|
+
{% if form.email.errors %}
|
|
54
|
+
<span class="helper error-text">{{ form.email.errors.0 }}</span>
|
|
55
|
+
{% endif %}
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<div class="field label border round textarea {% if form.bio.errors %}error{% endif %}">
|
|
59
|
+
<textarea name="{{ form.bio.name }}" id="{{ form.bio.id_for_label }}" rows="3">{{ form.bio.value|default:'' }}</textarea>
|
|
60
|
+
<label>{% trans "Bio" %}</label>
|
|
61
|
+
{% if form.bio.errors %}
|
|
62
|
+
<span class="helper error-text">{{ form.bio.errors.0 }}</span>
|
|
63
|
+
{% endif %}
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<div class="field middle-align {% if form.avatar.errors %}error{% endif %}">
|
|
67
|
+
<nav>
|
|
68
|
+
<div class="max">
|
|
69
|
+
<label class="button border transparent round">
|
|
70
|
+
<input type="file" name="{{ form.avatar.name }}" id="{{ form.avatar.id_for_label }}" accept="image/*">
|
|
71
|
+
<i>upload</i>
|
|
72
|
+
<span>{% trans "Upload Profile Picture" %}</span>
|
|
73
|
+
</label>
|
|
74
|
+
</div>
|
|
75
|
+
{% if user.avatar %}
|
|
76
|
+
<img src="{{ user.avatar.url }}" class="circle small">
|
|
77
|
+
{% endif %}
|
|
78
|
+
</nav>
|
|
79
|
+
{% if form.avatar.errors %}
|
|
80
|
+
<span class="helper error-text">{{ form.avatar.errors.0 }}</span>
|
|
81
|
+
{% endif %}
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<div class="large-space"></div>
|
|
85
|
+
|
|
86
|
+
<nav class="right-align">
|
|
87
|
+
<a class="button transparent border round" href="{% url 'index' %}">{% trans "Cancel" %}</a>
|
|
88
|
+
<button type="submit" class="button primary round">{% trans "Save changes" %}</button>
|
|
89
|
+
</nav>
|
|
90
|
+
|
|
91
|
+
</form>
|
|
92
|
+
</article>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
{% endblock %}
|
|
@@ -33,6 +33,7 @@ urlpatterns = [
|
|
|
33
33
|
path("signup/", views.signup, name="signup"),
|
|
34
34
|
path("login/", views.CustomLoginView.as_view(), name="login"),
|
|
35
35
|
path("logout/", LogoutView.as_view(next_page="index"), name="logout"),
|
|
36
|
+
path("profile/", views.user_profile, name="user_profile"),
|
|
36
37
|
path("admin/", admin.site.urls),
|
|
37
38
|
path("api/", api.urls),
|
|
38
39
|
path("media/<path:file_path>", views.media, name="media"),
|
|
@@ -14,6 +14,7 @@ from .forms import (
|
|
|
14
14
|
RatingForm,
|
|
15
15
|
UserEditForm,
|
|
16
16
|
TagForm,
|
|
17
|
+
UserProfileForm,
|
|
17
18
|
)
|
|
18
19
|
from django.http import HttpResponseBadRequest
|
|
19
20
|
from django.conf import settings
|
|
@@ -563,6 +564,8 @@ def index(request):
|
|
|
563
564
|
uploaders = User.objects.filter(recipes__isnull=False).distinct()
|
|
564
565
|
tags = Tag.objects.all() # ty:ignore[unresolved-attribute]
|
|
565
566
|
|
|
567
|
+
highlighted_recipes = Recipe.objects.filter(is_highlighted=True) # ty:ignore[unresolved-attribute]
|
|
568
|
+
|
|
566
569
|
return render(
|
|
567
570
|
request,
|
|
568
571
|
"index.html",
|
|
@@ -573,6 +576,7 @@ def index(request):
|
|
|
573
576
|
"tags": tags,
|
|
574
577
|
"selected_tags": request.GET.getlist("tag"),
|
|
575
578
|
"user": request.user, # Pass user to template
|
|
579
|
+
"highlighted_recipes": highlighted_recipes,
|
|
576
580
|
},
|
|
577
581
|
)
|
|
578
582
|
|
|
@@ -642,3 +646,18 @@ def media(request, file_path=None):
|
|
|
642
646
|
|
|
643
647
|
response = FileResponse(open(full_path, "rb"), as_attachment=True)
|
|
644
648
|
return response
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
@login_required
|
|
652
|
+
def user_profile(request):
|
|
653
|
+
if request.method == "POST":
|
|
654
|
+
form = UserProfileForm(request.POST, request.FILES, instance=request.user)
|
|
655
|
+
if form.is_valid():
|
|
656
|
+
form.save()
|
|
657
|
+
messages.success(request, _("Profile updated successfully."))
|
|
658
|
+
return redirect("user_profile")
|
|
659
|
+
else:
|
|
660
|
+
form = UserProfileForm(instance=request.user)
|
|
661
|
+
return render(
|
|
662
|
+
request, "profile.html", {"form": form, "version": sandwitches_version}
|
|
663
|
+
)
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
{% extends "base_beer.html" %}
|
|
2
|
-
{% load i18n static %}
|
|
3
|
-
|
|
4
|
-
{% block title %}Sandwitches{% 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 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>
|
|
27
|
-
</div>
|
|
28
|
-
</article>
|
|
29
|
-
|
|
30
|
-
<div class="space"></div>
|
|
31
|
-
|
|
32
|
-
{% include "components/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 %}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/components/language_dialog.html
RENAMED
|
File without changes
|
{sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/components/rating_section.html
RENAMED
|
File without changes
|
{sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/components/search_form.html
RENAMED
|
File without changes
|
{sandwitches-2.1.2 → sandwitches-2.3.0}/src/sandwitches/templates/components/search_scripts.html
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|