sandwitches 1.0.3__py3-none-any.whl → 1.2.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 +22 -2
- sandwitches/api.py +82 -0
- sandwitches/forms.py +58 -29
- sandwitches/locale/nl/LC_MESSAGES/django.mo +0 -0
- sandwitches/locale/nl/LC_MESSAGES/django.po +139 -0
- sandwitches/migrations/0003_rating.py +57 -0
- sandwitches/migrations/0004_add_uploaded_by.py +25 -0
- sandwitches/migrations/0005_historicalrecipe_uploaded_by.py +27 -0
- sandwitches/migrations/0006_profile.py +48 -0
- sandwitches/models.py +113 -16
- sandwitches/settings.py +91 -18
- sandwitches/storage.py +57 -4
- sandwitches/tasks.py +99 -0
- sandwitches/templates/base.html +2 -1
- sandwitches/templates/base_pico.html +20 -4
- sandwitches/templates/detail.html +89 -10
- sandwitches/templates/form.html +5 -4
- sandwitches/templates/index.html +10 -6
- sandwitches/templates/setup.html +9 -7
- sandwitches/templates/signup.html +50 -0
- sandwitches/urls.py +15 -10
- sandwitches/views.py +97 -7
- {sandwitches-1.0.3.dist-info → sandwitches-1.2.0.dist-info}/METADATA +7 -4
- sandwitches-1.2.0.dist-info/RECORD +33 -0
- sandwitches-1.0.3.dist-info/RECORD +0 -24
- {sandwitches-1.0.3.dist-info → sandwitches-1.2.0.dist-info}/WHEEL +0 -0
|
@@ -1,31 +1,50 @@
|
|
|
1
1
|
{% extends "base_pico.html" %}
|
|
2
2
|
{% block title %}{{ recipe.title }} — Sandwitch{% endblock %}
|
|
3
|
-
|
|
4
3
|
{% block content %}
|
|
5
4
|
|
|
6
|
-
{% load markdown_extras %}
|
|
5
|
+
{% load i18n markdown_extras %}
|
|
7
6
|
<nav aria-label="breadcrumb" class="container">
|
|
8
|
-
<a href="{% url 'index' %}">← Back to all</a>
|
|
7
|
+
<a href="{% url 'index' %}">← {% trans "Back to all" %}</a>
|
|
9
8
|
</nav>
|
|
10
9
|
|
|
11
10
|
<div class="grid">
|
|
12
11
|
<article class="card">
|
|
13
12
|
<figure>
|
|
14
13
|
{% if recipe.image %}
|
|
15
|
-
<img src="{{ recipe.
|
|
14
|
+
<img src="{{ recipe.image_large.url }}"
|
|
15
|
+
srcset="{{ recipe.image_medium.url }} 700w, {{ recipe.image_large.url }} 1200w"
|
|
16
|
+
sizes="(max-width: 768px) 95vw, 900px"
|
|
17
|
+
alt="{{ recipe.title }}"
|
|
18
|
+
loading="lazy">
|
|
16
19
|
{% endif %}
|
|
17
20
|
</figure>
|
|
21
|
+
{% trans "Description" %}
|
|
18
22
|
<header>
|
|
19
23
|
{{ recipe.title }}
|
|
24
|
+
{% if recipe.uploaded_by %}
|
|
25
|
+
<small>{% trans "Uploaded by" %}: {{ recipe.uploaded_by.get_full_name|default:recipe.uploaded_by.username }}</small>
|
|
26
|
+
{% endif %}
|
|
20
27
|
</header>
|
|
21
|
-
<h4>Description</h4>
|
|
22
|
-
{
|
|
28
|
+
<h4>{% trans "Description" %}</h4>
|
|
29
|
+
{% if recipe.description %}
|
|
30
|
+
{{ recipe.description|convert_markdown|safe }}
|
|
31
|
+
{% else %}
|
|
32
|
+
<p>{% trans "No description yet." %}</p>
|
|
33
|
+
{% endif %}
|
|
23
34
|
|
|
24
|
-
<h4>Ingredients</h4>
|
|
25
|
-
{
|
|
35
|
+
<h4>{% trans "Ingredients" %}</h4>
|
|
36
|
+
{% if recipe.ingredients %}
|
|
37
|
+
{{ recipe.ingredients|convert_markdown|safe }}
|
|
38
|
+
{% else %}
|
|
39
|
+
<p>{% trans "No ingredients listed." %}</p>
|
|
40
|
+
{% endif %}
|
|
26
41
|
|
|
27
|
-
<h4>Instructions</h4>
|
|
28
|
-
{
|
|
42
|
+
<h4>{% trans "Instructions" %}</h4>
|
|
43
|
+
{% if recipe.instructions %}
|
|
44
|
+
{{ recipe.instructions|convert_markdown|safe }}
|
|
45
|
+
{% else %}
|
|
46
|
+
<p>{% trans "No instructions yet." %}</p>
|
|
47
|
+
{% endif %}
|
|
29
48
|
|
|
30
49
|
<div class="tags-row">
|
|
31
50
|
<div style="margin-top:12px;">
|
|
@@ -34,6 +53,66 @@
|
|
|
34
53
|
{% endfor %}
|
|
35
54
|
</div>
|
|
36
55
|
</div>
|
|
56
|
+
|
|
57
|
+
<div style="margin-top:1rem;">
|
|
58
|
+
<h4>{% trans "Rating" %}</h4>
|
|
59
|
+
{% if rating_count %}
|
|
60
|
+
<p>{% trans "Average:" %} {{ avg_rating|floatformat:1 }} ({{ rating_count }} {% trans "vote" %}{% if rating_count %}s{% endif %})</p>
|
|
61
|
+
{% else %}
|
|
62
|
+
<p>{% trans "No ratings yet." %}</p>
|
|
63
|
+
{% endif %}
|
|
64
|
+
|
|
65
|
+
{% if user.is_authenticated %}
|
|
66
|
+
{% if user_rating %}
|
|
67
|
+
<p>{% trans "Your rating:" %} {{ user_rating.score }}</p>
|
|
68
|
+
{% else %}
|
|
69
|
+
<form method="post" action="{% url 'recipe_rate' pk=recipe.pk %}">
|
|
70
|
+
{% csrf_token %}
|
|
71
|
+
|
|
72
|
+
<fieldset>
|
|
73
|
+
<legend>{% trans "What would you rate this sandwich?" %}</legend>
|
|
74
|
+
|
|
75
|
+
{% if rating_form.score.errors %}
|
|
76
|
+
<div class="card-panel" role="alert">
|
|
77
|
+
<ul>
|
|
78
|
+
{% for err in rating_form.score.errors %}
|
|
79
|
+
<li>{{ err }}</li>
|
|
80
|
+
{% endfor %}
|
|
81
|
+
</ul>
|
|
82
|
+
</div>
|
|
83
|
+
{% endif %}
|
|
84
|
+
|
|
85
|
+
<div class="row">
|
|
86
|
+
{% for i in "12345"|make_list %}
|
|
87
|
+
<div class="col-sm-2">
|
|
88
|
+
<label class="form-check">
|
|
89
|
+
<input
|
|
90
|
+
type="radio"
|
|
91
|
+
name="{{ rating_form.score.name }}"
|
|
92
|
+
id="rating-{{ i }}"
|
|
93
|
+
value="{{ i }}"
|
|
94
|
+
{% if user_rating and user_rating.score|stringformat:"s" == i %}
|
|
95
|
+
checked
|
|
96
|
+
{% elif rating_form.score.value == i %}
|
|
97
|
+
checked
|
|
98
|
+
{% endif %}
|
|
99
|
+
/>
|
|
100
|
+
<span>{{ i }}</span>
|
|
101
|
+
</label>
|
|
102
|
+
</div>
|
|
103
|
+
{% endfor %}
|
|
104
|
+
</div>
|
|
105
|
+
</fieldset>
|
|
106
|
+
<p style="margin-top:0.5rem;">
|
|
107
|
+
<button type="submit">{% if user_rating %}{% trans "Update" %}{% else %}{% trans "Rate" %}{% endif %}</button>
|
|
108
|
+
</p>
|
|
109
|
+
</form>
|
|
110
|
+
{% endif %}
|
|
111
|
+
{% else %}
|
|
112
|
+
<p><a href="{% url 'admin:login' %}">{% trans "Log in" %}</a> {% trans "to rate this recipe." %}</p>
|
|
113
|
+
{% endif %}
|
|
114
|
+
</div>
|
|
115
|
+
|
|
37
116
|
{% if user.is_authenticated and user.is_staff %}
|
|
38
117
|
<footer>
|
|
39
118
|
<a href="/admin/sandwitches/recipe/{{ recipe.pk }}/change/">Edit</a>
|
sandwitches/templates/form.html
CHANGED
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
<html>
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
|
-
<title>Edit</title>
|
|
5
|
+
<title>{% trans "Edit" %}</title>
|
|
6
6
|
</head>
|
|
7
7
|
<body>
|
|
8
|
-
|
|
8
|
+
{% load i18n %}
|
|
9
|
+
<h1>{% trans "Edit Recipe:" %} {{ recipe.title }}</h1>
|
|
9
10
|
<form method="post" enctype="multipart/form-data">
|
|
10
11
|
{% csrf_token %} {{ form.as_p }}
|
|
11
|
-
<button type="submit">Save</button>
|
|
12
|
-
<a href="{% url 'recipes:admin_list' %}">Cancel</a>
|
|
12
|
+
<button type="submit">{% trans "Save" %}</button>
|
|
13
|
+
<a href="{% url 'recipes:admin_list' %}">{% trans "Cancel" %}</a>
|
|
13
14
|
</form>
|
|
14
15
|
</body>
|
|
15
16
|
</html>
|
sandwitches/templates/index.html
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
{% extends "base_pico.html" %}
|
|
1
|
+
{% extends "base_pico.html" %} {% load i18n static %}
|
|
2
2
|
|
|
3
3
|
{% block title %}Sandwitches{% endblock %}
|
|
4
4
|
|
|
5
5
|
{% block content %}
|
|
6
6
|
<div class="grid search-row">
|
|
7
7
|
<div>
|
|
8
|
-
<h4>Sandwitches: sandwiches so good, they haunt you
|
|
8
|
+
<h4>{% trans 'Sandwitches: sandwiches so good, they haunt you!' %}</h4>
|
|
9
9
|
</div>
|
|
10
10
|
<div>
|
|
11
11
|
<form role="search" onsubmit="return false;">
|
|
12
|
-
<input id="search" type="search" placeholder="Search by title or tag" aria-label="Search">
|
|
12
|
+
<input id="search" type="search" placeholder="{% trans "Search by title or tag" %}" aria-label="{% trans "Search" %}">
|
|
13
13
|
</form>
|
|
14
14
|
</div>
|
|
15
15
|
</div>
|
|
@@ -19,7 +19,11 @@
|
|
|
19
19
|
<article class="card" onclick="location.href='{% url 'recipe_detail' recipe.slug %}';" style="cursor:pointer;">
|
|
20
20
|
{% if recipe.image %}
|
|
21
21
|
<figure class="recipe-figure">
|
|
22
|
-
<img src="{{ recipe.
|
|
22
|
+
<img src="{{ recipe.image_medium.url }}"
|
|
23
|
+
srcset="{{ recipe.image_thumbnail.url }} 150w, {{ recipe.image_medium.url }} 700w"
|
|
24
|
+
sizes="(max-width: 640px) 90vw, 45vw"
|
|
25
|
+
alt="{{ recipe.title }}"
|
|
26
|
+
loading="lazy">
|
|
23
27
|
</figure>
|
|
24
28
|
{% endif %}
|
|
25
29
|
|
|
@@ -35,7 +39,7 @@
|
|
|
35
39
|
|
|
36
40
|
{% if user.is_authenticated and user.is_staff %}
|
|
37
41
|
<footer>
|
|
38
|
-
<a href="/admin/sandwitches/recipe/{{ recipe.pk }}/change/" rel="noopener">Edit</a>
|
|
42
|
+
<a href="/admin/sandwitches/recipe/{{ recipe.pk }}/change/" rel="noopener">{% trans "Edit" %}</a>
|
|
39
43
|
</footer>
|
|
40
44
|
{% endif %}
|
|
41
45
|
</article>
|
|
@@ -43,7 +47,7 @@
|
|
|
43
47
|
{% empty %}
|
|
44
48
|
<article class="card">
|
|
45
49
|
<div class="card-body">
|
|
46
|
-
<p>No sandwitches yet, please stay tuned
|
|
50
|
+
<p>{% trans "No sandwitches yet, please stay tuned." %}</p>
|
|
47
51
|
</div>
|
|
48
52
|
</article>
|
|
49
53
|
{% endfor %}
|
sandwitches/templates/setup.html
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
{% extends "base_pico.html" %}
|
|
2
|
-
{% load static %}
|
|
3
|
-
{% block title %}Initial setup — Create admin{% endblock %}
|
|
2
|
+
{% load static i18n %}
|
|
3
|
+
{% block title %}{% trans "Initial setup — Create admin" %}{% endblock %}
|
|
4
4
|
|
|
5
5
|
{% block content %}
|
|
6
6
|
<div class="container" style="max-width:720px; margin:2rem auto;">
|
|
7
7
|
<article class="card">
|
|
8
8
|
<div class="card-body">
|
|
9
|
-
<h2>Create initial administrator</h2>
|
|
9
|
+
<h2>{% trans "Create initial administrator" %}</h2>
|
|
10
10
|
<p>
|
|
11
|
-
This page is only available when there are no admin users in the database.
|
|
12
|
-
|
|
11
|
+
{% trans "This page is only available when there are no admin users in the database." %}
|
|
12
|
+
</p>
|
|
13
|
+
<p>
|
|
14
|
+
{% trans "After creating the account you will be logged in and redirected to the admin." %}
|
|
13
15
|
</p>
|
|
14
16
|
|
|
15
17
|
<form method="post" novalidate>
|
|
@@ -43,8 +45,8 @@
|
|
|
43
45
|
{{ form.password2 }}
|
|
44
46
|
|
|
45
47
|
<p style="margin-top:1rem;">
|
|
46
|
-
<button type="submit">Create admin</button>
|
|
47
|
-
<a class="contrast" href="{% url 'index' %}">Cancel</a>
|
|
48
|
+
<button type="submit">{% trans "Create admin" %}</button>
|
|
49
|
+
<a class="contrast" href="{% url 'index' %}">{% trans "Cancel" %}</a>
|
|
48
50
|
</p>
|
|
49
51
|
</form>
|
|
50
52
|
</div>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{% extends "base_pico.html" %}
|
|
2
|
+
{% load static i18n %}
|
|
3
|
+
{% block title %}{% trans "Sign Up" %}{% endblock %}
|
|
4
|
+
|
|
5
|
+
{% block content %}
|
|
6
|
+
<div class="container" style="max-width:720px; margin:2rem auto;">
|
|
7
|
+
<article class="card">
|
|
8
|
+
<div class="card-body">
|
|
9
|
+
<h2>{% trans "Sign up" %}</h2>
|
|
10
|
+
|
|
11
|
+
<form method="post" novalidate>
|
|
12
|
+
{% csrf_token %}
|
|
13
|
+
{% if form.non_field_errors %}
|
|
14
|
+
<div class="card-panel" role="alert">
|
|
15
|
+
<ul>
|
|
16
|
+
{% for err in form.non_field_errors %}
|
|
17
|
+
<li>{{ err }}</li>
|
|
18
|
+
{% endfor %}
|
|
19
|
+
</ul>
|
|
20
|
+
</div>
|
|
21
|
+
{% endif %}
|
|
22
|
+
|
|
23
|
+
<label for="{{ form.username.id_for_label }}">{% trans "Username" %}</label>
|
|
24
|
+
{{ form.username }}
|
|
25
|
+
|
|
26
|
+
<label for="{{ form.email.id_for_label }}">{% trans "Email (optional)" %}</label>
|
|
27
|
+
{{ form.email }}
|
|
28
|
+
|
|
29
|
+
<label for="{{ form.first_name.id_for_label }}">{% trans "First name" %}</label>
|
|
30
|
+
{{ form.first_name }}
|
|
31
|
+
|
|
32
|
+
<label for="{{ form.last_name.id_for_label }}">{% trans "Last name" %}</label>
|
|
33
|
+
{{ form.last_name }}
|
|
34
|
+
|
|
35
|
+
<label for="{{ form.password1.id_for_label }}">{% trans "Password" %}</label>
|
|
36
|
+
{{ form.password1 }}
|
|
37
|
+
|
|
38
|
+
<label for="{{ form.password2.id_for_label }}">{% trans "Confirm password" %}</label>
|
|
39
|
+
{{ form.password2 }}
|
|
40
|
+
|
|
41
|
+
<p style="margin-top:1rem;">
|
|
42
|
+
<button type="submit">{% trans "Sign Up" %}</button>
|
|
43
|
+
<a class="contrast" href="{% url 'index' %}">{% trans "Cancel" %}</a>
|
|
44
|
+
</p>
|
|
45
|
+
</form>
|
|
46
|
+
</div>
|
|
47
|
+
</article>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
{% endblock %}
|
sandwitches/urls.py
CHANGED
|
@@ -16,22 +16,31 @@ Including another URLconf
|
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
18
|
from django.contrib import admin
|
|
19
|
-
from django.urls import path
|
|
19
|
+
from django.urls import path, include
|
|
20
20
|
from . import views
|
|
21
|
+
from .api import api
|
|
22
|
+
from django.conf.urls.i18n import i18n_patterns
|
|
23
|
+
|
|
21
24
|
|
|
22
|
-
from django.conf import settings
|
|
23
|
-
from django.conf.urls.static import static
|
|
24
|
-
from debug_toolbar.toolbar import debug_toolbar_urls
|
|
25
25
|
import os
|
|
26
26
|
import sys
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
urlpatterns = [
|
|
30
|
-
path("",
|
|
30
|
+
path("i18n/", include("django.conf.urls.i18n")),
|
|
31
|
+
path("signup/", views.signup, name="signup"),
|
|
31
32
|
path("admin/", admin.site.urls),
|
|
33
|
+
path("api/", api.urls),
|
|
34
|
+
path("media/<path:file_path>", views.media, name="media"),
|
|
35
|
+
path("", views.index, name="index"),
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
urlpatterns += i18n_patterns(
|
|
32
39
|
path("recipes/<slug:slug>/", views.recipe_detail, name="recipe_detail"),
|
|
33
40
|
path("setup/", views.setup, name="setup"),
|
|
34
|
-
|
|
41
|
+
path("recipes/<int:pk>/rate/", views.recipe_rate, name="recipe_rate"),
|
|
42
|
+
prefix_default_language=True,
|
|
43
|
+
)
|
|
35
44
|
|
|
36
45
|
if "test" not in sys.argv or "PYTEST_VERSION" in os.environ:
|
|
37
46
|
from debug_toolbar.toolbar import debug_toolbar_urls
|
|
@@ -39,7 +48,3 @@ if "test" not in sys.argv or "PYTEST_VERSION" in os.environ:
|
|
|
39
48
|
urlpatterns = [
|
|
40
49
|
*urlpatterns,
|
|
41
50
|
] + debug_toolbar_urls()
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
if settings.DEBUG:
|
|
45
|
-
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
sandwitches/views.py
CHANGED
|
@@ -3,9 +3,15 @@ from django.urls import reverse
|
|
|
3
3
|
from django.contrib import messages
|
|
4
4
|
from django.contrib.auth import login
|
|
5
5
|
from django.contrib.auth import get_user_model
|
|
6
|
-
|
|
7
|
-
from .
|
|
8
|
-
from .
|
|
6
|
+
from django.contrib.auth.decorators import login_required
|
|
7
|
+
from django.utils.translation import gettext as _
|
|
8
|
+
from .models import Recipe, Rating
|
|
9
|
+
from .forms import RecipeForm, AdminSetupForm, UserSignupForm, RatingForm
|
|
10
|
+
from django.http import HttpResponseBadRequest
|
|
11
|
+
from django.conf import settings
|
|
12
|
+
from django.http import FileResponse, Http404
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
import mimetypes
|
|
9
15
|
|
|
10
16
|
User = get_user_model()
|
|
11
17
|
|
|
@@ -24,13 +30,56 @@ def recipe_edit(request, pk):
|
|
|
24
30
|
|
|
25
31
|
def recipe_detail(request, slug):
|
|
26
32
|
recipe = get_object_or_404(Recipe, slug=slug)
|
|
27
|
-
|
|
33
|
+
avg = recipe.average_rating()
|
|
34
|
+
count = recipe.rating_count()
|
|
35
|
+
user_rating = None
|
|
36
|
+
rating_form = None
|
|
37
|
+
if request.user.is_authenticated:
|
|
38
|
+
try:
|
|
39
|
+
user_rating = Rating.objects.get(recipe=recipe, user=request.user) # ty:ignore[unresolved-attribute]
|
|
40
|
+
except Rating.DoesNotExist: # ty:ignore[unresolved-attribute]
|
|
41
|
+
user_rating = None
|
|
42
|
+
# show form prefilled when possible
|
|
43
|
+
initial = {"score": str(user_rating.score)} if user_rating else None
|
|
44
|
+
rating_form = RatingForm(initial=initial)
|
|
45
|
+
return render(
|
|
46
|
+
request,
|
|
47
|
+
"detail.html",
|
|
48
|
+
{
|
|
49
|
+
"recipe": recipe,
|
|
50
|
+
"avg_rating": avg,
|
|
51
|
+
"rating_count": count,
|
|
52
|
+
"user_rating": user_rating,
|
|
53
|
+
"rating_form": rating_form,
|
|
54
|
+
},
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@login_required
|
|
59
|
+
def recipe_rate(request, pk):
|
|
60
|
+
"""
|
|
61
|
+
Create or update a rating for the given recipe by the logged-in user.
|
|
62
|
+
"""
|
|
63
|
+
recipe = get_object_or_404(Recipe, pk=pk)
|
|
64
|
+
if request.method != "POST":
|
|
65
|
+
return redirect("recipe_detail", slug=recipe.slug)
|
|
66
|
+
|
|
67
|
+
form = RatingForm(request.POST)
|
|
68
|
+
if form.is_valid():
|
|
69
|
+
score = int(form.cleaned_data["score"])
|
|
70
|
+
Rating.objects.update_or_create( # ty:ignore[unresolved-attribute]
|
|
71
|
+
recipe=recipe, user=request.user, defaults={"score": score}
|
|
72
|
+
)
|
|
73
|
+
messages.success(request, _("Your rating has been saved."))
|
|
74
|
+
else:
|
|
75
|
+
messages.error(request, _("Could not save rating."))
|
|
76
|
+
return redirect("recipe_detail", slug=recipe.slug)
|
|
28
77
|
|
|
29
78
|
|
|
30
79
|
def index(request):
|
|
31
80
|
if not User.objects.filter(is_superuser=True).exists():
|
|
32
81
|
return redirect("setup")
|
|
33
|
-
recipes = Recipe.objects.order_by("-created_at")
|
|
82
|
+
recipes = Recipe.objects.order_by("-created_at") # ty:ignore[unresolved-attribute]
|
|
34
83
|
return render(request, "index.html", {"recipes": recipes})
|
|
35
84
|
|
|
36
85
|
|
|
@@ -47,12 +96,53 @@ def setup(request):
|
|
|
47
96
|
form = AdminSetupForm(request.POST)
|
|
48
97
|
if form.is_valid():
|
|
49
98
|
user = form.save()
|
|
50
|
-
# log in the newly created admin
|
|
51
99
|
user.backend = "django.contrib.auth.backends.ModelBackend"
|
|
52
100
|
login(request, user)
|
|
53
|
-
messages.success(request, "Admin account created and signed in.")
|
|
101
|
+
messages.success(request, _("Admin account created and signed in."))
|
|
54
102
|
return redirect(reverse("admin:index"))
|
|
55
103
|
else:
|
|
56
104
|
form = AdminSetupForm()
|
|
57
105
|
|
|
58
106
|
return render(request, "setup.html", {"form": form})
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def signup(request):
|
|
110
|
+
"""
|
|
111
|
+
User signup page: create new regular user accounts.
|
|
112
|
+
"""
|
|
113
|
+
if request.method == "POST":
|
|
114
|
+
form = UserSignupForm(request.POST)
|
|
115
|
+
if form.is_valid():
|
|
116
|
+
user = form.save()
|
|
117
|
+
# log in the newly created user
|
|
118
|
+
user.backend = "django.contrib.auth.backends.ModelBackend"
|
|
119
|
+
login(request, user)
|
|
120
|
+
messages.success(request, _("Account created and signed in."))
|
|
121
|
+
return redirect("index")
|
|
122
|
+
else:
|
|
123
|
+
form = UserSignupForm()
|
|
124
|
+
|
|
125
|
+
return render(request, "signup.html", {"form": form})
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def media(request, file_path=None):
|
|
129
|
+
media_root = getattr(settings, "MEDIA_ROOT", None)
|
|
130
|
+
if not media_root:
|
|
131
|
+
return HttpResponseBadRequest("Invalid Media Root Configuration")
|
|
132
|
+
if not file_path:
|
|
133
|
+
return HttpResponseBadRequest("Invalid File Path")
|
|
134
|
+
|
|
135
|
+
base_path = Path(media_root).resolve()
|
|
136
|
+
full_path = base_path.joinpath(file_path).resolve()
|
|
137
|
+
if base_path not in full_path.parents:
|
|
138
|
+
return HttpResponseBadRequest("Access Denied")
|
|
139
|
+
|
|
140
|
+
if not full_path.exists() or not full_path.is_file():
|
|
141
|
+
raise Http404("File not found")
|
|
142
|
+
|
|
143
|
+
content_type, _ = mimetypes.guess_type(full_path)
|
|
144
|
+
if not content_type or not content_type.startswith("image/"):
|
|
145
|
+
return HttpResponseBadRequest("Access Denied: Only image files are allowed.")
|
|
146
|
+
|
|
147
|
+
response = FileResponse(open(full_path, "rb"), as_attachment=True)
|
|
148
|
+
return response
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: sandwitches
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Add your description here
|
|
5
5
|
Author: Martyn van Dijke
|
|
6
6
|
Author-email: Martyn van Dijke <martijnvdijke600@gmail.com>
|
|
7
7
|
Requires-Dist: django-debug-toolbar>=6.1.0
|
|
8
8
|
Requires-Dist: django-filter>=25.2
|
|
9
|
+
Requires-Dist: django-imagekit>=6.0.0
|
|
10
|
+
Requires-Dist: django-ninja>=1.5.1
|
|
9
11
|
Requires-Dist: django-simple-history>=3.10.1
|
|
10
|
-
Requires-Dist: django>=
|
|
11
|
-
Requires-Dist:
|
|
12
|
+
Requires-Dist: django-tasks>=0.10.0
|
|
13
|
+
Requires-Dist: django>=6.0.0
|
|
12
14
|
Requires-Dist: gunicorn>=23.0.0
|
|
13
15
|
Requires-Dist: markdown>=3.10
|
|
14
16
|
Requires-Dist: pillow>=12.0.0
|
|
15
|
-
Requires-Dist:
|
|
17
|
+
Requires-Dist: uvicorn>=0.40.0
|
|
18
|
+
Requires-Dist: whitenoise[brotli]>=6.11.0
|
|
16
19
|
Requires-Python: >=3.12
|
|
17
20
|
Description-Content-Type: text/markdown
|
|
18
21
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
sandwitches/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
sandwitches/admin.py,sha256=IIVQOr_CIXsdFXDPQoZ_c83DuqbHVnw4Ht9tAtmvLM0,870
|
|
3
|
+
sandwitches/api.py,sha256=r4HrlEv9IQMuvlr-WravamzTbUWcn85zS1e-iReHEuU,1968
|
|
4
|
+
sandwitches/asgi.py,sha256=cygnXdXSSVspM7ZXuj47Ef6oz7HSTw4D7BPzgE2PU5w,399
|
|
5
|
+
sandwitches/forms.py,sha256=NeGUi3xPzQpgw2cVWpo2ZKpYUXsCuJ3SzgGUkvxdV2A,2610
|
|
6
|
+
sandwitches/locale/nl/LC_MESSAGES/django.mo,sha256=GgZ4aNmU-v0FStt7mafi_UCR4hC0PC06W0mIUPPWtUE,2906
|
|
7
|
+
sandwitches/locale/nl/LC_MESSAGES/django.po,sha256=uk2W5ai6tyhKlA-rcucv5NpZfBrHNBev6Rp5wKWirqc,2843
|
|
8
|
+
sandwitches/migrations/0001_initial.py,sha256=01IfkFbUyYMpTHV5GaBxJEKjzRIdUdPR5sY3AUTODww,2546
|
|
9
|
+
sandwitches/migrations/0002_historicalrecipe.py,sha256=yU2KYssfjYhPXRYN6C8IMRFr-4QUGJozz-O167XuabM,2499
|
|
10
|
+
sandwitches/migrations/0003_rating.py,sha256=iKk9M9lcBS5LwsJkMYsmYfHKqKs2QRTILgINbnilASM,1857
|
|
11
|
+
sandwitches/migrations/0004_add_uploaded_by.py,sha256=gZawakGU-H0Abnn2Bw_Mswy9ngipzUC-BHRXRByKVZo,720
|
|
12
|
+
sandwitches/migrations/0005_historicalrecipe_uploaded_by.py,sha256=xmYDXgBxjrQkbEtorQlDzW0K1Z70UNxRrUk75WjYdEI,766
|
|
13
|
+
sandwitches/migrations/0006_profile.py,sha256=gzmxZWXmjldimYRdovuNouc2Y2idihg8UvEaU0ad_Ds,1558
|
|
14
|
+
sandwitches/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
+
sandwitches/models.py,sha256=7Upm6abkEZjmk5rIzViW7M_4a3ai03-9tLWLkCty2hs,6390
|
|
16
|
+
sandwitches/settings.py,sha256=5Qg0Wd-X1Kj1Fm7Teh0SRO8jOmVSUkpnUXfZRe1TjwI,5604
|
|
17
|
+
sandwitches/storage.py,sha256=HIiOEDa_LhpsbhCUBNO-SlCZDUJOoANUbyDIbspEcoE,2325
|
|
18
|
+
sandwitches/tasks.py,sha256=aGLpTE42mCZbh2Pyt7fXIjbELm9lD727ir_2bto-wWw,3241
|
|
19
|
+
sandwitches/templates/base.html,sha256=C9tUPfMKRvvdMqdDpE8ww21DH25bNktcVIwdrDEhajw,570
|
|
20
|
+
sandwitches/templates/base_pico.html,sha256=yeWlWlrCfI9yVLStSyODvcNtSmxXvWm4SZORZN-31R4,8273
|
|
21
|
+
sandwitches/templates/detail.html,sha256=p7FikXyse1bYxRCFCYH3GSwgNXHS7D9O6j4fUmmDkWQ,4592
|
|
22
|
+
sandwitches/templates/form.html,sha256=S4yUq9p2v7vWifhA5xj_Z4ObpMoaqUq2w08TDw3vk0o,449
|
|
23
|
+
sandwitches/templates/index.html,sha256=tgZFplcedG1r4s8pBc_qvJDXECPqrESPOSw56FnnCkw,2606
|
|
24
|
+
sandwitches/templates/setup.html,sha256=btid1XtR0MO_qSWriWX7LeWUtNFA7rDB4zt5Md9UNhU,1787
|
|
25
|
+
sandwitches/templates/signup.html,sha256=3qOdDMUHMshe2FoaQzR07lIm3Wbrzorb_h9Fp8Og-Eg,1585
|
|
26
|
+
sandwitches/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
|
+
sandwitches/templatetags/markdown_extras.py,sha256=0ibmRzxE3r85x4k7kK71R-9UT0CgeegYF7MHzj3juTI,344
|
|
28
|
+
sandwitches/urls.py,sha256=maKaIDfb_kNmMwrpS495TymuFnJJ6fbLoi5QRLjrFds,1582
|
|
29
|
+
sandwitches/views.py,sha256=Av9dfTAygGkFkHO47NK6RDjprMTVRpDa4c63X_h9ur8,5192
|
|
30
|
+
sandwitches/wsgi.py,sha256=Eyncpnahq_4s3Lr9ruB-R3Lu9j9zBXqgPbUj7qhIbwU,399
|
|
31
|
+
sandwitches-1.2.0.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
|
|
32
|
+
sandwitches-1.2.0.dist-info/METADATA,sha256=psetHwoaKHYa-_anYOLXOaBdi1UumZwg5lR8uStEg_I,731
|
|
33
|
+
sandwitches-1.2.0.dist-info/RECORD,,
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
sandwitches/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
sandwitches/admin.py,sha256=8QyK9xOUvGGPz30-7p2phyc-Ks9VKD_K1kCAagisPwA,120
|
|
3
|
-
sandwitches/asgi.py,sha256=cygnXdXSSVspM7ZXuj47Ef6oz7HSTw4D7BPzgE2PU5w,399
|
|
4
|
-
sandwitches/forms.py,sha256=EHTENN49BZRm0Ba2sU4Or9NVrO6GhkOaW-rsKcGjClA,1984
|
|
5
|
-
sandwitches/migrations/0001_initial.py,sha256=01IfkFbUyYMpTHV5GaBxJEKjzRIdUdPR5sY3AUTODww,2546
|
|
6
|
-
sandwitches/migrations/0002_historicalrecipe.py,sha256=yU2KYssfjYhPXRYN6C8IMRFr-4QUGJozz-O167XuabM,2499
|
|
7
|
-
sandwitches/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
sandwitches/models.py,sha256=HiPeh9OF1WoqWTjK5uUNuS5eLbtZBHfuXyg8h9Stb2M,3066
|
|
9
|
-
sandwitches/settings.py,sha256=vPk91YEhXlda2ADDla0EcReV_e0gpRsAJylkZI_GS34,3498
|
|
10
|
-
sandwitches/storage.py,sha256=XrzMw8mcUowEoV5hYjP-ZI27C3vfk010UkukA5SrGDk,917
|
|
11
|
-
sandwitches/templates/base.html,sha256=gUOxxwD7G8VeBranSYp40PCWsJ-ywf8e7Qyv8VoqRP4,494
|
|
12
|
-
sandwitches/templates/base_pico.html,sha256=ZjE04fUCQU3iLiJts38qQ-Lkg4tbnwnJ0YfzUB8PVlc,7378
|
|
13
|
-
sandwitches/templates/detail.html,sha256=1hOxlMbj7N6tK_u4t06iETe9lmKF0XSXv2fFp8Yr1hA,1242
|
|
14
|
-
sandwitches/templates/form.html,sha256=XgrQfb_ZEJfnAou7z3gxcj7wqZ4fwFANplvYdzXg80A,373
|
|
15
|
-
sandwitches/templates/index.html,sha256=fnsU5N5dPaa3BXOoIUvgD-9a3EPz0Fs25M0p_eQnf5w,2307
|
|
16
|
-
sandwitches/templates/setup.html,sha256=Y45l96Tl6ln9Mb5gCC4JnT1bf-zuQOpYYL1JL3k-1T0,1677
|
|
17
|
-
sandwitches/templatetags/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
-
sandwitches/templatetags/markdown_extras.py,sha256=0ibmRzxE3r85x4k7kK71R-9UT0CgeegYF7MHzj3juTI,344
|
|
19
|
-
sandwitches/urls.py,sha256=VsN-bLzAPgYAHm_sCJ36F5y_-eMHuPzGT9Trw7dGSSU,1398
|
|
20
|
-
sandwitches/views.py,sha256=_-s7vtJ-n5fnCTnzytFvKK-CMY6Wz5xCb5RKnbI1T4Y,1940
|
|
21
|
-
sandwitches/wsgi.py,sha256=Eyncpnahq_4s3Lr9ruB-R3Lu9j9zBXqgPbUj7qhIbwU,399
|
|
22
|
-
sandwitches-1.0.3.dist-info/WHEEL,sha256=eh7sammvW2TypMMMGKgsM83HyA_3qQ5Lgg3ynoecH3M,79
|
|
23
|
-
sandwitches-1.0.3.dist-info/METADATA,sha256=5W8DYgzufZh4dcTLpWwJ2MpM5pPuWRUEqcUntv-sqMY,621
|
|
24
|
-
sandwitches-1.0.3.dist-info/RECORD,,
|
|
File without changes
|