sandwitches 2.1.2__py3-none-any.whl → 2.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
sandwitches/admin.py CHANGED
@@ -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 = ("title", "uploaded_by", "created_at", "show_url")
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")
sandwitches/forms.py CHANGED
@@ -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
+ ]
@@ -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
+ ]
sandwitches/models.py CHANGED
@@ -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}"
sandwitches/settings.py CHANGED
@@ -130,6 +130,9 @@ LOGGING = {
130
130
  },
131
131
  }
132
132
 
133
+ LOGIN_REDIRECT_URL = "index"
134
+ LOGOUT_REDIRECT_URL = "index"
135
+
133
136
 
134
137
  # Password validation
135
138
  # https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators
@@ -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
- {% 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,11 @@
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
+ </div>
40
+ {% endif %}
@@ -31,6 +31,39 @@
31
31
 
32
32
  {% include "components/search_form.html" %}
33
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
+
34
67
  <div class="large-space"></div>
35
68
 
36
69
  {% include "partials/recipe_list.html" %}
@@ -39,4 +72,5 @@
39
72
 
40
73
  {% block page_scripts %}
41
74
  {% include "components/search_scripts.html" %}
75
+ {% include "components/carousel_scripts.html" %}
42
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 %}
sandwitches/urls.py CHANGED
@@ -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"),
sandwitches/views.py CHANGED
@@ -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,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sandwitches
3
- Version: 2.1.2
3
+ Version: 2.3.0
4
4
  Summary: Add your description here
5
5
  Author: Martyn van Dijke
6
6
  Author-email: Martyn van Dijke <martijnvdijke600@gmail.com>
@@ -1,9 +1,9 @@
1
1
  sandwitches/__init__.py,sha256=YTDsQDSdJmxV2Z0dTbBqZhuJRuXcNLSKL0SX73Lu2u8,195
2
- sandwitches/admin.py,sha256=3n73AD-j7vyajgoFdhPe3IXH6SFGIAq378gKDRr6ims,1852
2
+ sandwitches/admin.py,sha256=-02WqE8U3rxrVCoNB7sfvtyE4v_e3pt7mFwXfUlindo,2421
3
3
  sandwitches/api.py,sha256=4Upjd78dHVZDOIFkZjPst0mLfVdiToB3RVVrBpttTYg,5714
4
4
  sandwitches/asgi.py,sha256=cygnXdXSSVspM7ZXuj47Ef6oz7HSTw4D7BPzgE2PU5w,399
5
5
  sandwitches/feeds.py,sha256=iz1d11dV0utA0ZNsB7VIAp0h8Zr5mFNSKJWHbw_j6YM,683
6
- sandwitches/forms.py,sha256=QMSCpiy42AjretzEkj8FghfmBj-gkalSOXZVwiTtkOQ,5916
6
+ sandwitches/forms.py,sha256=oIL0Gofegq1LFfbUW66d6MvNR66NmLWK_dKUfHE6gz4,6158
7
7
  sandwitches/locale/nl/LC_MESSAGES/django.mo,sha256=EzQWzIhz_Na3w9AS7F-YjB-Xv63t4sMRSAkEQ1-g32M,5965
8
8
  sandwitches/locale/nl/LC_MESSAGES/django.po,sha256=znxspEoMwkmktusZtbVrt1KG1LDUwIEi4ZEIE3XGeoI,25904
9
9
  sandwitches/migrations/0001_initial.py,sha256=hXnCAhoA91C6YCinXyUdIfQ7QL29NPBHFfTqLgulMsY,12507
@@ -11,9 +11,11 @@ sandwitches/migrations/0002_historicalrecipe_servings_recipe_servings.py,sha256=
11
11
  sandwitches/migrations/0003_setting.py,sha256=DmugIrz0Wtftx7B0MrX6ms34j60kSOwcPz1sLRP4wE0,1200
12
12
  sandwitches/migrations/0004_alter_setting_ai_api_key_and_more.py,sha256=516vcSxWzsZiJaskH1JC-1FgB0t4ijNC2L3PcfdRPNg,1109
13
13
  sandwitches/migrations/0005_rating_comment.py,sha256=MRlTZkyATH1aJdkaesPlPkXkYt-mzqBik-MCcNrbLRI,398
