sandwitches 2.4.1__tar.gz → 2.5.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. {sandwitches-2.4.1 → sandwitches-2.5.0}/PKG-INFO +1 -1
  2. {sandwitches-2.4.1 → sandwitches-2.5.0}/pyproject.toml +1 -1
  3. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/forms.py +13 -8
  4. sandwitches-2.5.0/src/sandwitches/migrations/0016_user_theme.py +21 -0
  5. sandwitches-2.5.0/src/sandwitches/migrations/0017_setting_gotify_token_setting_gotify_url.py +31 -0
  6. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/models.py +39 -1
  7. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/tasks.py +31 -1
  8. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/admin/admin_base.html +0 -3
  9. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/base.html +1 -1
  10. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/base_beer.html +0 -1
  11. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/components/navbar.html +0 -6
  12. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/components/side_menu.html +4 -0
  13. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/components/user_menu.html +1 -0
  14. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/profile.html +1 -0
  15. sandwitches-2.5.0/src/sandwitches/templates/settings.html +53 -0
  16. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/signup.html +0 -12
  17. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/urls.py +1 -0
  18. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/views.py +28 -0
  19. sandwitches-2.4.1/src/sandwitches/templates/components/language_dialog.html +0 -26
  20. {sandwitches-2.4.1 → sandwitches-2.5.0}/README.md +0 -0
  21. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/__init__.py +0 -0
  22. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/admin.py +0 -0
  23. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/api.py +0 -0
  24. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/asgi.py +0 -0
  25. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/feeds.py +0 -0
  26. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/locale/nl/LC_MESSAGES/django.mo +0 -0
  27. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/locale/nl/LC_MESSAGES/django.po +0 -0
  28. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/management/__init__.py +0 -0
  29. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/management/commands/__init__.py +0 -0
  30. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/management/commands/reset_daily_orders.py +0 -0
  31. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/migrations/0001_initial.py +0 -0
  32. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/migrations/0002_historicalrecipe_servings_recipe_servings.py +0 -0
  33. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/migrations/0003_setting.py +0 -0
  34. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/migrations/0004_alter_setting_ai_api_key_and_more.py +0 -0
  35. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/migrations/0005_rating_comment.py +0 -0
  36. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/migrations/0006_historicalrecipe_is_highlighted_and_more.py +0 -0
  37. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/migrations/0007_historicalrecipe_price_recipe_price_order.py +0 -0
  38. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/migrations/0008_historicalrecipe_daily_orders_count_and_more.py +0 -0
  39. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/migrations/0009_historicalrecipe_is_approved_recipe_is_approved.py +0 -0
  40. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/migrations/0010_rename_is_approved_historicalrecipe_is_community_made_and_more.py +0 -0
  41. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/migrations/0011_alter_historicalrecipe_is_community_made_and_more.py +0 -0
  42. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/migrations/0012_rename_is_community_made_historicalrecipe_is_approved_and_more.py +0 -0
  43. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/migrations/0013_cartitem.py +0 -0
  44. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/migrations/0014_ensure_groups_exist.py +0 -0
  45. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/migrations/0015_order_completed_alter_order_status_and_more.py +0 -0
  46. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/migrations/__init__.py +0 -0
  47. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/settings.py +0 -0
  48. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/storage.py +0 -0
  49. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/admin/confirm_delete.html +0 -0
  50. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/admin/dashboard.html +0 -0
  51. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/admin/order_list.html +0 -0
  52. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/admin/partials/dashboard_charts.html +0 -0
  53. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/admin/partials/order_rows.html +0 -0
  54. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/admin/rating_list.html +0 -0
  55. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/admin/recipe_approval_list.html +0 -0
  56. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/admin/recipe_form.html +0 -0
  57. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/admin/recipe_list.html +0 -0
  58. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/admin/tag_form.html +0 -0
  59. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/admin/tag_list.html +0 -0
  60. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/admin/task_detail.html +0 -0
  61. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/admin/task_list.html +0 -0
  62. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/admin/user_form.html +0 -0
  63. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/admin/user_list.html +0 -0
  64. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/cart.html +0 -0
  65. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/community.html +0 -0
  66. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/components/carousel_scripts.html +0 -0
  67. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/components/favorites_search_form.html +0 -0
  68. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/components/footer.html +0 -0
  69. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/components/ingredients_scripts.html +0 -0
  70. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/components/ingredients_section.html +0 -0
  71. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/components/instructions_section.html +0 -0
  72. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/components/rating_section.html +0 -0
  73. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/components/recipe_header.html +0 -0
  74. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/components/search_form.html +0 -0
  75. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/components/search_scripts.html +0 -0
  76. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/detail.html +0 -0
  77. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/favorites.html +0 -0
  78. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/index.html +0 -0
  79. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/login.html +0 -0
  80. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/order_detail.html +0 -0
  81. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/partials/recipe_list.html +0 -0
  82. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templates/setup.html +0 -0
  83. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templatetags/__init__.py +0 -0
  84. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templatetags/custom_filters.py +0 -0
  85. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/templatetags/markdown_extras.py +0 -0
  86. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/utils.py +0 -0
  87. {sandwitches-2.4.1 → sandwitches-2.5.0}/src/sandwitches/wsgi.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: sandwitches
