From 49b65ded7b7dfd96faaac7b03b2d5cbc557a8456 Mon Sep 17 00:00:00 2001 From: "A. Ottr" Date: Sun, 22 Feb 2026 00:14:51 +0100 Subject: [PATCH] feat: move ticket attachments storage Signed-off-by: A. Ottr --- .gitignore | 1 + compose.yaml | 22 +++ docs/docker_compose_deployment.md | 4 +- paw/locale/de/LC_MESSAGES/django.mo | Bin 4794 -> 5514 bytes paw/locale/de/LC_MESSAGES/django.po | 148 +++++++++++++-------- paw/locale/en/LC_MESSAGES/django.po | 144 ++++++++++++-------- paw/locale/fr/LC_MESSAGES/django.mo | Bin 5914 -> 6626 bytes paw/locale/fr/LC_MESSAGES/django.po | 148 +++++++++++++-------- paw/locale/nl/LC_MESSAGES/django.mo | Bin 5776 -> 6430 bytes paw/locale/nl/LC_MESSAGES/django.po | 147 ++++++++++++-------- paw/settings.py | 5 + paw/static/css/paw.css | 62 +++++++++ paw/templates/404.html | 42 ++++++ paw/templates/500.html | 42 ++++++ paw/templates/ticketing/ticket_detail.html | 2 +- ticketing/models.py | 15 ++- ticketing/storage.py | 28 ++++ ticketing/urls.py | 3 +- ticketing/views.py | 44 +++++- 19 files changed, 618 insertions(+), 239 deletions(-) create mode 100644 compose.yaml create mode 100644 paw/templates/404.html create mode 100644 paw/templates/500.html create mode 100644 ticketing/storage.py diff --git a/.gitignore b/.gitignore index 76a9c12..bf017a1 100644 --- a/.gitignore +++ b/.gitignore @@ -156,6 +156,7 @@ node_modules static/* media/* +secure_media/* .DS_Store !paw/locale/**/django.mo .vscode \ No newline at end of file diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..76db2ff --- /dev/null +++ b/compose.yaml @@ -0,0 +1,22 @@ +version: "3.8" +services: + paw: + build: . + container_name: paw-ticket-system + command: gunicorn paw.wsgi:application --bind 0.0.0.0:8000 + restart: unless-stopped + ports: + - "127.0.0.1:8000:8000" + volumes: + - ./data:/usr/src/app/data + - media:/usr/src/app/media + - secure_media:/usr/src/app/secure_media + environment: + - DATABASE_ENGINE=sqlite3 + - DEBUG=true + - ALLOWED_HOSTS=localhost,127.0.0.1 + - SECRET_KEY=your-secret-key + +volumes: + media: + secure_media: diff --git a/docs/docker_compose_deployment.md b/docs/docker_compose_deployment.md index 15325f1..03d0eeb 100644 --- a/docs/docker_compose_deployment.md +++ b/docs/docker_compose_deployment.md @@ -21,6 +21,7 @@ services: volumes: - db:/usr/src/app/data - media:/usr/src/app/media + - secure_media:/usr/src/app/secure_media # storage for ticket attachments environment: - DATABASE_ENGINE=sqlite3 - DEBUG=true @@ -41,6 +42,7 @@ volumes: - /opt/paw/data:/usr/src/app/data - /opt/paw/media:/usr/src/app/media - /opt/paw/static:/usr/src/app/static + - /opt/paw/secure_media:/usr/src/app/secure_media # storage for ticket attachments ``` Now you write directives in your config to host these files, the following snipped shows an example nginx config: @@ -59,7 +61,7 @@ server { listen [::]:80; location /media/ { - # media files, uploaded by us + # media files, uploaded by us (profile pictures, etc.) alias /opt/paw/media/; # ending slash is required } diff --git a/paw/locale/de/LC_MESSAGES/django.mo b/paw/locale/de/LC_MESSAGES/django.mo index 21f99f9e412aee4bd51d4b96d62a8cdce6bba762..41aa5c3baafdef09afb22902631cdafe36b8019d 100644 GIT binary patch delta 2619 zcmdm`+NE88Pl#nI0|Ns?6axc;3v@(2?xZ$MyQ4k4h9AR1_p*n91INX3=9m5 zIT#pt85kH=b1*OngB-}gz@Wy!z;G5~FvAxp{U2&R7bgP)3j+g#7$?L7(wrdu^$ZM( zoDc)`IUznW=VV~8V_;wi;e;4GnG@o}<(v=)ZG)=c3*{e$^3Q+`WMH@gwdfI4{5dDY z=kGZoKL5=L@hB@71A{0isQI`c0xDdPpws1oSY*Wo2|7n8KZJ{cfti7UAsVVKo{ND& zmVtpG2P)qM6`#lj@z6Xj1_nU}28Lx^kdWKK1qp$ZP<7`(;SUOu+guPIK7cypJ(T~0 zi-AE36qno#3?d8+3`*P#4B`w74CdSn4EziX4F22@eev834AKk?40+rTk92WEESwDG zFX4t*ycVi{6IA>lH$y#x4g&+jDQ*UaDh39I@7$0?Q^W)DK_?F+t|sz;6AQz1D7}aW z5>jh@Hnd~%nMfk6Y53-};D5aowhpvDi;pwAD95*vO{;$dL$htje95C>-S zL!zz>s;`zG66bCFkf@lz4@nD)_#yVKtLKLV?OuL}k1z2cl155|AP%mA z@;ikf7WWH59J~aoeytD#g9HNu!x15nL+TkA?g}w5NPUvA#kY|!T?DJppXTLgK~ox0|P_7CIbV5F-Qc8of#mZ5e(&nC{XbV zqOBMh82mtGIf%o+z+ew5-$CU9lrPV~z@W##z~BVsgF*pRUVw^a0|rO}3}k?K78J4| z`@u>0aVO_YC&I+ z1QHH_)|!q;VlbvUjKRRb;LX6m;LgCnpv3@5D=naSCyCB>7C zIdw%n^GZ^S@)C0tf>VpiQi~K^i;D7#CdYH`=Sfdh@XIezaLX^vOW7>V#mQJ7oL^K_ zsiRPys*s#tnv;^JUZRkenU|tal98%VkeHsTP?=wr3Q^?IPOHI*HNJ-5}Edep|i@?T&2KgxDW~OJ9C?usSWacI37Zs%@ zm*_EooIF{BdxK|qD#(Unh)a@6OB9Opb5lz)K-QI~=9MUv7v<-rE9B=X`=jou8LilA5QGl9{TIo?2X*oKcdhfNTUPC{y$F6kIZ4;^n19 zDX9v%hqtBYWF}{%7L})F7N;tdr4|(@XXGcP=IJP;r{)~qRt$1LeoAT)*zC|CA0CCw z;u3|l#GK;f486%e1b613J1DUx&ams(n)$B=e-PZ2{|ejWn=)zNcy delta 1894 zcmeCu-lbZ9Pl#nI0|Ns?2m=Fy3!d1A_uP0|N&G1A`(v0|Pe$ z1A`Vj0|Q9Pj2)uhhMj?dkAZ<9lAVEpje&t7gPnnahk=11pPhk0jDdlnhMj>yh=GA& zGF1OEb_NDc1_p*r>p%z_+8gK_{;7h2659|yK0t^fczt|ZV*cliY zxH%XYco`TNggF=(gh3AEU|>*VU|=xi02|B@1EteBAPz6$U|?WjU|^`@fOw!8tiPUt zp_2n*;B*d%kLGhQFxY`Y0&4JY4u}u=IUzojy z3BhnEoxlkRp-hnedIkoDGEPVk)q(^V7#JpSGB7YRFfdGqDxA&9z#z-Oz_0=;e+(*q z7HZ)wP6h@+1_p*loRARu0(B587sMhiE{FrgxgZXa;evQb6%_tb3=9k=Tnr2%3=9nZ zTnr523=9k@Tnr5Sptyr7?BQZykY-?Dm<=^xI~T;F15o}&s0Fv6>hD9v-*GW8=rAxa zeCL9s1r=^c$XRnkqQIG(p&pb77(BTlaTdZ2adA91#HBgh3=FXh3=E~*5FcFThJ?%w zZUzP>1_p*Z+zbp(j0_C-q570~85l$v7#Q?OL?3^oi53|V{-2Q07WgBY*{DsX@gV&Pdnhy$)b#qUE6e8b1Upvb_$@P&_o z!GnQ;L5v?_a6CUG>dN>b4(x-{Gx#A6S;P+s!BzZ_5LnL-ad7<(sKVp?5Q{HDHQeBb z_~Zdp!&iPtHv0$V3kg6BmKK2cL|K4=L79Pp!9W0FPKW>`Dxw7-4l0E5s|6qqo+toL z1N96m1RyS12N7V{D*$ofVW`400t^fi3=9m91t1RiBf!8Q$-uxMAP5N|EkTGyrh*I% z3JeSk-hz;*$$`>!f)IyJ5QI2nh9D%$7YTx*yqi}5Qq;7dQidxRYIUbN|k|uA((-I z!5vgLFhFt%NF5COLK!Ly3=ED8+E5+?1A`@$2@(R;T\n" "Language-Team: LANGUAGE \n" @@ -18,8 +18,8 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: core/forms.py:27 core/forms.py:35 paw/templates/core/account_finish.html:33 -#: paw/templates/core/login.html:25 paw/templates/core/register.html:24 +#: core/forms.py:27 core/forms.py:35 paw/templates/core/account_finish.html:32 +#: paw/templates/core/login.html:23 paw/templates/core/register.html:23 msgid "Username" msgstr "Benutzername" @@ -58,22 +58,59 @@ msgstr "" msgid "Google SSO User" msgstr "" -#: paw/settings.py:135 +#: paw/settings.py:140 msgid "English" msgstr "English" -#: paw/settings.py:136 +#: paw/settings.py:141 msgid "French" msgstr "Français" -#: paw/settings.py:137 +#: paw/settings.py:142 msgid "German" msgstr "Deutsch" -#: paw/settings.py:138 +#: paw/settings.py:143 msgid "Dutch" msgstr "Nederlands" +#: paw/templates/404.html:9 +msgid "Page Not Found" +msgstr "Seite nicht gefunden" + +#: paw/templates/404.html:11 +msgid "" +"Sorry, we couldn't find the page you're looking for. The page may have been " +"moved, deleted, or the URL might be incorrect." +msgstr "" +"Entschuldigung, wir konnten die gesuchte Seite nicht finden. Die Seite wurde " +"möglicherweise verschoben, gelöscht oder die URL ist falsch." + +#: paw/templates/404.html:20 paw/templates/500.html:20 +msgid "Go Home" +msgstr "Zur Startseite" + +#: paw/templates/404.html:28 paw/templates/500.html:28 +msgid "Go to Tickets" +msgstr "Zu den Tickets" + +#: paw/templates/404.html:35 paw/templates/500.html:35 +#: paw/templates/dashboard_base.html:35 +#: paw/templates/ticketing/create_ticket.html:51 +msgid "Create Ticket" +msgstr "Ticket erstellen" + +#: paw/templates/500.html:9 +msgid "Internal Server Error" +msgstr "Interner Serverfehler" + +#: paw/templates/500.html:11 +msgid "" +"We're sorry, but something went wrong on our end. Please try again later." +msgstr "" +"Entschuldigung, auf unserer Seite ist etwas schiefgelaufen. Bitte versuchen " +"Sie es später erneut." + #: paw/templates/base.html:20 paw/templates/dashboard_base.html:41 msgid "Tickets" msgstr "Tickets" @@ -99,12 +136,12 @@ msgstr "Account einrichten" msgid "Done" msgstr "Fertig" -#: paw/templates/core/account_finish.html:38 -#: paw/templates/core/settings.html:55 +#: paw/templates/core/account_finish.html:36 +#: paw/templates/core/settings.html:51 msgid "Save" msgstr "Speichern" -#: paw/templates/core/login.html:7 paw/templates/core/login.html:40 +#: paw/templates/core/login.html:7 paw/templates/core/login.html:32 msgid "Log In" msgstr "Anmelden" @@ -112,27 +149,27 @@ msgstr "Anmelden" msgid "Register Account" msgstr "Account erstellen" -#: paw/templates/core/login.html:31 paw/templates/core/register.html:36 +#: paw/templates/core/login.html:27 paw/templates/core/register.html:32 msgid "Password" msgstr "Passwort" -#: paw/templates/core/login.html:36 +#: paw/templates/core/login.html:29 msgid "Password Reset" msgstr "Passwort zurücksetzen" -#: paw/templates/core/login.html:47 +#: paw/templates/core/login.html:39 msgid "Log in with Google" msgstr "Mit Google anmelden" -#: paw/templates/core/register.html:30 +#: paw/templates/core/register.html:28 msgid "Email Address" msgstr "Mail Adresse" -#: paw/templates/core/register.html:42 +#: paw/templates/core/register.html:36 msgid "Confirm Password" msgstr "Passwort bestätigen" -#: paw/templates/core/register.html:47 +#: paw/templates/core/register.html:40 msgid "Register" msgstr "Account erstellen" @@ -140,36 +177,31 @@ msgstr "Account erstellen" msgid "Settings" msgstr "Einstellungen" -#: paw/templates/core/settings.html:12 +#: paw/templates/core/settings.html:11 msgid "Mail Address" msgstr "Mail Adresse" -#: paw/templates/core/settings.html:23 +#: paw/templates/core/settings.html:19 msgid "Language" msgstr "Sprache" -#: paw/templates/core/settings.html:30 +#: paw/templates/core/settings.html:25 msgid "Use Darkmode" msgstr "Darkmode benutzen" -#: paw/templates/core/settings.html:37 +#: paw/templates/core/settings.html:31 msgid "Receive Email Notifications" msgstr "Mail-Benachrichtigungen erhalten" -#: paw/templates/core/settings.html:44 +#: paw/templates/core/settings.html:37 msgid "Profile Picture" msgstr "Profilbild" -#: paw/templates/core/settings.html:49 -#: paw/templates/ticketing/ticket_detail.html:152 +#: paw/templates/core/settings.html:42 +#: paw/templates/ticketing/ticket_detail.html:154 msgid "Contact" msgstr "Kontakt" -#: paw/templates/dashboard_base.html:35 -#: paw/templates/ticketing/create_ticket.html:52 -msgid "Create Ticket" -msgstr "Ticket erstellen" - #: paw/templates/dashboard_base.html:47 #: paw/templates/ticketing/tickets_history.html:5 msgid "History" @@ -184,31 +216,31 @@ msgid "Logout" msgstr "Abmelden" #: paw/templates/partials/assigned_to.html:17 -#: paw/templates/ticketing/ticket_detail.html:201 +#: paw/templates/ticketing/ticket_detail.html:203 msgid "Unassigned" msgstr "Nicht zugewiesen" -#: paw/templates/partials/ticket_priority_badge.html:4 ticketing/models.py:45 +#: paw/templates/partials/ticket_priority_badge.html:4 ticketing/models.py:47 msgid "Low" msgstr "Niedrig" -#: paw/templates/partials/ticket_priority_badge.html:6 ticketing/models.py:46 +#: paw/templates/partials/ticket_priority_badge.html:6 ticketing/models.py:48 msgid "Medium" msgstr "Mittel" -#: paw/templates/partials/ticket_priority_badge.html:8 ticketing/models.py:47 +#: paw/templates/partials/ticket_priority_badge.html:8 ticketing/models.py:49 msgid "High" msgstr "Hoch" -#: paw/templates/partials/ticket_status_badge.html:4 ticketing/models.py:40 +#: paw/templates/partials/ticket_status_badge.html:4 ticketing/models.py:42 msgid "Open" msgstr "Offen" -#: paw/templates/partials/ticket_status_badge.html:6 ticketing/models.py:41 +#: paw/templates/partials/ticket_status_badge.html:6 ticketing/models.py:43 msgid "In Progress" msgstr "In Bearbeitung" -#: paw/templates/partials/ticket_status_badge.html:8 ticketing/models.py:42 +#: paw/templates/partials/ticket_status_badge.html:8 ticketing/models.py:44 msgid "Closed" msgstr "Geschlossen" @@ -216,29 +248,29 @@ msgstr "Geschlossen" msgid "Create a new ticket" msgstr "Neues Ticket erstellen" -#: paw/templates/ticketing/create_ticket.html:12 -#: paw/templates/ticketing/ticket_detail.html:165 +#: paw/templates/ticketing/create_ticket.html:11 +#: paw/templates/ticketing/ticket_detail.html:167 #: paw/templates/ticketing/tickets.html:19 #: paw/templates/ticketing/tickets_history.html:19 msgid "Category" msgstr "Kategorie" -#: paw/templates/ticketing/create_ticket.html:19 +#: paw/templates/ticketing/create_ticket.html:15 #: paw/templates/ticketing/tickets.html:17 #: paw/templates/ticketing/tickets_history.html:17 ticketing/forms.py:73 msgid "Title" msgstr "Titel" -#: paw/templates/ticketing/create_ticket.html:26 ticketing/forms.py:74 +#: paw/templates/ticketing/create_ticket.html:23 ticketing/forms.py:74 msgid "Description" msgstr "Beschreibung" -#: paw/templates/ticketing/create_ticket.html:34 +#: paw/templates/ticketing/create_ticket.html:31 #: paw/templates/ticketing/ticket_detail.html:9 msgid "Attachments" msgstr "Anhänge" -#: paw/templates/ticketing/create_ticket.html:43 +#: paw/templates/ticketing/create_ticket.html:40 msgid "Create as follow-up to a closed ticket" msgstr "Als Nachfolge-Ticket zu einem geschlossenen Ticket erstellen" @@ -266,63 +298,63 @@ msgstr "Intern" msgid "Apply Template" msgstr "Vorlage anwenden" -#: paw/templates/ticketing/ticket_detail.html:96 +#: paw/templates/ticketing/ticket_detail.html:98 msgid "Add Comment" msgstr "Kommentar hinzufügen" -#: paw/templates/ticketing/ticket_detail.html:98 +#: paw/templates/ticketing/ticket_detail.html:100 msgid "Close Ticket" msgstr "Ticket schliessen" -#: paw/templates/ticketing/ticket_detail.html:107 +#: paw/templates/ticketing/ticket_detail.html:109 msgid "Make this an internal comment" msgstr "Mache dies einen internen Kommentar" -#: paw/templates/ticketing/ticket_detail.html:116 +#: paw/templates/ticketing/ticket_detail.html:118 msgid "Ticket has been closed" msgstr "Das Ticket wurde geschlossen" -#: paw/templates/ticketing/ticket_detail.html:121 +#: paw/templates/ticketing/ticket_detail.html:123 msgid "Re-Open Ticket" msgstr "Ticket wieder öffnen" -#: paw/templates/ticketing/ticket_detail.html:132 +#: paw/templates/ticketing/ticket_detail.html:134 msgid "Created by" msgstr "Erstellt von" -#: paw/templates/ticketing/ticket_detail.html:145 +#: paw/templates/ticketing/ticket_detail.html:147 msgid "Created on" msgstr "Erstellt am" -#: paw/templates/ticketing/ticket_detail.html:149 +#: paw/templates/ticketing/ticket_detail.html:151 msgid "Last updated" msgstr "Zuletzt aktualisiert" -#: paw/templates/ticketing/ticket_detail.html:171 +#: paw/templates/ticketing/ticket_detail.html:173 #: paw/templates/ticketing/tickets.html:42 #: paw/templates/ticketing/tickets_history.html:42 ticketing/forms.py:81 -#: ticketing/forms.py:104 ticketing/models.py:165 ticketing/models.py:178 +#: ticketing/forms.py:104 ticketing/models.py:167 ticketing/models.py:180 msgid "General" msgstr "Allgemein" -#: paw/templates/ticketing/ticket_detail.html:177 +#: paw/templates/ticketing/ticket_detail.html:179 msgid "Assign to new category" msgstr "Kategorie zuweisen" -#: paw/templates/ticketing/ticket_detail.html:180 -#: paw/templates/ticketing/ticket_detail.html:210 +#: paw/templates/ticketing/ticket_detail.html:182 +#: paw/templates/ticketing/ticket_detail.html:212 msgid "Assign" msgstr "Zuweisen" -#: paw/templates/ticketing/ticket_detail.html:185 +#: paw/templates/ticketing/ticket_detail.html:187 msgid "Assignees" msgstr "Bearbeiter" -#: paw/templates/ticketing/ticket_detail.html:192 +#: paw/templates/ticketing/ticket_detail.html:194 msgid "Assign to me" msgstr "Weise mir zu" -#: paw/templates/ticketing/ticket_detail.html:207 +#: paw/templates/ticketing/ticket_detail.html:209 msgid "Assign to new team" msgstr "Team zuweisen" @@ -410,7 +442,7 @@ msgstr "Kein Nachfolge-Ticket" msgid "No Team" msgstr "Kein Team" -#: ticketing/models.py:31 +#: ticketing/models.py:33 msgid "" "If a team is selected, new tickets will automatically assigned to this team." msgstr "" diff --git a/paw/locale/en/LC_MESSAGES/django.po b/paw/locale/en/LC_MESSAGES/django.po index 438748d..8b3388c 100644 --- a/paw/locale/en/LC_MESSAGES/django.po +++ b/paw/locale/en/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-08-09 01:16+0000\n" +"POT-Creation-Date: 2026-02-22 00:57+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,8 +18,8 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: core/forms.py:27 core/forms.py:35 paw/templates/core/account_finish.html:33 -#: paw/templates/core/login.html:25 paw/templates/core/register.html:24 +#: core/forms.py:27 core/forms.py:35 paw/templates/core/account_finish.html:32 +#: paw/templates/core/login.html:23 paw/templates/core/register.html:23 msgid "Username" msgstr "" @@ -57,22 +57,55 @@ msgstr "" msgid "Google SSO User" msgstr "" -#: paw/settings.py:135 +#: paw/settings.py:140 msgid "English" msgstr "English" -#: paw/settings.py:136 +#: paw/settings.py:141 msgid "French" msgstr "Français" -#: paw/settings.py:137 +#: paw/settings.py:142 msgid "German" msgstr "Deutsch" -#: paw/settings.py:138 +#: paw/settings.py:143 msgid "Dutch" msgstr "Nederlands" +#: paw/templates/404.html:9 +msgid "Page Not Found" +msgstr "" + +#: paw/templates/404.html:11 +msgid "" +"Sorry, we couldn't find the page you're looking for. The page may have been " +"moved, deleted, or the URL might be incorrect." +msgstr "" + +#: paw/templates/404.html:20 paw/templates/500.html:20 +msgid "Go Home" +msgstr "" + +#: paw/templates/404.html:28 paw/templates/500.html:28 +msgid "Go to Tickets" +msgstr "" + +#: paw/templates/404.html:35 paw/templates/500.html:35 +#: paw/templates/dashboard_base.html:35 +#: paw/templates/ticketing/create_ticket.html:51 +msgid "Create Ticket" +msgstr "" + +#: paw/templates/500.html:9 +msgid "Internal Server Error" +msgstr "" + +#: paw/templates/500.html:11 +msgid "" +"We're sorry, but something went wrong on our end. Please try again later." +msgstr "" + #: paw/templates/base.html:20 paw/templates/dashboard_base.html:41 msgid "Tickets" msgstr "" @@ -98,12 +131,12 @@ msgstr "" msgid "Done" msgstr "" -#: paw/templates/core/account_finish.html:38 -#: paw/templates/core/settings.html:55 +#: paw/templates/core/account_finish.html:36 +#: paw/templates/core/settings.html:51 msgid "Save" msgstr "" -#: paw/templates/core/login.html:7 paw/templates/core/login.html:40 +#: paw/templates/core/login.html:7 paw/templates/core/login.html:32 msgid "Log In" msgstr "" @@ -111,27 +144,27 @@ msgstr "" msgid "Register Account" msgstr "" -#: paw/templates/core/login.html:31 paw/templates/core/register.html:36 +#: paw/templates/core/login.html:27 paw/templates/core/register.html:32 msgid "Password" msgstr "" -#: paw/templates/core/login.html:36 +#: paw/templates/core/login.html:29 msgid "Password Reset" msgstr "" -#: paw/templates/core/login.html:47 +#: paw/templates/core/login.html:39 msgid "Log in with Google" msgstr "" -#: paw/templates/core/register.html:30 +#: paw/templates/core/register.html:28 msgid "Email Address" msgstr "" -#: paw/templates/core/register.html:42 +#: paw/templates/core/register.html:36 msgid "Confirm Password" msgstr "" -#: paw/templates/core/register.html:47 +#: paw/templates/core/register.html:40 msgid "Register" msgstr "" @@ -139,36 +172,31 @@ msgstr "" msgid "Settings" msgstr "Settings" -#: paw/templates/core/settings.html:12 +#: paw/templates/core/settings.html:11 msgid "Mail Address" msgstr "" -#: paw/templates/core/settings.html:23 +#: paw/templates/core/settings.html:19 msgid "Language" msgstr "" -#: paw/templates/core/settings.html:30 +#: paw/templates/core/settings.html:25 msgid "Use Darkmode" msgstr "" -#: paw/templates/core/settings.html:37 +#: paw/templates/core/settings.html:31 msgid "Receive Email Notifications" msgstr "" -#: paw/templates/core/settings.html:44 +#: paw/templates/core/settings.html:37 msgid "Profile Picture" msgstr "" -#: paw/templates/core/settings.html:49 -#: paw/templates/ticketing/ticket_detail.html:152 +#: paw/templates/core/settings.html:42 +#: paw/templates/ticketing/ticket_detail.html:154 msgid "Contact" msgstr "" -#: paw/templates/dashboard_base.html:35 -#: paw/templates/ticketing/create_ticket.html:52 -msgid "Create Ticket" -msgstr "" - #: paw/templates/dashboard_base.html:47 #: paw/templates/ticketing/tickets_history.html:5 msgid "History" @@ -183,31 +211,31 @@ msgid "Logout" msgstr "" #: paw/templates/partials/assigned_to.html:17 -#: paw/templates/ticketing/ticket_detail.html:201 +#: paw/templates/ticketing/ticket_detail.html:203 msgid "Unassigned" msgstr "" -#: paw/templates/partials/ticket_priority_badge.html:4 ticketing/models.py:45 +#: paw/templates/partials/ticket_priority_badge.html:4 ticketing/models.py:47 msgid "Low" msgstr "" -#: paw/templates/partials/ticket_priority_badge.html:6 ticketing/models.py:46 +#: paw/templates/partials/ticket_priority_badge.html:6 ticketing/models.py:48 msgid "Medium" msgstr "" -#: paw/templates/partials/ticket_priority_badge.html:8 ticketing/models.py:47 +#: paw/templates/partials/ticket_priority_badge.html:8 ticketing/models.py:49 msgid "High" msgstr "" -#: paw/templates/partials/ticket_status_badge.html:4 ticketing/models.py:40 +#: paw/templates/partials/ticket_status_badge.html:4 ticketing/models.py:42 msgid "Open" msgstr "" -#: paw/templates/partials/ticket_status_badge.html:6 ticketing/models.py:41 +#: paw/templates/partials/ticket_status_badge.html:6 ticketing/models.py:43 msgid "In Progress" msgstr "" -#: paw/templates/partials/ticket_status_badge.html:8 ticketing/models.py:42 +#: paw/templates/partials/ticket_status_badge.html:8 ticketing/models.py:44 msgid "Closed" msgstr "" @@ -215,29 +243,29 @@ msgstr "" msgid "Create a new ticket" msgstr "" -#: paw/templates/ticketing/create_ticket.html:12 -#: paw/templates/ticketing/ticket_detail.html:165 +#: paw/templates/ticketing/create_ticket.html:11 +#: paw/templates/ticketing/ticket_detail.html:167 #: paw/templates/ticketing/tickets.html:19 #: paw/templates/ticketing/tickets_history.html:19 msgid "Category" msgstr "" -#: paw/templates/ticketing/create_ticket.html:19 +#: paw/templates/ticketing/create_ticket.html:15 #: paw/templates/ticketing/tickets.html:17 #: paw/templates/ticketing/tickets_history.html:17 ticketing/forms.py:73 msgid "Title" msgstr "" -#: paw/templates/ticketing/create_ticket.html:26 ticketing/forms.py:74 +#: paw/templates/ticketing/create_ticket.html:23 ticketing/forms.py:74 msgid "Description" msgstr "" -#: paw/templates/ticketing/create_ticket.html:34 +#: paw/templates/ticketing/create_ticket.html:31 #: paw/templates/ticketing/ticket_detail.html:9 msgid "Attachments" msgstr "" -#: paw/templates/ticketing/create_ticket.html:43 +#: paw/templates/ticketing/create_ticket.html:40 msgid "Create as follow-up to a closed ticket" msgstr "" @@ -265,63 +293,63 @@ msgstr "" msgid "Apply Template" msgstr "" -#: paw/templates/ticketing/ticket_detail.html:96 +#: paw/templates/ticketing/ticket_detail.html:98 msgid "Add Comment" msgstr "" -#: paw/templates/ticketing/ticket_detail.html:98 +#: paw/templates/ticketing/ticket_detail.html:100 msgid "Close Ticket" msgstr "" -#: paw/templates/ticketing/ticket_detail.html:107 +#: paw/templates/ticketing/ticket_detail.html:109 msgid "Make this an internal comment" msgstr "" -#: paw/templates/ticketing/ticket_detail.html:116 +#: paw/templates/ticketing/ticket_detail.html:118 msgid "Ticket has been closed" msgstr "" -#: paw/templates/ticketing/ticket_detail.html:121 +#: paw/templates/ticketing/ticket_detail.html:123 msgid "Re-Open Ticket" msgstr "" -#: paw/templates/ticketing/ticket_detail.html:132 +#: paw/templates/ticketing/ticket_detail.html:134 msgid "Created by" msgstr "" -#: paw/templates/ticketing/ticket_detail.html:145 +#: paw/templates/ticketing/ticket_detail.html:147 msgid "Created on" msgstr "" -#: paw/templates/ticketing/ticket_detail.html:149 +#: paw/templates/ticketing/ticket_detail.html:151 msgid "Last updated" msgstr "" -#: paw/templates/ticketing/ticket_detail.html:171 +#: paw/templates/ticketing/ticket_detail.html:173 #: paw/templates/ticketing/tickets.html:42 #: paw/templates/ticketing/tickets_history.html:42 ticketing/forms.py:81 -#: ticketing/forms.py:104 ticketing/models.py:165 ticketing/models.py:178 +#: ticketing/forms.py:104 ticketing/models.py:167 ticketing/models.py:180 msgid "General" msgstr "" -#: paw/templates/ticketing/ticket_detail.html:177 +#: paw/templates/ticketing/ticket_detail.html:179 msgid "Assign to new category" msgstr "" -#: paw/templates/ticketing/ticket_detail.html:180 -#: paw/templates/ticketing/ticket_detail.html:210 +#: paw/templates/ticketing/ticket_detail.html:182 +#: paw/templates/ticketing/ticket_detail.html:212 msgid "Assign" msgstr "" -#: paw/templates/ticketing/ticket_detail.html:185 +#: paw/templates/ticketing/ticket_detail.html:187 msgid "Assignees" msgstr "" -#: paw/templates/ticketing/ticket_detail.html:192 +#: paw/templates/ticketing/ticket_detail.html:194 msgid "Assign to me" msgstr "" -#: paw/templates/ticketing/ticket_detail.html:207 +#: paw/templates/ticketing/ticket_detail.html:209 msgid "Assign to new team" msgstr "" @@ -409,7 +437,7 @@ msgstr "" msgid "No Team" msgstr "" -#: ticketing/models.py:31 +#: ticketing/models.py:33 msgid "" "If a team is selected, new tickets will automatically assigned to this team." msgstr "" diff --git a/paw/locale/fr/LC_MESSAGES/django.mo b/paw/locale/fr/LC_MESSAGES/django.mo index 22959d6fb233d07b1c780606f5cdfeb7140f47e6..38370ef5428bc64d2e98cc379e89628a04191c44 100644 GIT binary patch delta 2827 zcmbQG_sF>Zo)F7a1_lO(6b1$c83qOh8)gQEP6h^s7&ee7149D`0|N&G14APR0|Pe$ z149=F0|P4q1H&{9i26Ak3=CQf3=BIt7#MUI7#N;#FfiycFfa&mGBEHlFfiD2GBALQ z^5bM+;9+232<2p85My9qNaAE*5Mp4cXQU+$|z#zcD!0?unfq|WYf#Dw~0|PGu0|Ofu1A{OF1A_<`1A`g^ z1A_(^#C(4+UC+P}#RYLdDi;F-3j+f~As57lWn2&gYPcW6z25cBl885sB(7#M8185mgV85kIRp$dYy85n#R7#O0s zAr4y34GEFW+z_Ad+;fjHEOhk>CU6epfM5EqB>Ffa%*FfhdNK!UP>2NEKUPy^ba{K-5JAJ5={ zIBYqTzk!E=L7RbrVLuNf4ZP-IV31;9V36cxU=U$oU@+ljU=U|uVDRLHgg_!MLp?Z= z6!J1KNHZ`nH1I+UoWl!oz*1gFV%Y{2Kg$bo&^2C&18zgbU-3c`-)CM1hAIXI1_eHd z4`=g1^e^IrcyK2lBx(=xLDIl+zIq7b5+5Xp?(#t#@QROtA(nxG;WHn^!a#lo22TbC zhA@6ekS^e7U|?ckU|7P>z~IQpz_6Sj5>+n+AQpZWfLO#O$iSe=z`!6N2ni80K?Vjh zP;L`sU@&K3V3=Jm2nm|Yf)E2<2ts`JNsxg-g@J*AQHX&-k%56hO$g#Z4=5cf1W8Ou zLXe;=fQmN=K|Ite1POr|PbKltdtj&`bmplpav|coB$&=^_jaaSRL$MIsP~T!a|Ja0{yL zi3r4pZ$uy=z$Oae^NNBkWKb7nV31&7U@#N~Ijo+6!Alepr%|F1pTvnmvPrHeBo4bp zAwHi7m7gmLao8GBNYot`g;;b0Y#;-}eNjl7c`phH(J!KqsACm_3x%MxrZ^<-9mF9H^Am^YixP)~WHD5{ zS{&lQb|`<2I0J(z0|UbfaR!E71_p+E;tUM+pt3nf0%Ab11jK+I35d_9NI)!_B>}N$ zg#;x3ZjpdQ(HRK_hDjiUBp@MBD9ON(z`(#zAqfeAo05=_y(h`Qz{tSBUOO4sAd9{R16H@qS%=Ml7B%;5*Z-AHwKkp zAOQwQa3(V_Ft{>6O1?0tIEVt3c%YgRl-)u>0t}D@3t~or2oMI@9|FoQ3=9kgAOR=_ zRmE0NHi(K~U|?`yfD}rgBHM|9fkBypfkBgjfguu3usf*2rK&KsnG0a7A@Dk@O* z3=%SAfFwK+8-zi13CMm1BL+yy2MNM3C=cs{Dkla81|8Qk*~O7ay#GLy4YONuAY|3OD$4xEh@?{nyk*fpC>(4 z!7sl=!7aZuFJ<#%?rO&R{Gy^t9fk5#h2;FwoRmEE5{0zPycC6!j8uh!#Pn2!%KTFG zqEv;P{QT_9ymW=M{31Pt5SV0cVx>YxVp*y}Qfg|RLT-LpYKo3RN@`AO35bzj1U4o# z$VVYJGd-h3At_ZMGcP&6s3EW)6d6PEKl(LSku!LJ2gWW(qBpbS)}MEiF>W1cz6uLQ1JZ zF(|}JizeF&zvmAC`9CwSq$t0%EHNo(vzEwh#(I~-D~t1U4zJWv$jdJ+R>)INEX&U; zRwzg;RwyV{0IN+cQpib!gkE83szMn^T~TUsMru)VMrxIwf)7+OQK2BUv_$vts*<8q zg~KaL4zE;5IlQtUCo%c(%2XYN;?jbGqRiaGD^nHnOBHg|LE)InS6qS!!&JS^EMmNj zS$-fJit}@GQ;R_+fZbQDqfnZcs*notVX=B@afw1fQGQBkW=X0-N~%I$KFHSO!&^!Y wuhdfrOD)aJ$w{qJC_20{wYWI3GPOvdAg8ogp(L>=MUSB%zqE*mjD0& delta 2113 zcmaE4JWH?so)F7a1_lO(I0gm=83qOh9%crHdIkoDKdc~828JK(3=A9$3=BWn85p=3 z7#Ns17#KiOd>jz+c_8**cccX zj&U$B@GvkioaJC(5My9qxW&Q1AOy1NBL~D_7ET5RP6h@B0Zs-6E(Qh$DNcyEDknsv zAt%Hkc2K@MC&b_osJ>`U1_l8J28L8l1_pKp28J?D1_oXR28KFL1_ogU28JF^1_m_- z28IPt^N)k+dIp9|P>b$zGBB_(FfhF2g!u3s)POHg1DUuWKIYzlDo|!3Px7d$=Gz<>ZEhfG9V_r}9u* zgBuzG+z<=wxWPfk;0onOa5FG4GcYj3L)9g7Gcd?9FfbHA<@=!GQ@J4yTg1)4AjrVL zu!@_3p&k@ud$=JkI}25K3Ch0@b-)vDh!5X#Gcag_(grsqjx~4~7^D~&7{YiM7(^Ht z7z%k97{nPE7}|IsKAy(|(YKC=fkB#qfnhHX#A7#kAW`**hoK&v7(PQKxOgEB5$1(x zl;DMEQ0IjtE(2aj2*mP25>W~-#7EVUf;dc&fkA_TfgxQG zV$K9Xh{I+JLegCQQb9-%Z-gq?1vTI()W9o(3=Dc89||%s_%Sdra0@{k7$pQr{aHc~ z1M8r4hY-XelY}54Hw((&C0?ktoDv>!AF@q6`eC3=9nCMIj-=DhAQW zBL)d6RWXQ9b;TeS7>hwHa1?`NLtimSK2H^6V3@?fz)&s*F^5YW;!#0y28Mc2!67Ej zz`zJ8?!iST$d{mU0VD(}+d-vbBq))AN zF+dVAD9?jx&mfp2lnw>OzXAgTgDX_Pmw|!76Uqiv$xtS!s$}qHfMi8b^$8LORm)Z& z1{51HFfe#P*&$FGRFi_3pei|rfq@}_0aAH^s%B7a%L>ZV2AgX+)-X-B=Wd^ThkM`V vl{^)Uljrd5+Pr|jiDj~f$dbuyqVG1dirr+~oFl=*$XSqITEtLXTEqYVcXy(w diff --git a/paw/locale/fr/LC_MESSAGES/django.po b/paw/locale/fr/LC_MESSAGES/django.po index bf50c2a..7fe73e6 100644 --- a/paw/locale/fr/LC_MESSAGES/django.po +++ b/paw/locale/fr/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-08-09 01:16+0000\n" +"POT-Creation-Date: 2026-02-22 00:58+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,8 +18,8 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: core/forms.py:27 core/forms.py:35 paw/templates/core/account_finish.html:33 -#: paw/templates/core/login.html:25 paw/templates/core/register.html:24 +#: core/forms.py:27 core/forms.py:35 paw/templates/core/account_finish.html:32 +#: paw/templates/core/login.html:23 paw/templates/core/register.html:23 msgid "Username" msgstr "Nom d'utilisateur" @@ -60,22 +60,59 @@ msgstr "Un compte utilisant ce nom d'utilisateur existe déjà" msgid "Google SSO User" msgstr "Utilisateur Google SSO" -#: paw/settings.py:135 +#: paw/settings.py:140 msgid "English" msgstr "English" -#: paw/settings.py:136 +#: paw/settings.py:141 msgid "French" msgstr "Français" -#: paw/settings.py:137 +#: paw/settings.py:142 msgid "German" msgstr "Deutsch" -#: paw/settings.py:138 +#: paw/settings.py:143 msgid "Dutch" msgstr "Nederlands" +#: paw/templates/404.html:9 +msgid "Page Not Found" +msgstr "Page introuvable" + +#: paw/templates/404.html:11 +msgid "" +"Sorry, we couldn't find the page you're looking for. The page may have been " +"moved, deleted, or the URL might be incorrect." +msgstr "" +"Désolé, nous n'avons pas pu trouver la page que vous recherchez. La page a " +"peut-être été déplacée, supprimée ou l'URL est incorrecte." + +#: paw/templates/404.html:20 paw/templates/500.html:20 +msgid "Go Home" +msgstr "Retour à l'accueil" + +#: paw/templates/404.html:28 paw/templates/500.html:28 +msgid "Go to Tickets" +msgstr "Aller aux tickets" + +#: paw/templates/404.html:35 paw/templates/500.html:35 +#: paw/templates/dashboard_base.html:35 +#: paw/templates/ticketing/create_ticket.html:51 +msgid "Create Ticket" +msgstr "Nouveau ticket" + +#: paw/templates/500.html:9 +msgid "Internal Server Error" +msgstr "Erreur interne du serveur" + +#: paw/templates/500.html:11 +msgid "" +"We're sorry, but something went wrong on our end. Please try again later." +msgstr "" +"Nous sommes désolés, une erreur s'est produite de notre côté. Veuillez " +"réessayer plus tard." + #: paw/templates/base.html:20 paw/templates/dashboard_base.html:41 msgid "Tickets" msgstr "Tickets" @@ -101,12 +138,12 @@ msgstr "Configurer le compte" msgid "Done" msgstr "Terminer" -#: paw/templates/core/account_finish.html:38 -#: paw/templates/core/settings.html:55 +#: paw/templates/core/account_finish.html:36 +#: paw/templates/core/settings.html:51 msgid "Save" msgstr "Enregistrer" -#: paw/templates/core/login.html:7 paw/templates/core/login.html:40 +#: paw/templates/core/login.html:7 paw/templates/core/login.html:32 msgid "Log In" msgstr "Connexion" @@ -114,27 +151,27 @@ msgstr "Connexion" msgid "Register Account" msgstr "Inscription" -#: paw/templates/core/login.html:31 paw/templates/core/register.html:36 +#: paw/templates/core/login.html:27 paw/templates/core/register.html:32 msgid "Password" msgstr "Mot de passe" -#: paw/templates/core/login.html:36 +#: paw/templates/core/login.html:29 msgid "Password Reset" msgstr "Redéfinir le mot de passe" -#: paw/templates/core/login.html:47 +#: paw/templates/core/login.html:39 msgid "Log in with Google" msgstr "Se connecter avec Google" -#: paw/templates/core/register.html:30 +#: paw/templates/core/register.html:28 msgid "Email Address" msgstr "Adresse e-mail" -#: paw/templates/core/register.html:42 +#: paw/templates/core/register.html:36 msgid "Confirm Password" msgstr "Confirmer le mot de passe" -#: paw/templates/core/register.html:47 +#: paw/templates/core/register.html:40 msgid "Register" msgstr "S'enregistrer" @@ -142,36 +179,31 @@ msgstr "S'enregistrer" msgid "Settings" msgstr "Paramètres" -#: paw/templates/core/settings.html:12 +#: paw/templates/core/settings.html:11 msgid "Mail Address" msgstr "Adresse e-mail" -#: paw/templates/core/settings.html:23 +#: paw/templates/core/settings.html:19 msgid "Language" msgstr "Langue" -#: paw/templates/core/settings.html:30 +#: paw/templates/core/settings.html:25 msgid "Use Darkmode" msgstr "Utiliser le mode sombre" -#: paw/templates/core/settings.html:37 +#: paw/templates/core/settings.html:31 msgid "Receive Email Notifications" msgstr "Me notifier par e-mail" -#: paw/templates/core/settings.html:44 +#: paw/templates/core/settings.html:37 msgid "Profile Picture" msgstr "Image du profil" -#: paw/templates/core/settings.html:49 -#: paw/templates/ticketing/ticket_detail.html:152 +#: paw/templates/core/settings.html:42 +#: paw/templates/ticketing/ticket_detail.html:154 msgid "Contact" msgstr "Contact" -#: paw/templates/dashboard_base.html:35 -#: paw/templates/ticketing/create_ticket.html:52 -msgid "Create Ticket" -msgstr "Nouveau ticket" - #: paw/templates/dashboard_base.html:47 #: paw/templates/ticketing/tickets_history.html:5 msgid "History" @@ -186,31 +218,31 @@ msgid "Logout" msgstr "Se déconnecter" #: paw/templates/partials/assigned_to.html:17 -#: paw/templates/ticketing/ticket_detail.html:201 +#: paw/templates/ticketing/ticket_detail.html:203 msgid "Unassigned" msgstr "Non attribué" -#: paw/templates/partials/ticket_priority_badge.html:4 ticketing/models.py:45 +#: paw/templates/partials/ticket_priority_badge.html:4 ticketing/models.py:47 msgid "Low" msgstr "Bas" -#: paw/templates/partials/ticket_priority_badge.html:6 ticketing/models.py:46 +#: paw/templates/partials/ticket_priority_badge.html:6 ticketing/models.py:48 msgid "Medium" msgstr "Moyen" -#: paw/templates/partials/ticket_priority_badge.html:8 ticketing/models.py:47 +#: paw/templates/partials/ticket_priority_badge.html:8 ticketing/models.py:49 msgid "High" msgstr "Élevé" -#: paw/templates/partials/ticket_status_badge.html:4 ticketing/models.py:40 +#: paw/templates/partials/ticket_status_badge.html:4 ticketing/models.py:42 msgid "Open" msgstr "Ouvert" -#: paw/templates/partials/ticket_status_badge.html:6 ticketing/models.py:41 +#: paw/templates/partials/ticket_status_badge.html:6 ticketing/models.py:43 msgid "In Progress" msgstr "En cours" -#: paw/templates/partials/ticket_status_badge.html:8 ticketing/models.py:42 +#: paw/templates/partials/ticket_status_badge.html:8 ticketing/models.py:44 msgid "Closed" msgstr "Fermé" @@ -218,29 +250,29 @@ msgstr "Fermé" msgid "Create a new ticket" msgstr "Créer un ticket" -#: paw/templates/ticketing/create_ticket.html:12 -#: paw/templates/ticketing/ticket_detail.html:165 +#: paw/templates/ticketing/create_ticket.html:11 +#: paw/templates/ticketing/ticket_detail.html:167 #: paw/templates/ticketing/tickets.html:19 #: paw/templates/ticketing/tickets_history.html:19 msgid "Category" msgstr "Catégorie" -#: paw/templates/ticketing/create_ticket.html:19 +#: paw/templates/ticketing/create_ticket.html:15 #: paw/templates/ticketing/tickets.html:17 #: paw/templates/ticketing/tickets_history.html:17 ticketing/forms.py:73 msgid "Title" msgstr "Titre" -#: paw/templates/ticketing/create_ticket.html:26 ticketing/forms.py:74 +#: paw/templates/ticketing/create_ticket.html:23 ticketing/forms.py:74 msgid "Description" msgstr "Description" -#: paw/templates/ticketing/create_ticket.html:34 +#: paw/templates/ticketing/create_ticket.html:31 #: paw/templates/ticketing/ticket_detail.html:9 msgid "Attachments" msgstr "Pièces jointes" -#: paw/templates/ticketing/create_ticket.html:43 +#: paw/templates/ticketing/create_ticket.html:40 msgid "Create as follow-up to a closed ticket" msgstr "Créer comme le suivi d'un ticket fermé" @@ -268,63 +300,63 @@ msgstr "Interne" msgid "Apply Template" msgstr "Appliquer le modèle" -#: paw/templates/ticketing/ticket_detail.html:96 +#: paw/templates/ticketing/ticket_detail.html:98 msgid "Add Comment" msgstr "Ajouter un commentaire" -#: paw/templates/ticketing/ticket_detail.html:98 +#: paw/templates/ticketing/ticket_detail.html:100 msgid "Close Ticket" msgstr "Fermer" -#: paw/templates/ticketing/ticket_detail.html:107 +#: paw/templates/ticketing/ticket_detail.html:109 msgid "Make this an internal comment" msgstr "Rendre le commentaire interne" -#: paw/templates/ticketing/ticket_detail.html:116 +#: paw/templates/ticketing/ticket_detail.html:118 msgid "Ticket has been closed" msgstr "Le ticket a été fermé" -#: paw/templates/ticketing/ticket_detail.html:121 +#: paw/templates/ticketing/ticket_detail.html:123 msgid "Re-Open Ticket" msgstr "Réouverture du ticket" -#: paw/templates/ticketing/ticket_detail.html:132 +#: paw/templates/ticketing/ticket_detail.html:134 msgid "Created by" msgstr "Créé par" -#: paw/templates/ticketing/ticket_detail.html:145 +#: paw/templates/ticketing/ticket_detail.html:147 msgid "Created on" msgstr "Créé le" -#: paw/templates/ticketing/ticket_detail.html:149 +#: paw/templates/ticketing/ticket_detail.html:151 msgid "Last updated" msgstr "Dernière mise à jour le" -#: paw/templates/ticketing/ticket_detail.html:171 +#: paw/templates/ticketing/ticket_detail.html:173 #: paw/templates/ticketing/tickets.html:42 #: paw/templates/ticketing/tickets_history.html:42 ticketing/forms.py:81 -#: ticketing/forms.py:104 ticketing/models.py:165 ticketing/models.py:178 +#: ticketing/forms.py:104 ticketing/models.py:167 ticketing/models.py:180 msgid "General" msgstr "Général" -#: paw/templates/ticketing/ticket_detail.html:177 +#: paw/templates/ticketing/ticket_detail.html:179 msgid "Assign to new category" msgstr "Attribuer à une nouvelle catégorie" -#: paw/templates/ticketing/ticket_detail.html:180 -#: paw/templates/ticketing/ticket_detail.html:210 +#: paw/templates/ticketing/ticket_detail.html:182 +#: paw/templates/ticketing/ticket_detail.html:212 msgid "Assign" msgstr "Attribuer" -#: paw/templates/ticketing/ticket_detail.html:185 +#: paw/templates/ticketing/ticket_detail.html:187 msgid "Assignees" msgstr "Attribué à" -#: paw/templates/ticketing/ticket_detail.html:192 +#: paw/templates/ticketing/ticket_detail.html:194 msgid "Assign to me" msgstr "Me l'attribuer" -#: paw/templates/ticketing/ticket_detail.html:207 +#: paw/templates/ticketing/ticket_detail.html:209 msgid "Assign to new team" msgstr "Attribuer à une nouvelle équipe" @@ -413,7 +445,7 @@ msgstr "Aucun suivi" msgid "No Team" msgstr "Aucune équipe" -#: ticketing/models.py:31 +#: ticketing/models.py:33 msgid "" "If a team is selected, new tickets will automatically assigned to this team." msgstr "" diff --git a/paw/locale/nl/LC_MESSAGES/django.mo b/paw/locale/nl/LC_MESSAGES/django.mo index e03150f4fa27ac0fcd6a8afb1129ad3d0b27712a..961c4f2704cf32d9ebf9c40f764d268d365ea9f5 100644 GIT binary patch delta 2776 zcmbQBJI|>8o)F7a1_lO(6b1$c83qOh8)gQEP6h^s7&ee7149D`0|N&G14APR0|Pe$ z149=F0|P4q1H&{9i26Ak3=CQf3=BIt7#MUI7#N;#FfiycFfa&mGBEHlFfiD2GBALQ z^5bM+;9+232<2p85My9qNaAE*5Mp4cXQU+$|z#zcD!0?unfq|WYf#Dw~0|PGu0|Ofu1A{OF1A_<`1A`g^ z1A_(^#C(4+UC+P}#RYLdDi;F-3j+f~As57lWn2&gYPcW6z25cBl885sB(7#M8185mgV85kIRp$dYy85n#R7#O0s zAr4y34GEFW+z_Ad+;fjHEOhk>CU6epfM5EqB>Ffa%*FfhdNK!UP>2NEKUPy^ba{K-5JAJ5={ zIBYqTzk!E=L7RbrVLuNf4ZP-IV31;9V36cxU=U$oU@+ljU=U|uVDRLHgg_!MLp?Z= z6!J1KNHZ`nH1I+UoWl!oz*1gFV%Y{2Kg$bo&^2C&18zgbU-3c`-)CM1hAIXI1_eHd z4`=g1^e^IrcyK2lBx(=xLDIl+zIq7b5+5Xp?(#t#@QROtA(nxG;WHn^!a#lo22TbC zhA@6ekS^e7U|?ckU|7P>z~IElz_6SjV$Lf8NMidc0I`Tukbyygfq{Wvkbyyyfq}t5 zkb%Jv6vcuJ3`PtL3_bONkVLUx5E9pC1tC#zT@Vr?F9jh!U=@P+SVjn9k+u-TM`l70 z2irkucOi&50YZ=vjDm`13qjIUi4Y_WH9+;(PZNSzI8O*-;RYcF26YAohBHEt5cvYN zkWm=I=Mjbkt)wu-Aqv6}gSCYr4l);JV9)^Ndtn9!e+CAI3}J|cJA@$~I42Bo;9X&` z!|NHILp6R7hPe2LFeGtsia-*LqzEJm^q_oW5e9}R1_lNX5r{(;i$EN*N(AD7ts;py_liO+J|W7$V9vn6a7UDZK^K(&MZ_2w zR2di;oWvLylt4iy#=s!Kz`)Qg26505F^EAMp!89w_$?^?466Q<7{rI%;*gLK5r^oL z5r;&bp*RDBB?AM4oj3zS83O}Dg*XF4J*aGcFAnkHH*tuMc_bjTm;@v$6eJ)%Fpz+# zvzCDPG*E(pA(erFAwdFS@i7So23H0KhVv4T5EYe#UBN!MM92g*l5~#>_VqjoUW`GnbkqnUHHi&_N!HJgDE`*1_pgl<-`C#|o&(F@x zOIJwCFVa&8fl1~jRw`s9mZd5rrKaX79JcazyB8Akv6g`E2oYchPRE3hFN`=Jq#LPT}oWzpUBE8Mxe32{)eu;@i3dJRf zMI{A^>6v+n4DN{vc_6V8Xc*-SEfs`>QK~{QIEd2nOD78n-x3J`Yf#9`Of6AJPc6&O zOG(Y!{91S~QaELo=H;d4DWrhICo?ZmAtf_ap;VzNKQ+4qqOmL!q)ku31+FBsSRpq* zJvApYD_fx~wWuH`F|nk$L8a_7iFpbjoS#>fs*s(SSE8p7P?VpPnp&igky-)? Y8-@IWyv)?nay^E!{QM$@vcxjz+c_8**cccX zj&U$B@GvkioaJC(5My9qxW&Q1AOy1NBL~D_7ET5RP6h@B0Zs-6E(Qh$DNcyEDknsv zAt%Hkc2K@MC&b_osJ>`U1_l8J28L8l1_pKp28J?D1_oXR28KFL1_ogU28JF^1_m_- z28IPt^N)k+dIp9|P>b$zGBB_(FfhF2g!u3s)POHg1DUuWKIYzlDo|!3Px7d$=Gz<>ZEhfG9V_r}9u* zgBuzG+z<=wxWPfk;0onOa5FG4GcYj3L)9g7Gcd?9FfbHA<@=!GQ@J4yTg1)4AjrVL zu!@_3p&k@ud$=JkI}25K3Ch0@b-)vDh!5X#Gcag_(grsqjx~4~7^D~&7{YiM7(^Ht z7z%k97{nPE7}|IsKAy(|(YKC=fkB#qfnhHX#A7#kAW`**hoK&v7(PQKxOgEB5$1(x zl;DMEQ0IjtE(2aj2*mP25>W~-#7EV728AtAUz7?MbLg7w!kFkBRd_~e=} z*hLIag&_`l1GVtGFav`*0|NuM2qe`zi!d;#f>OH(1A`I+1H*I?1_lWR28LZC4B!&- zvIxZI_o4I`s5qx6L|#-BqFzyyfq?^*|E)wJK6e&{X!I6^#8rYQ1A`?414FJT#9_-t zAwF6s3JHNDQ2LxGB)i=Zg*f~LRNZG$h{rg^7#LC+7#JkPAR$>N1_|MIF$RWuP{Gk7 z#=yV`Del2Vr!}Y;hVnsWJE&BQWPtb@l$bzLpxgpV51=yN3?u->?hKH^Cj!a`QJ^vb zMB9Q01_lOw1_p*;1_lO4Q27rEI#9U`l2Zm%Bn%7;ZlDy)z`$U^07=ClIZy%P!@$7c z4639U7#LJQf(#4{#taM$_E0v60u{A3phAg(fx!zTzyJwFkVl;uAR!16HempzuX;#6 z2Bk<)EfviGNo1gkLms4xfq}t+fq@~Mfq@|q$_Lewpi&T&D?l}&90MeMfC?5+X$V!u zV9EeVACe3V42q!i!@$501e1i)p`iE&6)3Jy0bd3N22Us(R3$^1psJF=n}LBrj{#DE zfW$%7vK5E{#h?Pn1IiA8(x9>h!~|8zF$@e00Su7J3sg0OYFk!61_lO$&9xkBm?qnE zw@<#qy>IhMo(jgvbNF^`Ucev0GFeh&$>a+nH#YAT-NQKfgZO>Uvi$rahO)#w1^|SJ BsE+^u diff --git a/paw/locale/nl/LC_MESSAGES/django.po b/paw/locale/nl/LC_MESSAGES/django.po index a427106..2b58866 100644 --- a/paw/locale/nl/LC_MESSAGES/django.po +++ b/paw/locale/nl/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-08-09 01:16+0000\n" +"POT-Creation-Date: 2026-02-22 00:58+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,8 +18,8 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: core/forms.py:27 core/forms.py:35 paw/templates/core/account_finish.html:33 -#: paw/templates/core/login.html:25 paw/templates/core/register.html:24 +#: core/forms.py:27 core/forms.py:35 paw/templates/core/account_finish.html:32 +#: paw/templates/core/login.html:23 paw/templates/core/register.html:23 msgid "Username" msgstr "Gebruikersnaam" @@ -59,22 +59,58 @@ msgstr "Er bestaat al een account met deze gebruikersnaam" msgid "Google SSO User" msgstr "Google SSO Gebruiker" -#: paw/settings.py:135 +#: paw/settings.py:140 msgid "English" msgstr "English" -#: paw/settings.py:136 +#: paw/settings.py:141 msgid "French" msgstr "Français" -#: paw/settings.py:137 +#: paw/settings.py:142 msgid "German" msgstr "Deutsch" -#: paw/settings.py:138 +#: paw/settings.py:143 msgid "Dutch" msgstr "Nederlands" +#: paw/templates/404.html:9 +msgid "Page Not Found" +msgstr "Pagina niet gevonden" + +#: paw/templates/404.html:11 +msgid "" +"Sorry, we couldn't find the page you're looking for. The page may have been " +"moved, deleted, or the URL might be incorrect." +msgstr "" +"Sorry, we kunnen de pagina die u zoekt niet vinden. De pagina is mogelijk " +"verplaatst, verwijderd of de URL is onjuist." + +#: paw/templates/404.html:20 paw/templates/500.html:20 +msgid "Go Home" +msgstr "Naar startpagina" + +#: paw/templates/404.html:28 paw/templates/500.html:28 +msgid "Go to Tickets" +msgstr "Ga naar tickets" + +#: paw/templates/404.html:35 paw/templates/500.html:35 +#: paw/templates/dashboard_base.html:35 +#: paw/templates/ticketing/create_ticket.html:51 +msgid "Create Ticket" +msgstr "Ticketje aanmaken" + +#: paw/templates/500.html:9 +msgid "Internal Server Error" +msgstr "Interne serverfout" + +#: paw/templates/500.html:11 +msgid "" +"We're sorry, but something went wrong on our end. Please try again later." +msgstr "" +"Sorry, er is iets misgegaan aan onze kant. Probeer het later opnieuw." + #: paw/templates/base.html:20 paw/templates/dashboard_base.html:41 msgid "Tickets" msgstr "Ticketjes" @@ -100,12 +136,12 @@ msgstr "Account instellingen" msgid "Done" msgstr "Gedaan" -#: paw/templates/core/account_finish.html:38 -#: paw/templates/core/settings.html:55 +#: paw/templates/core/account_finish.html:36 +#: paw/templates/core/settings.html:51 msgid "Save" msgstr "Opslaan" -#: paw/templates/core/login.html:7 paw/templates/core/login.html:40 +#: paw/templates/core/login.html:7 paw/templates/core/login.html:32 msgid "Log In" msgstr "Inloggen" @@ -113,27 +149,27 @@ msgstr "Inloggen" msgid "Register Account" msgstr "Registreren" -#: paw/templates/core/login.html:31 paw/templates/core/register.html:36 +#: paw/templates/core/login.html:27 paw/templates/core/register.html:32 msgid "Password" msgstr "Wachtwoord" -#: paw/templates/core/login.html:36 +#: paw/templates/core/login.html:29 msgid "Password Reset" msgstr "Wachtwoord resetten" -#: paw/templates/core/login.html:47 +#: paw/templates/core/login.html:39 msgid "Log in with Google" msgstr "Inloggen met Google" -#: paw/templates/core/register.html:30 +#: paw/templates/core/register.html:28 msgid "Email Address" msgstr "E-mailadres" -#: paw/templates/core/register.html:42 +#: paw/templates/core/register.html:36 msgid "Confirm Password" msgstr "Wachtwoordbevestiging" -#: paw/templates/core/register.html:47 +#: paw/templates/core/register.html:40 msgid "Register" msgstr "Registreren" @@ -141,36 +177,31 @@ msgstr "Registreren" msgid "Settings" msgstr "Instellingen" -#: paw/templates/core/settings.html:12 +#: paw/templates/core/settings.html:11 msgid "Mail Address" msgstr "E-mailadres" -#: paw/templates/core/settings.html:23 +#: paw/templates/core/settings.html:19 msgid "Language" msgstr "Taal" -#: paw/templates/core/settings.html:30 +#: paw/templates/core/settings.html:25 msgid "Use Darkmode" msgstr "Gebruik donkere modus" -#: paw/templates/core/settings.html:37 +#: paw/templates/core/settings.html:31 msgid "Receive Email Notifications" msgstr "Meldingen per e-mail" -#: paw/templates/core/settings.html:44 +#: paw/templates/core/settings.html:37 msgid "Profile Picture" msgstr "Profielfoto" -#: paw/templates/core/settings.html:49 -#: paw/templates/ticketing/ticket_detail.html:152 +#: paw/templates/core/settings.html:42 +#: paw/templates/ticketing/ticket_detail.html:154 msgid "Contact" msgstr "Contact" -#: paw/templates/dashboard_base.html:35 -#: paw/templates/ticketing/create_ticket.html:52 -msgid "Create Ticket" -msgstr "Ticketje aanmaken" - #: paw/templates/dashboard_base.html:47 #: paw/templates/ticketing/tickets_history.html:5 msgid "History" @@ -185,31 +216,31 @@ msgid "Logout" msgstr "Uitloggen" #: paw/templates/partials/assigned_to.html:17 -#: paw/templates/ticketing/ticket_detail.html:201 +#: paw/templates/ticketing/ticket_detail.html:203 msgid "Unassigned" msgstr "Niet toegewezen" -#: paw/templates/partials/ticket_priority_badge.html:4 ticketing/models.py:45 +#: paw/templates/partials/ticket_priority_badge.html:4 ticketing/models.py:47 msgid "Low" msgstr "Laag" -#: paw/templates/partials/ticket_priority_badge.html:6 ticketing/models.py:46 +#: paw/templates/partials/ticket_priority_badge.html:6 ticketing/models.py:48 msgid "Medium" msgstr "Gemiddeld" -#: paw/templates/partials/ticket_priority_badge.html:8 ticketing/models.py:47 +#: paw/templates/partials/ticket_priority_badge.html:8 ticketing/models.py:49 msgid "High" msgstr "Hoog" -#: paw/templates/partials/ticket_status_badge.html:4 ticketing/models.py:40 +#: paw/templates/partials/ticket_status_badge.html:4 ticketing/models.py:42 msgid "Open" msgstr "Open" -#: paw/templates/partials/ticket_status_badge.html:6 ticketing/models.py:41 +#: paw/templates/partials/ticket_status_badge.html:6 ticketing/models.py:43 msgid "In Progress" msgstr "Lopend" -#: paw/templates/partials/ticket_status_badge.html:8 ticketing/models.py:42 +#: paw/templates/partials/ticket_status_badge.html:8 ticketing/models.py:44 msgid "Closed" msgstr "Gesloten" @@ -217,29 +248,29 @@ msgstr "Gesloten" msgid "Create a new ticket" msgstr "Maak een nieuw ticketje aan" -#: paw/templates/ticketing/create_ticket.html:12 -#: paw/templates/ticketing/ticket_detail.html:165 +#: paw/templates/ticketing/create_ticket.html:11 +#: paw/templates/ticketing/ticket_detail.html:167 #: paw/templates/ticketing/tickets.html:19 #: paw/templates/ticketing/tickets_history.html:19 msgid "Category" msgstr "Categorie" -#: paw/templates/ticketing/create_ticket.html:19 +#: paw/templates/ticketing/create_ticket.html:15 #: paw/templates/ticketing/tickets.html:17 #: paw/templates/ticketing/tickets_history.html:17 ticketing/forms.py:73 msgid "Title" msgstr "Onderwerp" -#: paw/templates/ticketing/create_ticket.html:26 ticketing/forms.py:74 +#: paw/templates/ticketing/create_ticket.html:23 ticketing/forms.py:74 msgid "Description" msgstr "Beschrijving" -#: paw/templates/ticketing/create_ticket.html:34 +#: paw/templates/ticketing/create_ticket.html:31 #: paw/templates/ticketing/ticket_detail.html:9 msgid "Attachments" msgstr "Bijlagen" -#: paw/templates/ticketing/create_ticket.html:43 +#: paw/templates/ticketing/create_ticket.html:40 msgid "Create as follow-up to a closed ticket" msgstr "Maak als vervolg op een gesloten ticket" @@ -267,63 +298,63 @@ msgstr "Privaat" msgid "Apply Template" msgstr "Gebruik model" -#: paw/templates/ticketing/ticket_detail.html:96 +#: paw/templates/ticketing/ticket_detail.html:98 msgid "Add Comment" msgstr "Opmerking toevoegen" -#: paw/templates/ticketing/ticket_detail.html:98 +#: paw/templates/ticketing/ticket_detail.html:100 msgid "Close Ticket" msgstr "Ticketje sluiten" -#: paw/templates/ticketing/ticket_detail.html:107 +#: paw/templates/ticketing/ticket_detail.html:109 msgid "Make this an internal comment" msgstr "Maak dit opmerking privaat" -#: paw/templates/ticketing/ticket_detail.html:116 +#: paw/templates/ticketing/ticket_detail.html:118 msgid "Ticket has been closed" msgstr "Ticketje is geslooten" -#: paw/templates/ticketing/ticket_detail.html:121 +#: paw/templates/ticketing/ticket_detail.html:123 msgid "Re-Open Ticket" msgstr "Heropen dit ticketje" -#: paw/templates/ticketing/ticket_detail.html:132 +#: paw/templates/ticketing/ticket_detail.html:134 msgid "Created by" msgstr "Gemaakt door" -#: paw/templates/ticketing/ticket_detail.html:145 +#: paw/templates/ticketing/ticket_detail.html:147 msgid "Created on" msgstr "Gemaakt op" -#: paw/templates/ticketing/ticket_detail.html:149 +#: paw/templates/ticketing/ticket_detail.html:151 msgid "Last updated" msgstr "Laatst bijgewerkt" -#: paw/templates/ticketing/ticket_detail.html:171 +#: paw/templates/ticketing/ticket_detail.html:173 #: paw/templates/ticketing/tickets.html:42 #: paw/templates/ticketing/tickets_history.html:42 ticketing/forms.py:81 -#: ticketing/forms.py:104 ticketing/models.py:165 ticketing/models.py:178 +#: ticketing/forms.py:104 ticketing/models.py:167 ticketing/models.py:180 msgid "General" msgstr "Generaal" -#: paw/templates/ticketing/ticket_detail.html:177 +#: paw/templates/ticketing/ticket_detail.html:179 msgid "Assign to new category" msgstr "Categorie opnieuw toewijzen" -#: paw/templates/ticketing/ticket_detail.html:180 -#: paw/templates/ticketing/ticket_detail.html:210 +#: paw/templates/ticketing/ticket_detail.html:182 +#: paw/templates/ticketing/ticket_detail.html:212 msgid "Assign" msgstr "Toewijzen" -#: paw/templates/ticketing/ticket_detail.html:185 +#: paw/templates/ticketing/ticket_detail.html:187 msgid "Assignees" msgstr "Toegewezen" -#: paw/templates/ticketing/ticket_detail.html:192 +#: paw/templates/ticketing/ticket_detail.html:194 msgid "Assign to me" msgstr "Toewijzen aan mij" -#: paw/templates/ticketing/ticket_detail.html:207 +#: paw/templates/ticketing/ticket_detail.html:209 msgid "Assign to new team" msgstr "Team opnieuw toewijzen" @@ -412,7 +443,7 @@ msgstr "Geen vervolg ticketje" msgid "No Team" msgstr "Geen team" -#: ticketing/models.py:31 +#: ticketing/models.py:33 msgid "" "If a team is selected, new tickets will automatically assigned to this team." msgstr "" diff --git a/paw/settings.py b/paw/settings.py index 6744220..09bd457 100644 --- a/paw/settings.py +++ b/paw/settings.py @@ -165,6 +165,11 @@ STATICFILES_DIRS = ( MEDIA_URL = '/media/' MEDIA_ROOT = BASE_DIR / 'media' +# Secure storage for ticket attachments +# This directory should NOT be served by the web server +SECURE_MEDIA_ENABLED = environ.get('SECURE_MEDIA_ENABLED', 'false').lower() == 'true' +SECURE_MEDIA_ROOT = BASE_DIR / 'secure_media' + # Default primary key field type # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field diff --git a/paw/static/css/paw.css b/paw/static/css/paw.css index 0d42ef0..67fd91f 100644 --- a/paw/static/css/paw.css +++ b/paw/static/css/paw.css @@ -15,6 +15,7 @@ --container-sm: 24rem; --container-lg: 32rem; --container-xl: 36rem; + --container-2xl: 42rem; --container-3xl: 48rem; --container-4xl: 56rem; --text-xs: 0.75rem; @@ -33,6 +34,8 @@ --text-3xl--line-height: calc(2.25 / 1.875); --text-4xl: 2.25rem; --text-4xl--line-height: calc(2.5 / 2.25); + --text-9xl: 8rem; + --text-9xl--line-height: 1; --font-weight-semibold: 600; --font-weight-bold: 700; --default-font-family: var(--font-sans); @@ -2001,6 +2004,9 @@ .mb-6 { margin-bottom: calc(var(--spacing) * 6); } + .mb-8 { + margin-bottom: calc(var(--spacing) * 8); + } .mb-10 { margin-bottom: calc(var(--spacing) * 10); } @@ -2222,6 +2228,9 @@ .w-full { width: 100%; } + .max-w-2xl { + max-width: var(--container-2xl); + } .max-w-3xl { max-width: var(--container-3xl); } @@ -2310,6 +2319,9 @@ .gap-2 { gap: calc(var(--spacing) * 2); } + .gap-4 { + gap: calc(var(--spacing) * 4); + } .self-center { align-self: center; } @@ -2403,6 +2415,9 @@ .px-3 { padding-inline: calc(var(--spacing) * 3); } + .px-4 { + padding-inline: calc(var(--spacing) * 4); + } .py-1 { padding-block: calc(var(--spacing) * 1); } @@ -2427,6 +2442,10 @@ font-size: var(--text-4xl); line-height: var(--tw-leading, var(--text-4xl--line-height)); } + .text-9xl { + font-size: var(--text-9xl); + line-height: var(--tw-leading, var(--text-9xl--line-height)); + } .text-base { font-size: var(--text-base); line-height: var(--tw-leading, var(--text-base--line-height)); @@ -2516,6 +2535,12 @@ .text-base-content { color: var(--color-base-content); } + .text-base-content\/70 { + color: var(--color-base-content); + @supports (color: color-mix(in lab, red, red)) { + color: color-mix(in oklab, var(--color-base-content) 70%, transparent); + } + } .text-base-content\/80 { color: var(--color-base-content); @supports (color: color-mix(in lab, red, red)) { @@ -2576,6 +2601,26 @@ } } } + .btn-outline { + @layer daisyui.l1 { + &:not( .btn-active, :hover, :active:focus, :focus-visible, input:checked:not(.filter .btn), :disabled, [disabled], .btn-disabled ) { + --btn-shadow: ""; + --btn-bg: #0000; + --btn-fg: var(--btn-color); + --btn-border: var(--btn-color); + --btn-noise: none; + } + @media (hover: none) { + &:not(.btn-active, :active, :focus-visible, input:checked:not(.filter .btn)):hover { + --btn-shadow: ""; + --btn-bg: #0000; + --btn-fg: var(--btn-color); + --btn-border: var(--btn-color); + --btn-noise: none; + } + } + } + } .btn-lg { @layer daisyui.l1.l2 { --fontsize: 1.125rem; @@ -2645,6 +2690,18 @@ --btn-fg: var(--color-neutral-content); } } + .btn-primary { + @layer daisyui.l1.l2.l3 { + --btn-color: var(--color-primary); + --btn-fg: var(--color-primary-content); + } + } + .btn-secondary { + @layer daisyui.l1.l2.l3 { + --btn-color: var(--color-secondary); + --btn-fg: var(--color-secondary-content); + } + } .btn-success { @layer daisyui.l1.l2.l3 { --btn-color: var(--color-success); @@ -2676,6 +2733,11 @@ display: block; } } + .sm\:flex-row { + @media (width >= 40rem) { + flex-direction: row; + } + } .lg\:drawer-open { @media (width >= 64rem) { @layer daisyui.l1.l2.l3 { diff --git a/paw/templates/404.html b/paw/templates/404.html new file mode 100644 index 0000000..203ad91 --- /dev/null +++ b/paw/templates/404.html @@ -0,0 +1,42 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block content %} +
+
+
+

