sandwitches 2.3.3__py3-none-any.whl → 2.4.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/forms.py +1 -1
- sandwitches/migrations/0012_rename_is_community_made_historicalrecipe_is_approved_and_more.py +22 -0
- sandwitches/migrations/0013_cartitem.py +55 -0
- sandwitches/migrations/0014_ensure_groups_exist.py +22 -0
- sandwitches/models.py +27 -1
- sandwitches/templates/admin/admin_base.html +4 -0
- sandwitches/templates/admin/dashboard.html +1 -0
- sandwitches/templates/admin/recipe_approval_list.html +56 -0
- sandwitches/templates/admin/recipe_form.html +2 -2
- sandwitches/templates/admin/recipe_list.html +1 -1
- sandwitches/templates/cart.html +102 -0
- sandwitches/templates/components/navbar.html +6 -0
- sandwitches/templates/components/recipe_header.html +3 -3
- sandwitches/templates/partials/recipe_list.html +11 -3
- sandwitches/urls.py +12 -0
- sandwitches/views.py +158 -16
- {sandwitches-2.3.3.dist-info → sandwitches-2.4.0.dist-info}/METADATA +1 -1
- {sandwitches-2.3.3.dist-info → sandwitches-2.4.0.dist-info}/RECORD +19 -14
- {sandwitches-2.3.3.dist-info → sandwitches-2.4.0.dist-info}/WHEEL +0 -0
sandwitches/forms.py
CHANGED
|
@@ -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/models.py
CHANGED
|
@@ -132,7 +132,7 @@ class Recipe(models.Model):
|
|
|
132
132
|
)
|
|
133
133
|
tags = models.ManyToManyField(Tag, blank=True, related_name="recipes")
|
|
134
134
|
is_highlighted = models.BooleanField(default=False)
|
|
135
|
-
|
|
135
|
+
is_approved = models.BooleanField(default=False)
|
|
136
136
|
max_daily_orders = models.PositiveIntegerField(
|
|
137
137
|
null=True, blank=True, verbose_name="Max daily orders"
|
|
138
138
|
)
|
|
@@ -278,3 +278,29 @@ class Order(models.Model):
|
|
|
278
278
|
|
|
279
279
|
def __str__(self):
|
|
280
280
|
return f"Order #{self.pk} - {self.user} - {self.recipe}"
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
class CartItem(models.Model):
|
|
284
|
+
user = models.ForeignKey(
|
|
285
|
+
settings.AUTH_USER_MODEL, related_name="cart_items", on_delete=models.CASCADE
|
|
286
|
+
)
|
|
287
|
+
recipe = models.ForeignKey(
|
|
288
|
+
Recipe, related_name="cart_items", on_delete=models.CASCADE
|
|
289
|
+
)
|
|
290
|
+
quantity = models.PositiveIntegerField(default=1)
|
|
291
|
+
created_at = models.DateTimeField(auto_now_add=True)
|
|
292
|
+
updated_at = models.DateTimeField(auto_now=True)
|
|
293
|
+
|
|
294
|
+
class Meta:
|
|
295
|
+
unique_together = ("user", "recipe")
|
|
296
|
+
verbose_name = "Cart Item"
|
|
297
|
+
verbose_name_plural = "Cart Items"
|
|
298
|
+
|
|
299
|
+
def __str__(self):
|
|
300
|
+
return f"{self.user.username}'s cart: {self.recipe.title} (x{self.quantity})"
|
|
301
|
+
|
|
302
|
+
@property
|
|
303
|
+
def total_price(self):
|
|
304
|
+
if self.recipe.price:
|
|
305
|
+
return self.recipe.price * self.quantity
|
|
306
|
+
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 %}
|
|
@@ -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 %}
|
|
@@ -82,8 +82,8 @@
|
|
|
82
82
|
|
|
83
83
|
<div class="field middle-align mt-1">
|
|
84
84
|
<label class="checkbox">
|
|
85
|
-
{{ form.
|
|
86
|
-
<span>{{ form.
|
|
85
|
+
{{ form.is_approved }}
|
|
86
|
+
<span>{{ form.is_approved.label }}</span>
|
|
87
87
|
</label>
|
|
88
88
|
</div>
|
|
89
89
|
</article>
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
</div>
|
|
89
89
|
</td>
|
|
90
90
|
<td class="min center-align">
|
|
91
|
-
{% if recipe.
|
|
91
|
+
{% if recipe.is_approved %}
|
|
92
92
|
<i class="primary-text">check_circle</i>
|
|
93
93
|
{% else %}
|
|
94
94
|
<a href="{% url 'admin_recipe_approve' recipe.pk %}" class="button tiny primary round" onclick="event.stopPropagation();">
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
{% extends "base_beer.html" %}
|
|
2
|
+
{% load i18n static %}
|
|
3
|
+
|
|
4
|
+
{% block title %}{% trans "Your Cart" %}{% endblock %}
|
|
5
|
+
|
|
6
|
+
{% block content %}
|
|
7
|
+
<main class="responsive">
|
|
8
|
+
<div class="large-space"></div>
|
|
9
|
+
<div class="row align-center">
|
|
10
|
+
<i class="extra">shopping_cart</i>
|
|
11
|
+
<h4 class="max">{% trans "Your Shopping Cart" %}</h4>
|
|
12
|
+
</div>
|
|
13
|
+
<div class="space"></div>
|
|
14
|
+
|
|
15
|
+
{% if cart_items %}
|
|
16
|
+
<div class="grid">
|
|
17
|
+
<div class="s12 m8">
|
|
18
|
+
<div class="padding border round surface">
|
|
19
|
+
<table class="border striped no-space">
|
|
20
|
+
<thead>
|
|
21
|
+
<tr>
|
|
22
|
+
<th class="min">{% trans "Item" %}</th>
|
|
23
|
+
<th class="max">{% trans "Description" %}</th>
|
|
24
|
+
<th class="min">{% trans "Quantity" %}</th>
|
|
25
|
+
<th class="min">{% trans "Price" %}</th>
|
|
26
|
+
<th class="min"></th>
|
|
27
|
+
</tr>
|
|
28
|
+
</thead>
|
|
29
|
+
<tbody>
|
|
30
|
+
{% for item in cart_items %}
|
|
31
|
+
<tr>
|
|
32
|
+
<td class="min">
|
|
33
|
+
{% if item.recipe.image %}
|
|
34
|
+
<img src="{{ item.recipe.image_thumbnail.url }}" class="circle small">
|
|
35
|
+
{% else %}
|
|
36
|
+
<i class="circle small gray1">restaurant</i>
|
|
37
|
+
{% endif %}
|
|
38
|
+
</td>
|
|
39
|
+
<td class="max">
|
|
40
|
+
<a href="{% url 'recipe_detail' item.recipe.slug %}" class="bold">{{ item.recipe.title }}</a>
|
|
41
|
+
</td>
|
|
42
|
+
<td class="min">
|
|
43
|
+
<form action="{% url 'update_cart_quantity' item.pk %}" method="post" class="row no-space align-center">
|
|
44
|
+
{% csrf_token %}
|
|
45
|
+
<div class="field border round small no-margin" style="width: 80px;">
|
|
46
|
+
<input type="number" name="quantity" value="{{ item.quantity }}" min="1" onchange="this.form.submit()">
|
|
47
|
+
</div>
|
|
48
|
+
</form>
|
|
49
|
+
</td>
|
|
50
|
+
<td class="min no-wrap">{{ item.total_price }} €</td>
|
|
51
|
+
<td class="min">
|
|
52
|
+
<a href="{% url 'remove_from_cart' item.pk %}" class="button circle transparent">
|
|
53
|
+
<i>delete</i>
|
|
54
|
+
</a>
|
|
55
|
+
</td>
|
|
56
|
+
</tr>
|
|
57
|
+
{% endfor %}
|
|
58
|
+
</tbody>
|
|
59
|
+
</table>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
<div class="s12 m4">
|
|
63
|
+
<article class="round padding border primary-container">
|
|
64
|
+
<h6 class="bold">{% trans "Summary" %}</h6>
|
|
65
|
+
<div class="row mt-1">
|
|
66
|
+
<div class="max">{% trans "Subtotal" %}</div>
|
|
67
|
+
<div class="bold">{{ total }} €</div>
|
|
68
|
+
</div>
|
|
69
|
+
<div class="divider mt-1 mb-1"></div>
|
|
70
|
+
<div class="row">
|
|
71
|
+
<div class="max bold">{% trans "Total" %}</div>
|
|
72
|
+
<div class="bold large-text">{{ total }} €</div>
|
|
73
|
+
</div>
|
|
74
|
+
<div class="space"></div>
|
|
75
|
+
<form action="{% url 'checkout_cart' %}" method="post">
|
|
76
|
+
{% csrf_token %}
|
|
77
|
+
<button type="submit" class="button primary round extend">
|
|
78
|
+
<i>check</i>
|
|
79
|
+
<span>{% trans "Checkout" %}</span>
|
|
80
|
+
</button>
|
|
81
|
+
</form>
|
|
82
|
+
</article>
|
|
83
|
+
<div class="space"></div>
|
|
84
|
+
<a href="{% url 'index' %}" class="button transparent extend">
|
|
85
|
+
<i>arrow_back</i>
|
|
86
|
+
<span>{% trans "Continue Shopping" %}</span>
|
|
87
|
+
</a>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
{% else %}
|
|
91
|
+
<div class="center-align padding">
|
|
92
|
+
<i class="extra gray-text">shopping_cart_off</i>
|
|
93
|
+
<h5 class="gray-text">{% trans "Your cart is empty." %}</h5>
|
|
94
|
+
<div class="space"></div>
|
|
95
|
+
<a href="{% url 'index' %}" class="button primary round">
|
|
96
|
+
<span>{% trans "Start Shopping" %}</span>
|
|
97
|
+
</a>
|
|
98
|
+
</div>
|
|
99
|
+
{% endif %}
|
|
100
|
+
<div class="large-space"></div>
|
|
101
|
+
</main>
|
|
102
|
+
{% endblock %}
|
|
@@ -15,6 +15,12 @@
|
|
|
15
15
|
</button>
|
|
16
16
|
|
|
17
17
|
{% if user.is_authenticated %}
|
|
18
|
+
<a href="{% url 'view_cart' %}" class="button circle transparent">
|
|
19
|
+
<i>shopping_cart</i>
|
|
20
|
+
{% if user.cart_items.exists %}
|
|
21
|
+
<badge class="red">{{ user.cart_items.count }}</badge>
|
|
22
|
+
{% endif %}
|
|
23
|
+
</a>
|
|
18
24
|
<a href="{% url 'user_profile' %}">
|
|
19
25
|
{% if user.avatar %}
|
|
20
26
|
<img src="{{ user.avatar.url }}" class="circle">
|
|
@@ -37,11 +37,11 @@
|
|
|
37
37
|
<i class="primary-text">euro_symbol</i>
|
|
38
38
|
<h5 class="bold ml-1">{{ recipe.price }}</h5>
|
|
39
39
|
{% if user.is_authenticated %}
|
|
40
|
-
<form action="{% url '
|
|
40
|
+
<form action="{% url 'add_to_cart' recipe.pk %}" method="post" class="ml-2">
|
|
41
41
|
{% csrf_token %}
|
|
42
42
|
<button type="submit" class="button primary round">
|
|
43
|
-
<i>
|
|
44
|
-
<span>{% trans "
|
|
43
|
+
<i>add_shopping_cart</i>
|
|
44
|
+
<span>{% trans "Add to Cart" %}</span>
|
|
45
45
|
</button>
|
|
46
46
|
</form>
|
|
47
47
|
{% endif %}
|
|
@@ -72,11 +72,19 @@
|
|
|
72
72
|
</div>
|
|
73
73
|
</div>
|
|
74
74
|
|
|
75
|
-
<div class="padding pt-0">
|
|
76
|
-
<a href="{% url 'recipe_detail' recipe.slug %}" class="button fill round
|
|
77
|
-
<span>{% trans 'View
|
|
75
|
+
<div class="padding pt-0 row no-space">
|
|
76
|
+
<a href="{% url 'recipe_detail' recipe.slug %}" class="button fill round max">
|
|
77
|
+
<span>{% trans 'View' %}</span>
|
|
78
78
|
<i class="suffix">arrow_forward</i>
|
|
79
79
|
</a>
|
|
80
|
+
{% if user.is_authenticated and recipe.price %}
|
|
81
|
+
<form action="{% url 'add_to_cart' recipe.pk %}" method="post" class="ml-1">
|
|
82
|
+
{% csrf_token %}
|
|
83
|
+
<button type="submit" class="button circle primary" title="{% trans 'Add to Cart' %}">
|
|
84
|
+
<i>add_shopping_cart</i>
|
|
85
|
+
</button>
|
|
86
|
+
</form>
|
|
87
|
+
{% endif %}
|
|
80
88
|
</div>
|
|
81
89
|
</article>
|
|
82
90
|
</div>
|
sandwitches/urls.py
CHANGED
|
@@ -39,6 +39,13 @@ urlpatterns = [
|
|
|
39
39
|
path("api/", api.urls),
|
|
40
40
|
path("media/<path:file_path>", views.media, name="media"),
|
|
41
41
|
path("favorites/", views.favorites, name="favorites"),
|
|
42
|
+
path("cart/", views.view_cart, name="view_cart"),
|
|
43
|
+
path("cart/add/<int:pk>/", views.add_to_cart, name="add_to_cart"),
|
|
44
|
+
path("cart/remove/<int:pk>/", views.remove_from_cart, name="remove_from_cart"),
|
|
45
|
+
path(
|
|
46
|
+
"cart/update/<int:pk>/", views.update_cart_quantity, name="update_cart_quantity"
|
|
47
|
+
),
|
|
48
|
+
path("cart/checkout/", views.checkout_cart, name="checkout_cart"),
|
|
42
49
|
path("", views.index, name="index"),
|
|
43
50
|
path("feeds/latest/", LatestRecipesFeed(), name="latest_recipes_feed"),
|
|
44
51
|
path(
|
|
@@ -54,6 +61,11 @@ urlpatterns += i18n_patterns(
|
|
|
54
61
|
path("recipes/<int:pk>/favorite/", views.toggle_favorite, name="toggle_favorite"),
|
|
55
62
|
path("dashboard/", views.admin_dashboard, name="admin_dashboard"),
|
|
56
63
|
path("dashboard/recipes/", views.admin_recipe_list, name="admin_recipe_list"),
|
|
64
|
+
path(
|
|
65
|
+
"dashboard/approvals/",
|
|
66
|
+
views.admin_recipe_approval_list,
|
|
67
|
+
name="admin_recipe_approval_list",
|
|
68
|
+
),
|
|
57
69
|
path("dashboard/recipes/add/", views.admin_recipe_add, name="admin_recipe_add"),
|
|
58
70
|
path(
|
|
59
71
|
"dashboard/recipes/<int:pk>/edit/",
|
sandwitches/views.py
CHANGED
|
@@ -8,7 +8,7 @@ from django.contrib.auth import get_user_model
|
|
|
8
8
|
from django.contrib.auth.decorators import login_required
|
|
9
9
|
from django.contrib.admin.views.decorators import staff_member_required
|
|
10
10
|
from django.utils.translation import gettext as _
|
|
11
|
-
from .models import Recipe, Rating, Tag, Order
|
|
11
|
+
from .models import Recipe, Rating, Tag, Order, CartItem
|
|
12
12
|
from .forms import (
|
|
13
13
|
RecipeForm,
|
|
14
14
|
AdminSetupForm,
|
|
@@ -42,7 +42,7 @@ def community(request):
|
|
|
42
42
|
if form.is_valid():
|
|
43
43
|
recipe = form.save(commit=False)
|
|
44
44
|
recipe.uploaded_by = request.user
|
|
45
|
-
recipe.
|
|
45
|
+
recipe.is_approved = False
|
|
46
46
|
recipe.save()
|
|
47
47
|
form.save_m2m()
|
|
48
48
|
messages.success(
|
|
@@ -53,16 +53,17 @@ def community(request):
|
|
|
53
53
|
else:
|
|
54
54
|
form = UserRecipeSubmissionForm()
|
|
55
55
|
|
|
56
|
-
# Community recipes =
|
|
57
|
-
recipes = Recipe.objects.filter(
|
|
58
|
-
"
|
|
59
|
-
)
|
|
56
|
+
# Community recipes = uploaded by users in 'community' group
|
|
57
|
+
recipes = Recipe.objects.filter( # ty:ignore[unresolved-attribute]
|
|
58
|
+
uploaded_by__groups__name="community"
|
|
59
|
+
).prefetch_related("favorited_by")
|
|
60
60
|
|
|
61
|
-
if not request.user.is_staff:
|
|
61
|
+
if not (request.user.is_staff or request.user.groups.filter(name="admin").exists()):
|
|
62
62
|
# Regular users only see approved community recipes or their own
|
|
63
|
-
recipes = recipes.filter(
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
recipes = recipes.filter(Q(is_approved=True) | Q(uploaded_by=request.user))
|
|
64
|
+
else:
|
|
65
|
+
# Admins see all community recipes
|
|
66
|
+
pass
|
|
66
67
|
|
|
67
68
|
recipes = recipes.order_by("-created_at")
|
|
68
69
|
|
|
@@ -155,7 +156,7 @@ def admin_dashboard(request):
|
|
|
155
156
|
order_counts = [d["count"] for d in order_data]
|
|
156
157
|
|
|
157
158
|
pending_recipes = Recipe.objects.filter( # ty:ignore[unresolved-attribute]
|
|
158
|
-
|
|
159
|
+
is_approved=False, uploaded_by__groups__name="community"
|
|
159
160
|
).order_by("-created_at")
|
|
160
161
|
context = {
|
|
161
162
|
"recipe_count": recipe_count,
|
|
@@ -220,6 +221,21 @@ def admin_recipe_list(request):
|
|
|
220
221
|
)
|
|
221
222
|
|
|
222
223
|
|
|
224
|
+
@staff_member_required
|
|
225
|
+
def admin_recipe_approval_list(request):
|
|
226
|
+
recipes = Recipe.objects.filter( # ty:ignore[unresolved-attribute]
|
|
227
|
+
is_approved=False, uploaded_by__groups__name="community"
|
|
228
|
+
).order_by("-created_at")
|
|
229
|
+
return render(
|
|
230
|
+
request,
|
|
231
|
+
"admin/recipe_approval_list.html",
|
|
232
|
+
{
|
|
233
|
+
"recipes": recipes,
|
|
234
|
+
"version": sandwitches_version,
|
|
235
|
+
},
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
|
|
223
239
|
@staff_member_required
|
|
224
240
|
def admin_recipe_add(request):
|
|
225
241
|
if request.method == "POST":
|
|
@@ -266,11 +282,14 @@ def admin_recipe_edit(request, pk):
|
|
|
266
282
|
@staff_member_required
|
|
267
283
|
def admin_recipe_approve(request, pk):
|
|
268
284
|
recipe = get_object_or_404(Recipe, pk=pk)
|
|
269
|
-
recipe.
|
|
285
|
+
recipe.is_approved = True
|
|
270
286
|
recipe.save()
|
|
271
287
|
messages.success(
|
|
272
288
|
request, _("Recipe '%(title)s' approved.") % {"title": recipe.title}
|
|
273
289
|
)
|
|
290
|
+
referer = request.META.get("HTTP_REFERER")
|
|
291
|
+
if referer and "dashboard/approvals" in referer:
|
|
292
|
+
return redirect("admin_recipe_approval_list")
|
|
274
293
|
return redirect("admin_recipe_list")
|
|
275
294
|
|
|
276
295
|
|
|
@@ -494,10 +513,19 @@ def admin_order_list(request):
|
|
|
494
513
|
def recipe_detail(request, slug):
|
|
495
514
|
recipe = get_object_or_404(Recipe, slug=slug)
|
|
496
515
|
|
|
497
|
-
|
|
516
|
+
# If it's a community recipe, it must be approved or viewed by staff/owner
|
|
517
|
+
is_community = (
|
|
518
|
+
recipe.uploaded_by
|
|
519
|
+
and recipe.uploaded_by.groups.filter(name="community").exists()
|
|
520
|
+
)
|
|
521
|
+
if is_community and not recipe.is_approved:
|
|
498
522
|
if not (
|
|
499
523
|
request.user.is_authenticated
|
|
500
|
-
and (
|
|
524
|
+
and (
|
|
525
|
+
request.user.is_staff
|
|
526
|
+
or recipe.uploaded_by == request.user
|
|
527
|
+
or request.user.groups.filter(name="admin").exists()
|
|
528
|
+
)
|
|
501
529
|
):
|
|
502
530
|
raise Http404("Recipe not found or pending approval.")
|
|
503
531
|
|
|
@@ -668,8 +696,8 @@ def index(request):
|
|
|
668
696
|
|
|
669
697
|
recipes = Recipe.objects.all().prefetch_related("favorited_by") # ty:ignore[unresolved-attribute]
|
|
670
698
|
|
|
671
|
-
# Only show
|
|
672
|
-
recipes = recipes.filter(
|
|
699
|
+
# Only show recipes from people in the admin group
|
|
700
|
+
recipes = recipes.filter(uploaded_by__groups__name="admin")
|
|
673
701
|
|
|
674
702
|
# Filtering
|
|
675
703
|
q = request.GET.get("q")
|
|
@@ -743,6 +771,8 @@ def setup(request):
|
|
|
743
771
|
First-time setup page: create initial superuser if none exists.
|
|
744
772
|
Visible only while there are no superusers in the DB.
|
|
745
773
|
"""
|
|
774
|
+
from django.contrib.auth.models import Group
|
|
775
|
+
|
|
746
776
|
# do not allow access if a superuser already exists
|
|
747
777
|
if User.objects.filter(is_superuser=True).exists():
|
|
748
778
|
return redirect("index")
|
|
@@ -751,6 +781,12 @@ def setup(request):
|
|
|
751
781
|
form = AdminSetupForm(request.POST)
|
|
752
782
|
if form.is_valid():
|
|
753
783
|
user = form.save()
|
|
784
|
+
|
|
785
|
+
# Ensure groups exist and add user to admin group
|
|
786
|
+
admin_group, created = Group.objects.get_or_create(name="admin")
|
|
787
|
+
Group.objects.get_or_create(name="community")
|
|
788
|
+
user.groups.add(admin_group)
|
|
789
|
+
|
|
754
790
|
user.backend = "django.contrib.auth.backends.ModelBackend"
|
|
755
791
|
login(request, user)
|
|
756
792
|
messages.success(request, _("Admin account created and signed in."))
|
|
@@ -765,10 +801,17 @@ def signup(request):
|
|
|
765
801
|
"""
|
|
766
802
|
User signup page: create new regular user accounts.
|
|
767
803
|
"""
|
|
804
|
+
from django.contrib.auth.models import Group
|
|
805
|
+
|
|
768
806
|
if request.method == "POST":
|
|
769
807
|
form = UserSignupForm(request.POST, request.FILES)
|
|
770
808
|
if form.is_valid():
|
|
771
809
|
user = form.save()
|
|
810
|
+
|
|
811
|
+
# Add user to community group
|
|
812
|
+
community_group, created = Group.objects.get_or_create(name="community")
|
|
813
|
+
user.groups.add(community_group)
|
|
814
|
+
|
|
772
815
|
# log in the newly created user
|
|
773
816
|
user.backend = "django.contrib.auth.backends.ModelBackend"
|
|
774
817
|
login(request, user)
|
|
@@ -818,3 +861,102 @@ def user_profile(request):
|
|
|
818
861
|
return render(
|
|
819
862
|
request, "profile.html", {"form": form, "version": sandwitches_version}
|
|
820
863
|
)
|
|
864
|
+
|
|
865
|
+
|
|
866
|
+
@login_required
|
|
867
|
+
def view_cart(request):
|
|
868
|
+
cart_items = CartItem.objects.filter(user=request.user).select_related("recipe") # ty:ignore[unresolved-attribute]
|
|
869
|
+
total = sum(item.total_price for item in cart_items)
|
|
870
|
+
return render(
|
|
871
|
+
request,
|
|
872
|
+
"cart.html",
|
|
873
|
+
{
|
|
874
|
+
"cart_items": cart_items,
|
|
875
|
+
"total": total,
|
|
876
|
+
"version": sandwitches_version,
|
|
877
|
+
},
|
|
878
|
+
)
|
|
879
|
+
|
|
880
|
+
|
|
881
|
+
@login_required
|
|
882
|
+
def add_to_cart(request, pk):
|
|
883
|
+
recipe = get_object_or_404(Recipe, pk=pk)
|
|
884
|
+
if not recipe.price:
|
|
885
|
+
messages.error(request, _("This recipe cannot be ordered (no price set)."))
|
|
886
|
+
return redirect("recipe_detail", slug=recipe.slug)
|
|
887
|
+
|
|
888
|
+
cart_item, created = CartItem.objects.get_or_create( # ty:ignore[unresolved-attribute]
|
|
889
|
+
user=request.user, recipe=recipe
|
|
890
|
+
)
|
|
891
|
+
if not created:
|
|
892
|
+
cart_item.quantity += 1
|
|
893
|
+
cart_item.save()
|
|
894
|
+
|
|
895
|
+
messages.success(
|
|
896
|
+
request, _("Added %(title)s to your cart.") % {"title": recipe.title}
|
|
897
|
+
)
|
|
898
|
+
return redirect("view_cart")
|
|
899
|
+
|
|
900
|
+
|
|
901
|
+
@login_required
|
|
902
|
+
def remove_from_cart(request, pk):
|
|
903
|
+
cart_item = get_object_or_404(CartItem, pk=pk, user=request.user)
|
|
904
|
+
cart_item.delete()
|
|
905
|
+
messages.success(request, _("Removed from cart."))
|
|
906
|
+
return redirect("view_cart")
|
|
907
|
+
|
|
908
|
+
|
|
909
|
+
@login_required
|
|
910
|
+
def update_cart_quantity(request, pk):
|
|
911
|
+
if request.method == "POST":
|
|
912
|
+
cart_item = get_object_or_404(CartItem, pk=pk, user=request.user)
|
|
913
|
+
try:
|
|
914
|
+
quantity = int(request.POST.get("quantity", 1))
|
|
915
|
+
if quantity > 0:
|
|
916
|
+
cart_item.quantity = quantity
|
|
917
|
+
cart_item.save()
|
|
918
|
+
else:
|
|
919
|
+
cart_item.delete()
|
|
920
|
+
except ValueError:
|
|
921
|
+
pass
|
|
922
|
+
return redirect("view_cart")
|
|
923
|
+
|
|
924
|
+
|
|
925
|
+
@login_required
|
|
926
|
+
def checkout_cart(request):
|
|
927
|
+
cart_items = CartItem.objects.filter(user=request.user) # ty:ignore[unresolved-attribute]
|
|
928
|
+
if not cart_items.exists():
|
|
929
|
+
messages.error(request, _("Your cart is empty."))
|
|
930
|
+
return redirect("view_cart")
|
|
931
|
+
|
|
932
|
+
created_orders = [] # noqa: F841
|
|
933
|
+
errors = []
|
|
934
|
+
|
|
935
|
+
# We use a transaction to ensure either all orders are created or none if something goes wrong
|
|
936
|
+
from django.db import transaction
|
|
937
|
+
|
|
938
|
+
try:
|
|
939
|
+
with transaction.atomic():
|
|
940
|
+
for item in cart_items:
|
|
941
|
+
# Create Order for each recipe in cart (quantity times?)
|
|
942
|
+
# Current Order model doesn't have quantity, so we create multiple orders or update Order model.
|
|
943
|
+
# For now, let's create 'quantity' number of orders as per current schema
|
|
944
|
+
# OR we could update Order model to support quantity.
|
|
945
|
+
# Let's see if Order has quantity. (Checked: it does not).
|
|
946
|
+
for i in range(item.quantity):
|
|
947
|
+
try:
|
|
948
|
+
Order.objects.create(user=request.user, recipe=item.recipe) # ty:ignore[unresolved-attribute]
|
|
949
|
+
except (ValidationError, ValueError) as e:
|
|
950
|
+
errors.append(f"{item.recipe.title}: {str(e)}")
|
|
951
|
+
raise e # Trigger rollback
|
|
952
|
+
|
|
953
|
+
cart_items.delete()
|
|
954
|
+
messages.success(request, _("Orders submitted successfully!"))
|
|
955
|
+
return redirect("user_profile")
|
|
956
|
+
except Exception:
|
|
957
|
+
if errors:
|
|
958
|
+
for error in errors:
|
|
959
|
+
messages.error(request, error)
|
|
960
|
+
else:
|
|
961
|
+
messages.error(request, _("An error occurred during checkout."))
|
|
962
|
+
return redirect("view_cart")
|
|
@@ -3,7 +3,7 @@ sandwitches/admin.py,sha256=-02WqE8U3rxrVCoNB7sfvtyE4v_e3pt7mFwXfUlindo,2421
|
|
|
3
3
|
sandwitches/api.py,sha256=ruD5QeOPY-l9PvkJQiaOYoI0sRARDpqpFrFDgBxo9cQ,6389
|
|
4
4
|
sandwitches/asgi.py,sha256=cygnXdXSSVspM7ZXuj47Ef6oz7HSTw4D7BPzgE2PU5w,399
|
|
5
5
|
sandwitches/feeds.py,sha256=iz1d11dV0utA0ZNsB7VIAp0h8Zr5mFNSKJWHbw_j6YM,683
|
|
6
|
-
sandwitches/forms.py,sha256=
|
|
6
|
+
sandwitches/forms.py,sha256=YvkSTa9h_ag_b58ToOHCQIHBa3VeHMC9RKB9F7qI-gk,7152
|
|
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/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -20,20 +20,24 @@ sandwitches/migrations/0008_historicalrecipe_daily_orders_count_and_more.py,sha2
|
|
|
20
20
|
sandwitches/migrations/0009_historicalrecipe_is_approved_recipe_is_approved.py,sha256=XP76J2_HiMOFeIU17Yu8AYXtswE-dcsNZq3lJGFgGtg,588
|
|
21
21
|
sandwitches/migrations/0010_rename_is_approved_historicalrecipe_is_community_made_and_more.py,sha256=9Xv-rBRUvx5UWbr7i4BeWbllCcVkcNuC8T3sxKSdWyU,575
|
|
22
22
|
sandwitches/migrations/0011_alter_historicalrecipe_is_community_made_and_more.py,sha256=O2D57bAsSwBklYqfMTWrHE3Zxj3lrk-CO9yDP8sQS0M,659
|
|
23
|
+
sandwitches/migrations/0012_rename_is_community_made_historicalrecipe_is_approved_and_more.py,sha256=bCDPpHmZTIW70-YeL30WhuJ2mORktkrsntKqTw0vj94,577
|
|
24
|
+
sandwitches/migrations/0013_cartitem.py,sha256=KYMinpnZiLHwjo7p7EdJHQExuEGC9jtpcZcbm1r7JFo,1787
|
|
25
|
+
sandwitches/migrations/0014_ensure_groups_exist.py,sha256=5FSA742bEQtwHZl5CWZQYIdmS8FBxMgWS079dOaOltY,564
|
|
23
26
|
sandwitches/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
|
-
sandwitches/models.py,sha256=
|
|
27
|
+
sandwitches/models.py,sha256=ptEdQP4oOvyjdhYQlTtnZD-3eIUAY-8nU17G-hmN_2I,10796
|
|
25
28
|
sandwitches/settings.py,sha256=5_eQAJCAV093hnhr3XOxHekT4IF-PEJcRiTecq71_SQ,5841
|
|
26
29
|
sandwitches/storage.py,sha256=ibBG6tVtArqzgEKsRimZPwsqW7i9j4WiPLLHrOJchow,3578
|
|
27
30
|
sandwitches/tasks.py,sha256=YiliAT2rj0fh7hrwKq5_qWtv9AGhd5iulj_iBwZBBKg,6024
|
|
28
|
-
sandwitches/templates/admin/admin_base.html,sha256=
|
|
31
|
+
sandwitches/templates/admin/admin_base.html,sha256=aXba3MKFOKhaauFf0z0fFRjPpFHEbT_fREQx31TyxAM,4497
|
|
29
32
|
sandwitches/templates/admin/confirm_delete.html,sha256=HfsZI_gV8JQTKz215TYgPWBrgrFhGv1UB3N-0Hln-14,804
|
|
30
|
-
sandwitches/templates/admin/dashboard.html,sha256=
|
|
33
|
+
sandwitches/templates/admin/dashboard.html,sha256=Ial8zH2odIPpstSkQmzGrasl0QxvgGhFPAGy7V5xRzY,5916
|
|
31
34
|
sandwitches/templates/admin/order_list.html,sha256=eHFUn2speXaaj5_SFUG0Z0HfWVUR9-VCDRBeb8ufFb0,819
|
|
32
35
|
sandwitches/templates/admin/partials/dashboard_charts.html,sha256=NYrt-LDZO4__2KDWhAYL5K_f-2Zgj0iiuaZQiRZlBWg,3639
|
|
33
36
|
sandwitches/templates/admin/partials/order_rows.html,sha256=Ye35liahKbQ3rqa6fIGSTwb7seoXoqyqSw0wyNq2C_o,893
|
|
34
37
|
sandwitches/templates/admin/rating_list.html,sha256=8CHAsBfKfs4izhb-IyOiDjJXqAZxFcStoRSGh4pRlgM,1365
|
|
35
|
-
sandwitches/templates/admin/
|
|
36
|
-
sandwitches/templates/admin/
|
|
38
|
+
sandwitches/templates/admin/recipe_approval_list.html,sha256=M6GFYI45lAkLkvqP44cu5tDYVOeeVNklEphof1euesM,2281
|
|
39
|
+
sandwitches/templates/admin/recipe_form.html,sha256=23wHT4hs128xnv2nkS6AtcKzY3sblia_dGVNnaeIp5Y,8734
|
|
40
|
+
sandwitches/templates/admin/recipe_list.html,sha256=5fGnRIQ7JfvM3yfG-sngEIEgiPnPDkjK1Tn3nO8EDh4,5359
|
|
37
41
|
sandwitches/templates/admin/tag_form.html,sha256=JRWgAl4fz_Oy-Kuo1K6Mex_CXdsHMABzzyPazthr1Kg,989
|
|
38
42
|
sandwitches/templates/admin/tag_list.html,sha256=ttxwXgfdxkEs4Cmrz5RHaGmaqLd7JDmWhjv80XIQqyw,1246
|
|
39
43
|
sandwitches/templates/admin/task_detail.html,sha256=dO5zHOG-yTY1ly3VPA3o3jjie0wmxw0gdddtSKpN-W8,3825
|
|
@@ -42,6 +46,7 @@ sandwitches/templates/admin/user_form.html,sha256=7_6GShLROFeJJexL1XLFUXdW9_lYF8
|
|
|
42
46
|
sandwitches/templates/admin/user_list.html,sha256=6O1YctULY-tqJnagybJof9ERA_NL1LX_a8cAu6_aWVQ,2193
|
|
43
47
|
sandwitches/templates/base.html,sha256=mwCESNirfvvdyMg2e1Siy_LA8fLH29m0aS_Jv0Qom4U,3597
|
|
44
48
|
sandwitches/templates/base_beer.html,sha256=4QgU4_gu_RRMtimmRAhATDJ3mj_WANxtilQJYNgAL60,2077
|
|
49
|
+
sandwitches/templates/cart.html,sha256=YqmrzOLLPAXSqeXeUTrt9AwTTWOitOLTaD_k3mYYVpM,4537
|
|
45
50
|
sandwitches/templates/community.html,sha256=6x-Z8E0W3Ii-d0aG7DdCJoWQM9bVKNP_NSP8fTqpo6o,5324
|
|
46
51
|
sandwitches/templates/components/carousel_scripts.html,sha256=9vEL5JJv8zUUjEtsnHW-BwwXUNWqQ6w_vf6UdxgEv_I,1934
|
|
47
52
|
sandwitches/templates/components/favorites_search_form.html,sha256=tpD8SpS47TUDJBwxhMuvjhTN9pjWoRGFW50TBv48Ld4,5202
|
|
@@ -50,9 +55,9 @@ sandwitches/templates/components/ingredients_scripts.html,sha256=2zKTC65GYF589uW
|
|
|
50
55
|
sandwitches/templates/components/ingredients_section.html,sha256=XsaVXTs9MIwjfJeLjlzah3GWWj8oFU-_HJd9i9l1HAo,665
|
|
51
56
|
sandwitches/templates/components/instructions_section.html,sha256=RFlA4uPiI6vf1e2QgiD5KzGoy7Vg7y7nFY7TFabCYLA,277
|
|
52
57
|
sandwitches/templates/components/language_dialog.html,sha256=iz-6QhFe4f_dsVhGDhVx6KKKLgQz4grX8tbIqSQjDsg,1184
|
|
53
|
-
sandwitches/templates/components/navbar.html,sha256=
|
|
58
|
+
sandwitches/templates/components/navbar.html,sha256=t-ZWvd9Z3UQRR2RswcsRXRNbygiesOD0Bh1jhmm2vEY,1396
|
|
54
59
|
sandwitches/templates/components/rating_section.html,sha256=8O5IsFfQwnElMQZLnDpJiuCvvQMLa3jCS67u_RhMi7o,2717
|
|
55
|
-
sandwitches/templates/components/recipe_header.html,sha256=
|
|
60
|
+
sandwitches/templates/components/recipe_header.html,sha256=U6CxuR275QD9TIqo3VQftqvV6tqmH1rJdDcQotnXxYM,2001
|
|
56
61
|
sandwitches/templates/components/search_form.html,sha256=B8579Jo44gLrlmvkkc2-Vuv_QD93Ljt6F2J1WgTDV60,6617
|
|
57
62
|
sandwitches/templates/components/search_scripts.html,sha256=HvsO5e50DoTZeoFiYeNP5S8S5h7Zfr9VULOWKKR1i_M,3423
|
|
58
63
|
sandwitches/templates/components/side_menu.html,sha256=qXYyk8W-GnG-u_mJ65ADa4HWfUa2ubxnQAKwxwacF9M,1787
|
|
@@ -61,17 +66,17 @@ sandwitches/templates/detail.html,sha256=g-O_RsW9Ix9ivWC0nZ4FwHY2NhgYZ3bEGLpqGY0
|
|
|
61
66
|
sandwitches/templates/favorites.html,sha256=0cPpW07N6Isrb8XpvA5Eh97L2-12QFZ43EzeJvbOlXo,917
|
|
62
67
|
sandwitches/templates/index.html,sha256=7anU7k8s80JYk59Rwsm8EdlNYd7B5clCvV7pKq2IUy0,2518
|
|
63
68
|
sandwitches/templates/login.html,sha256=LiQskhkOkfx0EE4ssA1ToqQ3oEll08OPYLDIkLjHfU8,2177
|
|
64
|
-
sandwitches/templates/partials/recipe_list.html,sha256=
|
|
69
|
+
sandwitches/templates/partials/recipe_list.html,sha256=LUHKFKG90D72K9X2X3d1osvj2jX1QU_MbPe0lNwRSII,4555
|
|
65
70
|
sandwitches/templates/profile.html,sha256=PQTL6_xn0pGUxqEOYuz5j0pmqAyG0Wr3KvyFBO8_k1s,4156
|
|
66
71
|
sandwitches/templates/setup.html,sha256=iNveFgePATsCSO4XMbGPa8TnWHyvj8S_5WwcW6i7pbo,4661
|
|
67
72
|
sandwitches/templates/signup.html,sha256=pNBSlRGZI_B5ccF3dWpUgWBcjODkdLlq7HhyJLYIHCI,6176
|
|
68
73
|
sandwitches/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
69
74
|
sandwitches/templatetags/custom_filters.py,sha256=0KDFlFz4b5LwlcURBAmzyYWKKea-LwydZytJGVkkuKA,243
|
|
70
75
|
sandwitches/templatetags/markdown_extras.py,sha256=0ibmRzxE3r85x4k7kK71R-9UT0CgeegYF7MHzj3juTI,344
|
|
71
|
-
sandwitches/urls.py,sha256=
|
|
76
|
+
sandwitches/urls.py,sha256=1GyqdrWsCIbKN8wsT4eeE98blx-bBHc6tJgvXzta2nc,4859
|
|
72
77
|
sandwitches/utils.py,sha256=SJP-TkeRZ0OIfaMigYrOSbxRqYXswoqoWhwll3nFuAM,7245
|
|
73
|
-
sandwitches/views.py,sha256=
|
|
78
|
+
sandwitches/views.py,sha256=WF17_nRo6wDdC8oVjGBcPHGRtoi_Ji7x8W9HdcjAyQA,30890
|
|
74
79
|
sandwitches/wsgi.py,sha256=Eyncpnahq_4s3Lr9ruB-R3Lu9j9zBXqgPbUj7qhIbwU,399
|
|
75
|
-
sandwitches-2.
|
|
76
|
-
sandwitches-2.
|
|
77
|
-
sandwitches-2.
|
|
80
|
+
sandwitches-2.4.0.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
|
|
81
|
+
sandwitches-2.4.0.dist-info/METADATA,sha256=E9TYl5ZmaC1n9_NM-va3fhaloK8HkrYiP_rei8B_DhE,3111
|
|
82
|
+
sandwitches-2.4.0.dist-info/RECORD,,
|
|
File without changes
|