3
- Version: 2.4.1
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>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sandwitches"
3
- version = "2.4.1"
3
+ version = "2.5.0"
4
4
  description = "Add your description here"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -1,5 +1,4 @@
1
1
  from django import forms
2
- from django.conf import settings
3
2
  from django.contrib.auth import get_user_model
4
3
  from django.contrib.auth.forms import UserCreationForm
5
4
  from django.utils.translation import gettext_lazy as _
@@ -47,11 +46,6 @@ class AdminSetupForm(forms.ModelForm, BaseUserFormMixin):
47
46
 
48
47
 
49
48
  class UserSignupForm(UserCreationForm, BaseUserFormMixin):
50
- language = forms.ChoiceField(
51
- choices=settings.LANGUAGES,
52
- label=_("Preferred language"),
53
- initial=settings.LANGUAGE_CODE,
54
- )
55
49
  avatar = forms.ImageField(label=_("Profile Image"), required=False)
56
50
  bio = forms.CharField(
57
51
  widget=forms.Textarea(attrs={"rows": 3}), label=_("Bio"), required=False
@@ -64,7 +58,6 @@ class UserSignupForm(UserCreationForm, BaseUserFormMixin):
64
58
  "first_name",
65
59
  "last_name",
66
60
  "email",
67
- "language",
68
61
  "avatar",
69
62
  "bio",
70
63
  )
@@ -77,7 +70,6 @@ class UserSignupForm(UserCreationForm, BaseUserFormMixin):
77
70
  user.is_superuser = False
78
71
  user.is_staff = False
79
72
  # Explicitly save the extra fields if they aren't automatically handled by ModelForm save (they should be if in Meta.fields)
80
- user.language = self.cleaned_data["language"]
81
73
  user.avatar = self.cleaned_data["avatar"]
82
74
  user.bio = self.cleaned_data["bio"]
83
75
  if commit:
@@ -114,6 +106,16 @@ class UserProfileForm(forms.ModelForm):
114
106
  return user
115
107
 
116
108
 
109
+ class UserSettingsForm(forms.ModelForm):
110
+ class Meta:
111
+ model = User
112
+ fields = ("language", "theme")
113
+ labels = {
114
+ "language": _("Preferred Language"),
115
+ "theme": _("Preferred Theme"),
116
+ }
117
+
118
+
117
119
  class UserEditForm(forms.ModelForm):
118
120
  image_data = forms.CharField(widget=forms.HiddenInput(), required=False)
119
121
 
@@ -299,7 +301,10 @@ class SettingForm(forms.ModelForm):
299
301
  "ai_connection_point",
300
302
  "ai_model",
301
303
  "ai_api_key",
304
+ "gotify_url",
305
+ "gotify_token",
302
306
  ]
303
307
  widgets = {
304
308
  "ai_api_key": forms.PasswordInput(render_value=True),
309
+ "gotify_token": forms.PasswordInput(render_value=True),
305
310
  }