14
+ sandwitches/migrations/0006_historicalrecipe_is_highlighted_and_more.py,sha256=BBCx4uxPK9udqsJ6qkoYpZ_3tH4uujquj9j0Gm7Ff6w,566
15
+ sandwitches/migrations/0007_historicalrecipe_price_recipe_price_order.py,sha256=o-hsVK39ArT_KkSkMnZfhCTQu66v81P6LbZPqRxjonU,2793
14
16
  sandwitches/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- sandwitches/models.py,sha256=MHx892trckPaIM-K5u8fFGj8HuLHi7SnPG5COP94Ung,7196
16
- sandwitches/settings.py,sha256=av4Cjplj0pwfThvaNfxPXh7D-0GCE4y_smtcH_mVYWE,5761
17
+ sandwitches/models.py,sha256=ZcZYgHr7ikYHqCq3X1qaZirK5Vbd7dakOyYYjBNwNQc,8575
18
+ sandwitches/settings.py,sha256=hNLDqbRPGdtNVNX31zxcDWKxWa95AcswKbj4XW4Z2lE,5821
17
19
  sandwitches/storage.py,sha256=ibBG6tVtArqzgEKsRimZPwsqW7i9j4WiPLLHrOJchow,3578
18
20
  sandwitches/tasks.py,sha256=rRQpWZp-vDbMgVmnZAz8GaPzK7F3uzAr6lho7Zq-mdM,3656
19
21
  sandwitches/templates/admin/admin_base.html,sha256=yPFo1K1LrRuPKfOuOGLEgQm29vWqzvkSoDNlpmxYPaY,4261
@@ -30,34 +32,36 @@ sandwitches/templates/admin/user_form.html,sha256=7_6GShLROFeJJexL1XLFUXdW9_lYF8
30
32
  sandwitches/templates/admin/user_list.html,sha256=6O1YctULY-tqJnagybJof9ERA_NL1LX_a8cAu6_aWVQ,2193
31
33
  sandwitches/templates/base.html,sha256=07zDXWzRC-aptZW6oq3xcxvDA1Lh87F7kTpBeYFUwsg,3211
32
34
  sandwitches/templates/base_beer.html,sha256=4QgU4_gu_RRMtimmRAhATDJ3mj_WANxtilQJYNgAL60,2077
35
+ sandwitches/templates/components/carousel_scripts.html,sha256=9vEL5JJv8zUUjEtsnHW-BwwXUNWqQ6w_vf6UdxgEv_I,1934
33
36
  sandwitches/templates/components/favorites_search_form.html,sha256=tpD8SpS47TUDJBwxhMuvjhTN9pjWoRGFW50TBv48Ld4,5202
34
37
  sandwitches/templates/components/footer.html,sha256=Qk-myRtXS6-1b3fMowVGnSuFb_UkUgX6BYX9zgh_SV8,486
35
38
  sandwitches/templates/components/ingredients_scripts.html,sha256=2zKTC65GYF589uW1sCpDq0jOkS3BnsuOwUpJbbigrn4,1794
36
39
  sandwitches/templates/components/ingredients_section.html,sha256=XsaVXTs9MIwjfJeLjlzah3GWWj8oFU-_HJd9i9l1HAo,665
37
40
  sandwitches/templates/components/instructions_section.html,sha256=RFlA4uPiI6vf1e2QgiD5KzGoy7Vg7y7nFY7TFabCYLA,277
38
41
  sandwitches/templates/components/language_dialog.html,sha256=iz-6QhFe4f_dsVhGDhVx6KKKLgQz4grX8tbIqSQjDsg,1184
39
- sandwitches/templates/components/navbar.html,sha256=CtWqAmPke7nm-UqSvGGl6TPaNwT2Zqkw9UySOCA8hfo,1049
42
+ sandwitches/templates/components/navbar.html,sha256=X2qOPHhVrSl4TkTk4YY-60YjpwCG7IEe4L6a9ywMih4,1164
40
43
  sandwitches/templates/components/rating_section.html,sha256=8O5IsFfQwnElMQZLnDpJiuCvvQMLa3jCS67u_RhMi7o,2717
41
- sandwitches/templates/components/recipe_header.html,sha256=-LCp6KqkQO7a7yqR0jKUQ95-RczYU-_MFO058lE04_w,1473
44
+ sandwitches/templates/components/recipe_header.html,sha256=9r7nZHNCZcuBb-9h7rEQFm3ug7Yv_XvbGTDqWwwN964,1666
42
45
  sandwitches/templates/components/search_form.html,sha256=B8579Jo44gLrlmvkkc2-Vuv_QD93Ljt6F2J1WgTDV60,6617
