sandwitches 2.4.0__py3-none-any.whl → 2.5.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.
@@ -47,7 +47,7 @@
47
47
  </style>
48
48
  {% block extra_head %}{% endblock %}
49
49
  </head>
50
- <body class="{% block body_class %}{% endblock %}">
50
+ <body class="{% block body_class %}{% endblock %} {% if user.is_authenticated %}{{ user.theme }}{% endif %}">
51
51
  <div id="loading-sandwich" class="loading-sandwich-container">
52
52
  <svg class="loading-sandwich-svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
53
53
  <path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z" />
@@ -12,7 +12,6 @@
12
12
 
13
13
  {% block navbar %}
14
14
  {% include "components/navbar.html" %}
15
- {% include "components/language_dialog.html" %}
16
15
  {% include "components/user_menu.html" %}
17
16
  {% include "components/side_menu.html" %}
18
17
  {% endblock %}
@@ -4,6 +4,15 @@
4
4
  {% block title %}{% trans "Community" %}{% endblock %}
5
5
 
6
6
  {% block content %}
7
+ <style>
8
+ .cropper-container {
9
+ max-height: 70vh;
10
+ }
11
+ #cropper-image {
12
+ display: block;
13
+ max-width: 100%;
14
+ }
15
+ </style>
7
16
  <main class="responsive">
8
17
  <div class="large-space"></div>
9
18
  <article class="round s12 m10 l8 offset-m1 offset-l2 elevate">
@@ -15,6 +24,7 @@
15
24
 
16
25
  <form method="post" enctype="multipart/form-data">
17
26
  {% csrf_token %}
27
+ {{ form.image_data }}
18
28
 
19
29
  <div class="grid">
20
30
  <div class="s12">
@@ -53,15 +63,21 @@
53
63
 
54
64
  <!-- Image Upload with Preview -->
55
65
  <div class="s12 m6">
56
- <div class="field label border round">
66
+ <div class="field file label border round">
67
+ <input type="text" readonly>
57
68
  {{ form.image }}
58
69
  <label>{% trans "Image" %}</label>
70
+ <i>publish</i>
59
71
  </div>
60
72
  </div>
61
73
  <div class="s12 m6 center-align relative">
62
- <div class="padding border round dashed surface-variant" style="min-height: 150px; display: flex; align-items: center; justify-content: center;">
63
- <img id="image-preview" src="{% if recipe.image %}{{ recipe.image.url }}{% endif %}" class="responsive round" style="max-height: 200px; {% if not recipe.image %}display:none;{% endif %}">
74
+ <div class="padding border round dashed surface-variant" style="min-height: 150px; display: flex; align-items: center; justify-content: center; flex-direction: column;">
75
+ <img id="image-preview" src="{% if recipe.image %}{{ recipe.image.url }}{% endif %}" class="responsive round mb-1" style="max-height: 200px; {% if not recipe.image %}display:none;{% endif %}">
64
76
  <span id="image-placeholder" class="gray-text" {% if recipe.image %}style="display:none;"{% endif %}>{% trans "Image Preview" %}</span>
77
+ <button type="button" id="edit-image-btn" class="button transparent border round" style="display: none;" onclick="openCropper()">
78
+ <i>crop_rotate</i>
79
+ <span>{% trans "Edit Image" %}</span>
80
+ </button>
65
81
  </div>
66
82
  </div>
67
83
 
@@ -101,6 +117,41 @@
101
117
  </div>
102
118
  </article>
103
119
 