@@ -0,0 +1,21 @@
1
+ # Generated by Django 6.0.1 on 2026-01-28 09:08
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+ dependencies = [
8
+ ("sandwitches", "0015_order_completed_alter_order_status_and_more"),
9
+ ]
10
+
11
+ operations = [
12
+ migrations.AddField(
13
+ model_name="user",
14
+ name="theme",
15
+ field=models.CharField(
16
+ choices=[("light", "Light"), ("dark", "Dark")],
17
+ default="light",
18
+ max_length=10,
19
+ ),
20
+ ),
21
+ ]
@@ -0,0 +1,31 @@
1
+ # Generated by Django 6.0.1 on 2026-01-28 12:45
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+ dependencies = [
8
+ ("sandwitches", "0016_user_theme"),
9
+ ]
10
+
11
+ operations = [
12
+ migrations.AddField(
13
+ model_name="setting",
14
+ name="gotify_token",
15
+ field=models.CharField(
16
+ blank=True,
17
+ help_text="The application token for Gotify",
18
+ max_length=255,
19
+ null=True,
20
+ ),
21
+ ),
22
+ migrations.AddField(
23
+ model_name="setting",
24
+ name="gotify_url",
25
+ field=models.URLField(
26
+ blank=True,
27
+ help_text="The URL of your Gotify server (e.g., https://gotify.example.com)",
28
+ null=True,
29
+ ),
30
+ ),
31
+ ]
@@ -4,7 +4,7 @@ from .storage import HashedFilenameStorage
4
4
  from simple_history.models import HistoricalRecords
5
5
  from django.contrib.auth.models import AbstractUser
6
6
  from django.db.models import Avg
7
- from .tasks import email_users, notify_order_submitted
7
+ from .tasks import email_users, notify_order_submitted, send_gotify_notification
8
8
  from django.conf import settings
9
9
  from django.core.validators import MinValueValidator, MaxValueValidator
10
10
  import logging
@@ -27,6 +27,18 @@ class Setting(SingletonModel):
27
27
  ai_model = models.CharField(max_length=255, blank=True, null=True)
28
28
  ai_api_key = models.CharField(max_length=255, blank=True, null=True)
29
29
 
30
+ gotify_url = models.URLField(
31
+ blank=True,
32
+ null=True,
33
+ help_text="The URL of your Gotify server (e.g., https://gotify.example.com)",
34
+ )
35
+ gotify_token = models.CharField(
36
+ max_length=255,
37
+ blank=True,
38
+ null=True,
39
+ help_text="The application token for Gotify",
40
+ )
41
+
30
42
  def __str__(self):
31
43
  return "Site Settings"
32
44
 
@@ -50,6 +62,11 @@ class User(AbstractUser):
50
62
  choices=settings.LANGUAGES,
51
63
  default=settings.LANGUAGE_CODE,
52
64
  )
65
+ theme = models.CharField(
66
+ max_length=10,
67
+ choices=[("light", "Light"), ("dark", "Dark")],
68
+ default="light",
69
+ )
53
70
  favorites = models.ManyToManyField(
54
71
  "Recipe", related_name="favorited_by", blank=True
55
72
  )
@@ -61,6 +78,16 @@ class User(AbstractUser):
61
78
  def __str__(self):
62
79
  return self.username
63
80
 
81
+ def save(self, *args, **kwargs):
82
+ is_new = self.pk is None
83
+ super().save(*args, **kwargs)
84
+ if is_new:
85
+ send_gotify_notification.enqueue(
86
+ title="New User Created",
87
+ message=f"User {self.username} has joined Sandwitches!",
88
+ priority=4,
89
+ )
90
+
64
91
 
65
92
  class Tag(models.Model):
66
93
  name = models.CharField(max_length=50, unique=True)
@@ -173,6 +200,12 @@ class Recipe(models.Model):
173
200
  logging.warning(
174
201
  "Email sending is disabled; not sending email notification, make sure SEND_EMAIL is set to True in settings."
175
202
  )
203
+
204
+ send_gotify_notification.enqueue(
205
+ title="New Recipe Uploaded",
206
+ message=f"A new recipe '{self.title}' has been uploaded by {self.uploaded_by or 'Unknown'}.",
207
+ priority=5,
208
+ )
176
209
  else:
177
210
  logging.debug(
178
211
  "Existing recipe saved (update); skipping email notification."
@@ -282,6 +315,11 @@ class Order(models.Model):
282
315
 
283
316
  if is_new:
284
317
  notify_order_submitted.enqueue(order_id=self.pk)
318
+ send_gotify_notification.enqueue(
319
+ title="New Order Received",
320
+ message=f"Order #{self.pk} for '{self.recipe.title}' by {self.user.username}. Total: {self.total_price}€",
321
+ priority=6,
322
+ )
285
323
 
286
324
  def __str__(self):
287
325
  return f"Order #{self.pk} - {self.user} - {self.recipe}"
@@ -1,5 +1,5 @@
1
- # from gunicorn.http.wsgi import log
2
1
  import logging
2
+ import requests
3
3
 
4
4
  # from django.core.mail import send_mail
5
5
  from django_tasks import task
@@ -109,6 +109,36 @@ def notify_order_submitted(order_id):
109
109
  logging.info(f"Order confirmation email sent to {user.email} for order {order.id}")
110
110
 
111
111
 
112
+ @task(priority=1, queue_name="emails")
113
+ def send_gotify_notification(title, message, priority=5):
114
+ from .models import Setting
115
+
116
+ config = Setting.get_solo()
117
+ url = config.gotify_url
118
+ token = config.gotify_token
119
+
120
+ if not url or not token:
121
+ logging.debug("Gotify URL or Token not configured. Skipping notification.")
122
+ return False
123
+
124
+ try:
125
+ response = requests.post(
126
+ f"{url.rstrip('/')}/message?token={token}",
127
+ json={
128
+ "title": title,
129
+ "message": message,
130
+ "priority": priority,
131
+ },
132
+ timeout=10,
133
+ )
134
+ response.raise_for_status()
135
+ logging.info(f"Gotify notification sent: {title}")
136
+ return True
137
+ except Exception as e:
138
+ logging.error(f"Failed to send Gotify notification: {e}")
139
+ return False
140
+
141
+
112
142
  def send_emails(recipe_id, emails):
113
143
  from .models import Recipe
114
144
 
@@ -30,9 +30,6 @@
30
30
  <h6 class="max">{% block admin_title %}{% trans "Sandwitches Admin" %}{% endblock %}</h6>
31
31
  </a>
32
32
  <div class="max"></div>
33
- <button class="circle transparent" onclick="toggleMode()">
34
- <i>dark_mode</i>
35
- </button>
36
33
 
37
34
  {% if user.avatar %}
38
35
  <img src="{{ user.avatar.url }}" class="circle" data-ui="#user-menu">
@@ -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 %}
@@ -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>
@@ -84,6 +84,7 @@
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>
@@ -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>
@@ -34,6 +34,7 @@ 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"),
37
38
  path("orders/<int:pk>/", views.user_order_detail, name="user_order_detail"),
38
39
  path("community/", views.community, name="community"),
39
40
  path("admin/", admin.site.urls),
@@ -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
@@ -898,6 +900,32 @@ def user_profile(request):
898
900
  )
899
901
 
900
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
+
919
+ return render(
920
+ request,
921
+ "settings.html",
922
+ {
923
+ "form": form,
924
+ "version": sandwitches_version,
925
+ },
926
+ )
927
+
928
+
901
929
  @login_required
902
930
  def user_order_detail(request, pk):
903
931
  order = get_object_or_404(Order, pk=pk, user=request.user)
@@ -1,26 +0,0 @@
1
- {% load i18n %}
2
- <dialog id="language-menu">
3
- <form method="post" action="{% url 'set_language' %}">
4
- {% csrf_token %}
5
- <h5 class="center-align">{% trans "Select Language" %}</h5>
6
- <div class="grid center-align">
7
- {% get_current_language as LANGUAGE_CODE %}
8
- {% get_available_languages as LANGUAGES %}
9
- {% for code, name in LANGUAGES %}
10
- <div class="s6">
11
- <button type="submit" name="language" value="{{ code }}" class="circle large {% if code == LANGUAGE_CODE %}primary{% else %}surface{% endif %}">
12
- <span style="font-size: 2rem;">
13
- {% if code == 'en' %}🇬🇧{% elif code == 'nl' %}🇳🇱{% else %}🌐{% endif %}
14
- </span>
15
- </button>
16
- <div class="small-text">{{ name }}</div>
17
- </div>
18
- {% endfor %}
19
- </div>
20
- <input type="hidden" name="next" value="{{ request.get_full_path }}" />
21
- </form>
22
- <div class="space"></div>
23
- <nav class="center-align">
24
- <button class="transparent link" data-ui="#language-menu">{% trans "Close" %}</button>
25
- </nav>
26
- </dialog>
File without changes