43
46
  sandwitches/templates/components/search_scripts.html,sha256=HvsO5e50DoTZeoFiYeNP5S8S5h7Zfr9VULOWKKR1i_M,3423
44
47
  sandwitches/templates/components/side_menu.html,sha256=JyrhmyWLKKZj1yqriHqQc6Fv25TpmIgXNhoEdxrePVQ,1529
45
48
  sandwitches/templates/components/user_menu.html,sha256=c20cBpyLheGvHdQ5nn-c4fjNlhfnAt3FAsw1V46rTwQ,369
46
49
  sandwitches/templates/detail.html,sha256=g-O_RsW9Ix9ivWC0nZ4FwHY2NhgYZ3bEGLpqGY0JSxg,5642
47
50
  sandwitches/templates/favorites.html,sha256=0cPpW07N6Isrb8XpvA5Eh97L2-12QFZ43EzeJvbOlXo,917
48
- sandwitches/templates/index.html,sha256=JqIeAY2Yz5yZ9MWSMjWXOQIohykcPlJHBRUrspahGCU,899
51
+ sandwitches/templates/index.html,sha256=7anU7k8s80JYk59Rwsm8EdlNYd7B5clCvV7pKq2IUy0,2518
49
52
  sandwitches/templates/login.html,sha256=LiQskhkOkfx0EE4ssA1ToqQ3oEll08OPYLDIkLjHfU8,2177
50
- sandwitches/templates/partials/recipe_list.html,sha256=AVzz_fIE4cBVU-E2E0VpE1tFmUV8jy7QpOninZEUMi8,3952
53
+ sandwitches/templates/partials/recipe_list.html,sha256=oF5zlgAyX_uG63BwulyhFywrn8QLnWXatXVtZPVcPmE,4186
54
+ sandwitches/templates/profile.html,sha256=TiYAf3ZPj18OYrmm-J_rjjqBrXbC9K2t1P4ZnKTajIY,4151
51
55
  sandwitches/templates/recipe_form.html,sha256=TzdlzeSqUoJTDTuCdFbeF5jCBZUTZEtlTIC_IQM3WL8,4754
52
56
  sandwitches/templates/setup.html,sha256=iNveFgePATsCSO4XMbGPa8TnWHyvj8S_5WwcW6i7pbo,4661
53
57
  sandwitches/templates/signup.html,sha256=pNBSlRGZI_B5ccF3dWpUgWBcjODkdLlq7HhyJLYIHCI,6176
54
58
  sandwitches/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
59
  sandwitches/templatetags/custom_filters.py,sha256=0KDFlFz4b5LwlcURBAmzyYWKKea-LwydZytJGVkkuKA,243
56
60
  sandwitches/templatetags/markdown_extras.py,sha256=0ibmRzxE3r85x4k7kK71R-9UT0CgeegYF7MHzj3juTI,344
57
- sandwitches/urls.py,sha256=fT4JYLMrTS1IDgb3Ruivj69lXcW86Th44Lt4Kr6Tchs,3922
61
+ sandwitches/urls.py,sha256=f_oPaPGMFfCc-rfmdmhUfFer3FjdHOixcRbueNhA9e8,3985
58
62
  sandwitches/utils.py,sha256=SJP-TkeRZ0OIfaMigYrOSbxRqYXswoqoWhwll3nFuAM,7245
59
- sandwitches/views.py,sha256=DA2q1ZrzdimTyVGt9Sbnvvex20gzMuu3N4SGoLQFNtU,20372
63
+ sandwitches/views.py,sha256=muqTfZ8Nm44dhyBX0QDVybNhWLvf1b7m47ri6aV-INE,21054
60
64
  sandwitches/wsgi.py,sha256=Eyncpnahq_4s3Lr9ruB-R3Lu9j9zBXqgPbUj7qhIbwU,399
61
- sandwitches-2.1.2.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
62
- sandwitches-2.1.2.dist-info/METADATA,sha256=TH8TEmijKUIiOWXUBs-WBv8hZestoOqw0NldlLLJ6kM,3111
63
- sandwitches-2.1.2.dist-info/RECORD,,
65
+ sandwitches-2.3.0.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
66
+ sandwitches-2.3.0.dist-info/METADATA,sha256=0e2iaCj3WmbGAntDQ8dGhjb6HfiPKoHXiTxS5YGA0gk,3111
67
+ sandwitches-2.3.0.dist-info/RECORD,,