404

+

{% trans 'Page Not Found' %}

+

+ {% trans "Sorry, we couldn't find the page you're looking for. The page may have been moved, deleted, or the URL might be incorrect." %} +

+
+ +
+
+{% endblock %} + diff --git a/paw/templates/500.html b/paw/templates/500.html new file mode 100644 index 0000000..c267324 --- /dev/null +++ b/paw/templates/500.html @@ -0,0 +1,42 @@ +{% extends 'base.html' %} +{% load i18n %} + +{% block content %} +
+
+
+

500

+

{% trans 'Internal Server Error' %}

+

+ {% trans "We're sorry, but something went wrong on our end. Please try again later." %} +

+
+ +
+
+{% endblock %} + diff --git a/paw/templates/ticketing/ticket_detail.html b/paw/templates/ticketing/ticket_detail.html index b04a4c9..746fc32 100644 --- a/paw/templates/ticketing/ticket_detail.html +++ b/paw/templates/ticketing/ticket_detail.html @@ -9,7 +9,7 @@

{% trans 'Attachments' %}

{% for attachment in attachments %} - + {% trans 'Attachment' %} {{ forloop.counter }} diff --git a/ticketing/models.py b/ticketing/models.py index 6049baf..a2d601a 100644 --- a/ticketing/models.py +++ b/ticketing/models.py @@ -6,10 +6,12 @@ from django.db.models.signals import post_save, pre_save from django.dispatch import receiver from uuid import uuid4 from core.models import MailTemplate +from .storage import SecureFileStorage +from django.conf import settings def ticket_directory_path(instance, filename): - """ file will be uploaded to MEDIA_ROOT/ticket_/ """ + """ file will be uploaded to (SECURE_)MEDIA_ROOT/attachments/ticket_/ """ ext = filename.split('.')[-1] return "attachments/ticket_{0}/{1}.{2}".format(instance.ticket.id, uuid4(), ext) @@ -225,11 +227,20 @@ def send_mail_comment_notification(sender, instance, created, **kwargs): class FileAttachment(models.Model): ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE) file = models.FileField( - upload_to=ticket_directory_path, max_length=255) + upload_to=ticket_directory_path, + max_length=255, + storage=SecureFileStorage() if settings.SECURE_MEDIA_ENABLED else None) uploaded_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f'{_('Attachment for')} {self.ticket.title}' + + def get_attachment_url(self): + """ + Returns the URL for downloading the attachment. + """ + from django.urls import reverse + return reverse('download_attachment', args=[self.id]) class Template(models.Model): diff --git a/ticketing/storage.py b/ticketing/storage.py new file mode 100644 index 0000000..800371d --- /dev/null +++ b/ticketing/storage.py @@ -0,0 +1,28 @@ +""" +Custom storage backend for secure file attachments. +Files stored here are not publicly accessible via web server. +""" +from django.core.files.storage import FileSystemStorage +from django.conf import settings +import os + + +class SecureFileStorage(FileSystemStorage): + """ + Storage backend that stores files in a secure location + outside the public media directory. + """ + + def __init__(self, location=None, base_url=None): + if location is None: + location = settings.SECURE_MEDIA_ROOT + # base_url is None to prevent public URL generation + super().__init__(location=location, base_url=None) + + def url(self, name): + """ + Override url() to return None or raise an error. + Files should only be accessed through the secure download view. + """ + return None + diff --git a/ticketing/urls.py b/ticketing/urls.py index 9371ef6..a9bf051 100644 --- a/ticketing/urls.py +++ b/ticketing/urls.py @@ -1,6 +1,6 @@ from django.urls import path -from .views import show_ticket, show_tickets, create_ticket, dashboard, show_tickets_history +from .views import show_ticket, show_tickets, create_ticket, dashboard, show_tickets_history, download_attachment urlpatterns = [ path("tickets/", show_ticket, name="ticket_detail"), @@ -8,4 +8,5 @@ urlpatterns = [ path("tickets", show_tickets, name="all_tickets"), path("tickets/history", show_tickets_history, name="tickets_history"), path("dashboard", dashboard, name="dashboard"), + path("tickets/attachments//", download_attachment, name="download_attachment"), ] diff --git a/ticketing/views.py b/ticketing/views.py index 86209e1..046ade5 100644 --- a/ticketing/views.py +++ b/ticketing/views.py @@ -1,10 +1,13 @@ from django.shortcuts import render, redirect from django.contrib.auth.decorators import login_required from django.shortcuts import get_object_or_404 -from .models import Ticket, Template +from .models import Ticket, Template, FileAttachment from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger from django.db.models import Q from .forms import CommentForm, TicketForm, TemplateForm, TeamAssignmentForm, CategoryAssignmentForm +from django.http import Http404, FileResponse +from django.conf import settings +import os @login_required @@ -92,7 +95,7 @@ def show_ticket(request, ticket_id): comments = ticket.comment_set.all() context = { - "ticket": ticket, "comments": comments, "attachments": [attachment.file for attachment in ticket.fileattachment_set.all()], + "ticket": ticket, "comments": comments, "attachments": ticket.fileattachment_set.all(), "form": form, "template_form": template_form, "team_assignment_form": team_assignment_form, "category_assignment_form": category_assignment_form, "can_edit": can_edit @@ -132,3 +135,40 @@ def dashboard(request): in_progress_tickets = tickets.filter(status=Ticket.Status.IN_PROGRESS) closed_tickets = tickets.filter(status=Ticket.Status.CLOSED) return render(request, "ticketing/dashboard.html", {"tickets": tickets, "open_tickets": open_tickets, "in_progress_tickets": in_progress_tickets, "closed_tickets": closed_tickets}) + + +@login_required +def download_attachment(request, attachment_id): + """ + View that checks if the user has permission + to access the ticket before serving the file. + """ + attachment = get_object_or_404(FileAttachment, pk=attachment_id) + ticket = attachment.ticket + + if not ticket.can_open(request.user): + raise Http404("File not found") + + if not attachment.file: + raise Http404("File not found") + + try: + file = attachment.file.open('rb') + response = FileResponse(file, content_type='application/octet-stream') + + filename = os.path.basename(attachment.file.name) + response['Content-Disposition'] = f'inline; filename="{filename}"' + return response + except (ValueError, IOError, OSError): + # backwards compatibility + try: + old_path = os.path.join(settings.MEDIA_ROOT, attachment.file.name) + if os.path.exists(old_path): + file = open(old_path, 'rb') + response = FileResponse(file, content_type='application/octet-stream') + filename = os.path.basename(attachment.file.name) + response['Content-Disposition'] = f'inline; filename="{filename}"' + return response + except (ValueError, IOError, OSError): + pass + raise Http404("File not found") -- 2.51.2