120
+ <dialog id="cropper-dialog" class="large">
121
+ <div class="padding">
122
+ <h5 class="bold mb-1">{% trans "Edit Image" %}</h5>
123
+ <div class="cropper-container mb-1">
124
+ <img id="cropper-image" src="">
125
+ </div>
126
+ <div class="row scroll no-space border round mb-1">
127
+ <button type="button" class="button transparent max" onclick="cropper.rotate(-90)" title="{% trans 'Rotate Left' %}">
128
+ <i>rotate_left</i>
129
+ </button>
130
+ <button type="button" class="button transparent max" onclick="cropper.rotate(90)" title="{% trans 'Rotate Right' %}">
131
+ <i>rotate_right</i>
132
+ </button>
133
+ <div class="divider vertical"></div>
134
+ <button type="button" class="button transparent max" onclick="cropper.scaleX(-cropper.getData().scaleX || -1)" title="{% trans 'Flip Horizontal' %}">
135
+ <i>flip</i>
136
+ </button>
137
+ <button type="button" class="button transparent max" onclick="cropper.scaleY(-cropper.getData().scaleY || -1)" title="{% trans 'Flip Vertical' %}">
138
+ <i>flip</i>
139
+ </button>
140
+ <div class="divider vertical"></div>
141
+ <button type="button" class="button transparent max" onclick="cropper.setAspectRatio(1)" title="{% trans '1:1' %}">1:1</button>
142
+ <button type="button" class="button transparent max" onclick="cropper.setAspectRatio(4/3)" title="{% trans '4:3' %}">4:3</button>
143
+ <button type="button" class="button transparent max" onclick="cropper.setAspectRatio(16/9)" title="{% trans '16:9' %}">16:9</button>
144
+ <button type="button" class="button transparent max" onclick="cropper.setAspectRatio(NaN)" title="{% trans 'Free' %}">
145
+ <i>crop_free</i>
146
+ </button>
147
+ </div>
148
+ <nav class="right-align">
149
+ <button type="button" class="button transparent" onclick="ui('#cropper-dialog')">{% trans "Cancel" %}</button>
150
+ <button type="button" class="button primary" onclick="applyCrop()">{% trans "Apply" %}</button>
151
+ </nav>
152
+ </div>
153
+ </dialog>
154
+
104
155
 
105
156
  <div class="large-space"></div>
106
157
 
@@ -117,21 +168,67 @@
117
168
 
118
169
  {% block page_scripts %}
119
170
  <script>
120
- // Image Preview Logic
121
- var imageInput = document.querySelector('input[type="file"]');
171
+ let cropper;
172
+ const imageInput = document.querySelector('input[type="file"]');
173
+ const imagePreview = document.getElementById('image-preview');
174
+ const imagePlaceholder = document.getElementById('image-placeholder');
175
+ const editImageBtn = document.getElementById('edit-image-btn');
176
+ const imageDataInput = document.getElementsByName('image_data')[0];
177
+ const cropperImage = document.getElementById('cropper-image');
178
+
179
+ function openCropper() {
180
+ if (!imagePreview.src || imagePreview.src === window.location.href) return;
181
+ cropperImage.src = imagePreview.src;
182
+ ui('#cropper-dialog');
183
+
184
+ if (cropper) {
185
+ cropper.destroy();
186
+ }
187
+
188
+ setTimeout(() => {
189
+ cropper = new Cropper(cropperImage, {
190
+ viewMode: 1,
191
+ autoCropArea: 1,
192
+ responsive: true,
193
+ restore: false,
194
+ checkCrossOrigin: true,
195
+ guides: true,
196
+ center: true,
197
+ highlight: false,
198
+ cropBoxMovable: true,
199
+ cropBoxResizable: true,
200
+ toggleDragModeOnDblclick: false,
201
+ });
202
+ }, 100);
203
+ }
204
+
205
+ function applyCrop() {
206
+ const canvas = cropper.getCroppedCanvas({
207
+ maxWidth: 2000,
208
+ maxHeight: 2000,
209
+ });
210
+
211
+ const croppedData = canvas.toDataURL('image/jpeg', 0.9);
212
+ imagePreview.src = croppedData;
213
+ imagePreview.style.display = 'block';
214
+ if (imagePlaceholder) imagePlaceholder.style.display = 'none';
215
+ if (editImageBtn) editImageBtn.style.display = 'inline-flex';
216
+ imageDataInput.value = croppedData;
217
+
218
+ ui('#cropper-dialog');
219
+ }
220
+
122
221
  if (imageInput) {
123
222
  imageInput.onchange = function (evt) {
124
- var tgt = evt.target || window.event.srcElement,
125
- files = tgt.files;
126
-
223
+ const files = evt.target.files;
127
224
  if (FileReader && files && files.length) {
128
- var fr = new FileReader();
225
+ const fr = new FileReader();
129
226
  fr.onload = function () {
130
- var preview = document.getElementById('image-preview');
131
- var placeholder = document.getElementById('image-placeholder');
132
- preview.src = fr.result;
133
- preview.style.display = 'block';
134
- if(placeholder) placeholder.style.display = 'none';
227
+ imagePreview.src = fr.result;
228
+ imagePreview.style.display = 'block';
229
+ if (imagePlaceholder) imagePlaceholder.style.display = 'none';
230
+ if (editImageBtn) editImageBtn.style.display = 'inline-flex';
231
+ openCropper();
135
232
  }
136
233
  fr.readAsDataURL(files[0]);
137
234
  }
@@ -7,12 +7,6 @@
7
7
  <img src="{% static 'icons/icon.svg' %}" class="circle small">
8
8
  </a>
9
9
  <div class="max"></div>
10
- <button class="circle transparent" onclick="ui('mode', ui('mode') == 'dark' ? 'light' : 'dark')">
11
- <i>dark_mode</i>
12
- </button>
13
- <button class="circle transparent" data-ui="#language-menu">
14
- <i>language</i>
15
- </button>
16
10
 
17
11
  {% if user.is_authenticated %}
18
12
  <a href="{% url 'view_cart' %}" class="button circle transparent">
@@ -19,6 +19,10 @@
19
19
  <i class="extra padding">group</i>
20
20
  <span class="large-text">{% trans "Community" %}</span>
21
21
  </a>
22
+ <a href="{% url 'user_settings' %}" class="padding {% if request.resolver_match.url_name == 'user_settings' %}active{% endif %}">
23
+ <i class="extra padding">settings</i>
24
+ <span class="large-text">{% trans "Settings" %}</span>
25
+ </a>
22
26
  {% endif %}
23
27
  <a href="/api/docs" class="padding">
24
28
  <i class="extra padding">api</i>
@@ -1,6 +1,7 @@
1
1
  {% load i18n %}
2
2
  {% if user.is_authenticated %}
3
3
  <menu id="user-menu" class="no-wrap left">
4
+ <a href="{% url 'user_settings' %}" class="row"><i>settings</i>{% trans "Settings" %}</a>
4
5
  {% if user.is_staff %}
5
6
  <a href="{% url 'admin_dashboard' %}" class="row"><i>admin_panel_settings</i>{% trans "Admin" %}</a>
6
7
  <div class="divider"></div>
@@ -0,0 +1,68 @@
1
+ {% extends "base_beer.html" %}
2
+ {% load static i18n %}
3
+
4
+ {% block title %}{% trans "Order Details" %} #{{ order.id }}{% endblock %}
5
+
6
+ {% block content %}
7
+ <div class="large-space"></div>
8
+
9
+ <div class="grid">
10
+ <div class="s12 m10 l8 xl6 middle-align center-align" style="margin: 0 auto;">
11
+ <article class="round elevate left-align">
12
+ <div class="padding">
13
+ <nav>
14
+ <a href="{% url 'user_profile' %}" class="button transparent circle">
15
+ <i>arrow_back</i>
16
+ </a>
17
+ <h5 class="max">{% trans "Order" %} #{{ order.id }}</h5>
18
+ <span class="chip {% if order.status == 'PENDING' %}surface-variant{% elif order.status == 'COMPLETED' %}primary{% elif order.status == 'CANCELLED' %}error{% else %}secondary{% endif %}">
19
+ {{ order.get_status_display }}
20
+ </span>
21
+ </nav>
22
+ <div class="divider"></div>
23
+
24
+ <div class="grid">
25
+ <div class="s12 m6">
26
+ <p class="small-text">{% trans "Date Ordered" %}</p>
27
+ <p>{{ order.created_at|date:"d F Y, H:i" }}</p>
28
+ </div>
29
+ <div class="s12 m6">
30
+ <p class="small-text">{% trans "Last Update" %}</p>
31
+ <p>{{ order.updated_at|date:"d F Y, H:i" }}</p>
32
+ </div>
33
+ </div>
34
+
35
+ <div class="divider"></div>
36
+
37
+ <h6>{% trans "Item Details" %}</h6>
38
+ <div class="row">
39
+ <div class="max">
40
+ <a href="{% url 'recipe_detail' order.recipe.slug %}" class="bold primary-text">{{ order.recipe.title }}</a>
41
+ <p class="small-text">{{ order.recipe.description|truncatewords:20 }}</p>
42
+ </div>
43
+ <div class="min">
44
+ <span class="bold">{{ order.total_price }} €</span>
45
+ </div>
46
+ </div>
47
+
48
+ {% if order.recipe.image %}
49
+ <div class="space"></div>
50
+ <img src="{{ order.recipe.image.url }}" class="responsive round border" alt="{{ order.recipe.title }}">
51
+ {% endif %}
52
+
53
+ <div class="divider"></div>
54
+
55
+ <div class="row">
56
+ <div class="max text-right">
57
+ <span class="bold">{% trans "Total" %}</span>
58
+ </div>
59
+ <div class="min">
60
+ <span class="bold text-primary">{{ order.total_price }} €</span>
61
+ </div>
62
+ </div>
63
+
64
+ </div>
65
+ </article>
66
+ </div>
67
+ </div>
68
+ {% endblock %}
@@ -84,12 +84,103 @@
84
84
  <div class="large-space"></div>
85
85
 
86
86
  <nav class="right-align">
87
+ <a class="button transparent border round" href="{% url 'user_settings' %}"><i>settings</i> {% trans "Settings" %}</a>
87
88
  <a class="button transparent border round" href="{% url 'index' %}">{% trans "Cancel" %}</a>
88
89
  <button type="submit" class="button primary round">{% trans "Save changes" %}</button>
89
90
  </nav>
90
91
 
91
92
  </form>
92
93
  </article>
94
+
95
+ <div class="large-space"></div>
96
+
97
+ <h4 class="center-align primary-text">{% trans "Order History" %}</h4>
98
+
99
+ <form method="get" class="row no-wrap middle-align">
100
+ <div class="field label border round small">
101
+ <select name="status" onchange="this.form.submit()">
102
+ <option value="">{% trans "All Statuses" %}</option>
103
+ {% for code, label in status_choices %}
104
+ <option value="{{ code }}" {% if current_status == code %}selected{% endif %}>{{ label }}</option>
105
+ {% endfor %}
106
+ </select>
107
+ <label>{% trans "Filter by Status" %}</label>
108
+ </div>
109
+ <div class="space"></div>
110
+ <div class="field label border round small">
111
+ <select name="sort" onchange="this.form.submit()">
112
+ <option value="date_desc" {% if current_sort == 'date_desc' %}selected{% endif %}>{% trans "Newest First" %}</option>
113
+ <option value="date_asc" {% if current_sort == 'date_asc' %}selected{% endif %}>{% trans "Oldest First" %}</option>
114
+ <option value="price_desc" {% if current_sort == 'price_desc' %}selected{% endif %}>{% trans "Price: High to Low" %}</option>
115
+ <option value="price_asc" {% if current_sort == 'price_asc' %}selected{% endif %}>{% trans "Price: Low to High" %}</option>
116
+ </select>
117
+ <label>{% trans "Sort by" %}</label>
118
+ </div>
119
+ </form>
120
+
121
+ {% if orders %}
122
+ <div class="padding border round surface left-align">
123
+ <table class="border striped">
124
+ <thead>
125
+ <tr>
126
+ <th class="min">#</th>
127
+ <th class="max">{% trans "Recipe" %}</th>
128
+ <th class="min">{% trans "Date" %}</th>
129
+ <th class="min">{% trans "Status" %}</th>
130
+ <th class="min">{% trans "Price" %}</th>
131
+ <th class="min"></th>
132
+ </tr>
133
+ </thead>
134
+ <tbody>
135
+ {% for order in orders %}
136
+ <tr>
137
+ <td>{{ order.id }}</td>
138
+ <td>
139
+ <a href="{% url 'recipe_detail' order.recipe.slug %}">{{ order.recipe.title }}</a>
140
+ </td>
141
+ <td class="no-wrap">{{ order.created_at|date:"d/m/Y" }}</td>
142
+ <td>
143
+ <span class="chip tiny {% if order.status == 'PENDING' %}surface-variant{% elif order.status == 'COMPLETED' %}primary{% elif order.status == 'CANCELLED' %}error{% else %}secondary{% endif %}">
144
+ {{ order.get_status_display }}
145
+ </span>
146
+ </td>
147
+ <td class="no-wrap">{{ order.total_price }} €</td>
148
+ <td>
149
+ <a href="{% url 'user_order_detail' order.id %}" class="button circle transparent small">
150
+ <i>visibility</i>
151
+ </a>
152
+ </td>
153
+ </tr>
154
+ {% endfor %}
155
+ </tbody>
156
+ </table>
157
+ </div>
158
+
159
+ {% if orders.paginator.num_pages > 1 %}
160
+ <div class="center-align padding">
161
+ <nav class="row">
162
+ {% if orders.has_previous %}
163
+ <a href="?page={{ orders.previous_page_number }}{% if current_status %}&status={{ current_status }}{% endif %}{% if current_sort %}&sort={{ current_sort }}{% endif %}" class="button transparent circle"><i>chevron_left</i></a>
164
+ {% else %}
165
+ <button class="button transparent circle" disabled><i>chevron_left</i></button>
166
+ {% endif %}
167
+
168
+ <span class="padding">{{ orders.number }} / {{ orders.paginator.num_pages }}</span>
169
+
170
+ {% if orders.has_next %}
171
+ <a href="?page={{ orders.next_page_number }}{% if current_status %}&status={{ current_status }}{% endif %}{% if current_sort %}&sort={{ current_sort }}{% endif %}" class="button transparent circle"><i>chevron_right</i></a>
172
+ {% else %}
173
+ <button class="button transparent circle" disabled><i>chevron_right</i></button>
174
+ {% endif %}
175
+ </nav>
176
+ </div>
177
+ {% endif %}
178
+
179
+ {% else %}
180
+ <div class="padding border round surface">
181
+ <p class="center-align">{% trans "No previous orders found." %}</p>
182
+ </div>
183
+ {% endif %}
93
184
  </div>
94
185
  </div>
95
186
  {% endblock %}
@@ -0,0 +1,53 @@
1
+ {% extends "base_beer.html" %}
2
+ {% load static i18n %}
3
+ {% block title %}{% trans "Settings" %}{% endblock %}
4
+
5
+ {% block content %}
6
+ <div class="large-space"></div>
7
+
8
+ <div class="grid">
9
+ <div class="s12 m8 l6 xl4 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 "User Settings" %}</h4>
13
+ </div>
14
+
15
+ <form method="post" novalidate>
16
+ {% csrf_token %}
17
+
18
+ <div class="field label border round {% if form.language.errors %}error{% endif %}">
19
+ <select name="{{ form.language.name }}" id="{{ form.language.id_for_label }}">
20
+ {% for value, label in form.language.field.choices %}
21
+ <option value="{{ value }}" {% if form.language.value == value %}selected{% endif %}>{{ label }}</option>
22
+ {% endfor %}
23
+ </select>
24
+ <label>{% trans "Preferred Language" %}</label>
25
+ {% if form.language.errors %}
26
+ <span class="helper error-text">{{ form.language.errors.0 }}</span>
27
+ {% endif %}
28
+ </div>
29
+
30
+ <div class="field label border round {% if form.theme.errors %}error{% endif %}">
31
+ <select name="{{ form.theme.name }}" id="{{ form.theme.id_for_label }}">
32
+ {% for value, label in form.theme.field.choices %}
33
+ <option value="{{ value }}" {% if form.theme.value == value %}selected{% endif %}>{{ label }}</option>
34
+ {% endfor %}
35
+ </select>
36
+ <label>{% trans "Preferred Theme" %}</label>
37
+ {% if form.theme.errors %}
38
+ <span class="helper error-text">{{ form.theme.errors.0 }}</span>
39
+ {% endif %}
40
+ </div>
41
+
42
+ <div class="large-space"></div>
43
+
44
+ <nav class="right-align">
45
+ <a class="button transparent border round" href="{% url 'index' %}">{% trans "Cancel" %}</a>
46
+ <button type="submit" class="button primary round">{% trans "Save changes" %}</button>
47
+ </nav>
48
+
49
+ </form>
50
+ </article>
51
+ </div>
52
+ </div>
53
+ {% endblock %}
@@ -64,18 +64,6 @@
64
64
  {% endif %}
65
65
  </div>
66
66
 
67
- <div class="field label border round {% if form.language.errors %}error{% endif %}">
68
- <select name="{{ form.language.name }}" id="{{ form.language.id_for_label }}">
69
- {% for value, label in form.language.field.choices %}
70
- <option value="{{ value }}" {% if form.language.value == value %}selected{% endif %}>{{ label }}</option>
71
- {% endfor %}
72
- </select>
73
- <label>{% trans "Preferred Language" %}</label>
74
- {% if form.language.errors %}
75
- <span class="helper error-text">{{ form.language.errors.0 }}</span>
76
- {% endif %}
77
- </div>
78
-
79
67
  <div class="field label border round textarea {% if form.bio.errors %}error{% endif %}">
80
68
  <textarea name="{{ form.bio.name }}" id="{{ form.bio.id_for_label }}" rows="3">{{ form.bio.value|default:'' }}</textarea>
81
69
  <label>{% trans "Bio" %}</label>
sandwitches/urls.py CHANGED
@@ -34,6 +34,8 @@ urlpatterns = [
34
34
  path("login/", views.CustomLoginView.as_view(), name="login"),
35
35
  path("logout/", LogoutView.as_view(next_page="index"), name="logout"),
36
36
  path("profile/", views.user_profile, name="user_profile"),
37
+ path("settings/", views.user_settings, name="user_settings"),
38
+ path("orders/<int:pk>/", views.user_order_detail, name="user_order_detail"),
37
39
  path("community/", views.community, name="community"),
38
40
  path("admin/", admin.site.urls),
39
41
  path("api/", api.urls),
sandwitches/views.py CHANGED
@@ -8,6 +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 django.utils import translation
11
12
  from .models import Recipe, Rating, Tag, Order, CartItem
12
13
  from .forms import (
13
14
  RecipeForm,
@@ -18,6 +19,7 @@ from .forms import (
18
19
  TagForm,
19
20
  UserProfileForm,
20
21
  UserRecipeSubmissionForm,
22
+ UserSettingsForm,
21
23
  )
22
24
  from django.http import HttpResponseBadRequest, Http404
23
25
  from django.conf import settings
@@ -28,6 +30,7 @@ from PIL import Image
28
30
  from django.db.models import Q, Avg
29
31
  from django_tasks.backends.database.models import DBTaskResult
30
32
  from django.contrib.auth.views import LoginView
33
+ from django.core.paginator import Paginator
31
34
 
32
35
 
33
36
  from sandwitches import __version__ as sandwitches_version
@@ -858,8 +861,78 @@ def user_profile(request):
858
861
  return redirect("user_profile")
859
862
  else:
860
863
  form = UserProfileForm(instance=request.user)
864
+
865
+ orders = request.user.orders.select_related("recipe").all()
866
+
867
+ # Filtering
868
+ status_filter = request.GET.get("status")
869
+ if status_filter:
870
+ orders = orders.filter(status=status_filter)
871
+
872
+ # Sorting
873
+ sort_param = request.GET.get("sort", "-created_at")
874
+ allowed_sorts = {
875
+ "date_asc": "created_at",
876
+ "date_desc": "-created_at",
877
+ "price_asc": "total_price",
878
+ "price_desc": "-total_price",
879
+ "status": "status",
880
+ }
881
+ order_by = allowed_sorts.get(sort_param, "-created_at")
882
+ orders = orders.order_by(order_by)
883
+
884
+ # Pagination
885
+ paginator = Paginator(orders, 5) # Show 5 orders per page
886
+ page_number = request.GET.get("page")
887
+ page_obj = paginator.get_page(page_number)
888
+
889
+ return render(
890
+ request,
891
+ "profile.html",
892
+ {
893
+ "form": form,
894
+ "version": sandwitches_version,
895
+ "orders": page_obj,
896
+ "current_status": status_filter,
897
+ "current_sort": sort_param,
898
+ "status_choices": Order.STATUS_CHOICES,
899
+ },
900
+ )
901
+
902
+
903
+ @login_required
904
+ def user_settings(request):
905
+ if request.method == "POST":
906
+ form = UserSettingsForm(request.POST, instance=request.user)
907
+ if form.is_valid():
908
+ user = form.save()
909
+ # Update language in session and cookie
910
+ translation.activate(user.language)
911
+ request.session[translation.LANGUAGE_SESSION_KEY] = user.language # ty:ignore[unresolved-attribute]
912
+ messages.success(request, _("Settings updated successfully."))
913
+ response = redirect("user_settings")
914
+ response.set_cookie(settings.LANGUAGE_COOKIE_NAME, user.language)
915
+ return response
916
+ else:
917
+ form = UserSettingsForm(instance=request.user)
918
+
861
919
  return render(
862
- request, "profile.html", {"form": form, "version": sandwitches_version}
920
+ request,
921
+ "settings.html",
922
+ {
923
+ "form": form,
924
+ "version": sandwitches_version,
925
+ },
926
+ )
927
+
928
+
929
+ @login_required
930
+ def user_order_detail(request, pk):
931
+ order = get_object_or_404(Order, pk=pk, user=request.user)
932
+ return render(
933
+ request,
934
+ "order_detail.html",
935
+ {"order": order, "version": sandwitches_version},
863
936
  )
864
937
 
865
938
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sandwitches
3
- Version: 2.4.0
3
+ Version: 2.5.0
4
4
  Summary: Add your description here
5
5
  Author: Martyn van Dijke
6
6
  Author-email: Martyn van Dijke <martijnvdijke600@gmail.com>