diff --git a/.env.example b/.env.example index 9c931e6a..fb541c7a 100644 --- a/.env.example +++ b/.env.example @@ -83,4 +83,14 @@ BIRTHDAY_DAYS=50 # if you want to forbid members to create members, set to False and only admins will be able to do it # ALLOW_MEMBERS_TO_CREATE_MEMBERS=True # if you want to forbid members to invite members, set to False and only admins will be able to do it -# ALLOW_MEMBERS_TO_INVITE_MEMBERS=True \ No newline at end of file +# ALLOW_MEMBERS_TO_INVITE_MEMBERS=True + +# Troves settings +# Maximum size of a trove file +# TROVE_FILE_MAX_SIZE=20*1024*1024 # 20MB +# Maximum size of a trove picture +# TROVE_PICTURE_FILE_MAX_SIZE=5*1024*1024 # 5MB +# Maximum size of a trove thumbnail in pixels +# TROVE_THUMBNAIL_SIZE=GALLERIES_THUMBNAIL_SIZE +# Default number of troves per page +# DEFAULT_TROVE_PAGE_SIZE=10 diff --git a/chat/views/views_private_rooms.py b/chat/views/views_private_rooms.py index f430dab0..d41b52a5 100644 --- a/chat/views/views_private_rooms.py +++ b/chat/views/views_private_rooms.py @@ -14,7 +14,7 @@ from members.models import Member from ..models import ChatMessage, ChatRoom, PrivateChatRoom -from cousinsmatter.utils import Paginator, assert_request_is_ajax +from cousinsmatter.utils import PageOutOfBounds, Paginator, assert_request_is_ajax logger = logging.getLogger(__name__) @@ -95,21 +95,24 @@ def private_chat_rooms(request, page_num=1): first_message_author=Subquery(first_msg_auth_subquery) ).order_by('date_added') - page = Paginator.get_page(request, private_chat_rooms, page_num, - reverse_link="chat:private_chat_page", - default_page_size=settings.DEFAULT_CHATROOMS_PER_PAGE) - author_ids = [room.first_message_author for room in page.object_list if room.first_message_author is not None] - # print("author ids", author_ids) - authors = Member.objects.filter(id__in=author_ids) - for room in page.object_list: - if room.first_message_author: - # print("first msg auth=", room.first_message_author) - for author in authors: - # print("author name:", author.username) - if room.first_message_author == author.id: - room.first_message_author = author - # print("final author:", room.first_message_author) - return render(request, 'chat/chat_rooms.html', {"page": page, 'private': True}) + try: + page = Paginator.get_page(request, private_chat_rooms, page_num, + reverse_link="chat:private_chat_page", + default_page_size=settings.DEFAULT_CHATROOMS_PER_PAGE) + author_ids = [room.first_message_author for room in page.object_list if room.first_message_author is not None] + # print("author ids", author_ids) + authors = Member.objects.filter(id__in=author_ids) + for room in page.object_list: + if room.first_message_author: + # print("first msg auth=", room.first_message_author) + for author in authors: + # print("author name:", author.username) + if room.first_message_author == author.id: + room.first_message_author = author + # print("final author:", room.first_message_author) + return render(request, 'chat/chat_rooms.html', {"page": page, 'private': True}) + except PageOutOfBounds as exc: + return redirect(exc.redirect_to) @login_required @@ -201,13 +204,15 @@ def private_chat_room(request, room_slug, page_num=None): messages.error(request, _("You are not a member of this private room")) return redirect(reverse('chat:private_chat_rooms')) message_list = ChatMessage.objects.filter(room=room.id) - - page = Paginator.get_page(request, message_list, - page_num=page_num, - reverse_link="chat:room_page", - compute_link=lambda page_num: reverse("chat:room_page", args=[room_slug, page_num]), - default_page_size=100) - return render(request, 'chat/room_detail.html', {'room': room, "page": page, "private": True}) + try: + page = Paginator.get_page(request, message_list, + page_num=page_num, + reverse_link="chat:room_page", + compute_link=lambda page_num: reverse("chat:room_page", args=[room_slug, page_num]), + default_page_size=100) + return render(request, 'chat/room_detail.html', {'room': room, "page": page, "private": True}) + except PageOutOfBounds as exc: + return redirect(exc.redirect_to) @login_required diff --git a/chat/views/views_public_rooms.py b/chat/views/views_public_rooms.py index 87a473e8..f5d93348 100644 --- a/chat/views/views_public_rooms.py +++ b/chat/views/views_public_rooms.py @@ -12,7 +12,7 @@ from members.models import Member from ..models import ChatMessage, ChatRoom -from cousinsmatter.utils import Paginator, assert_request_is_ajax +from cousinsmatter.utils import PageOutOfBounds, Paginator, assert_request_is_ajax from cm_main import followers from urllib.parse import unquote @@ -36,24 +36,26 @@ def chat_rooms(request, page_num=1): num_messages=Count("chatmessage"), first_message_author=Subquery(first_msg_auth_subquery) ).order_by('date_added') - - page = Paginator.get_page(request, - object_list=chat_rooms, - page_num=page_num, - reverse_link="chat:chat_page", - default_page_size=settings.DEFAULT_CHATROOMS_PER_PAGE) - author_ids = [room.first_message_author for room in page.object_list if room.first_message_author is not None] - # print("author ids", author_ids) - authors = Member.objects.filter(id__in=author_ids) - for room in page.object_list: - if room.first_message_author: - # print("first msg auth=", room.first_message_author) - for author in authors: - # print("author name:", author.username) - if room.first_message_author == author.id: - room.first_message_author = author - # print("final author:", room.first_message_author) - return render(request, 'chat/chat_rooms.html', {"page": page}) + try: + page = Paginator.get_page(request, + object_list=chat_rooms, + page_num=page_num, + reverse_link="chat:chat_page", + default_page_size=settings.DEFAULT_CHATROOMS_PER_PAGE) + author_ids = [room.first_message_author for room in page.object_list if room.first_message_author is not None] + # print("author ids", author_ids) + authors = Member.objects.filter(id__in=author_ids) + for room in page.object_list: + if room.first_message_author: + # print("first msg auth=", room.first_message_author) + for author in authors: + # print("author name:", author.username) + if room.first_message_author == author.id: + room.first_message_author = author + # print("final author:", room.first_message_author) + return render(request, 'chat/chat_rooms.html', {"page": page}) + except PageOutOfBounds as exc: + return redirect(exc.redirect_to) @login_required @@ -88,14 +90,16 @@ def new_room(request): def chat_room(request, room_slug, page_num=None): room = get_object_or_404(ChatRoom, slug=room_slug) messages = ChatMessage.objects.filter(room=room.id) - - page = Paginator.get_page(request, - object_list=messages, - page_num=page_num, - reverse_link="chat:room_page", - compute_link=lambda page_num: reverse("chat:room_page", args=[room_slug, page_num]), - default_page_size=settings.DEFAULT_CHATMESSAGES_PER_PAGE) - return render(request, 'chat/room_detail.html', {'room': room, "page": page}) + try: + page = Paginator.get_page(request, + object_list=messages, + page_num=page_num, + reverse_link="chat:room_page", + compute_link=lambda page_num: reverse("chat:room_page", args=[room_slug, page_num]), + default_page_size=settings.DEFAULT_CHATMESSAGES_PER_PAGE) + return render(request, 'chat/room_detail.html', {'room': room, "page": page}) + except PageOutOfBounds as exc: + return redirect(exc.redirect_to) @login_required diff --git a/cm_main/locale/de/LC_MESSAGES/django.mo b/cm_main/locale/de/LC_MESSAGES/django.mo index 73f03557..4de56b25 100644 Binary files a/cm_main/locale/de/LC_MESSAGES/django.mo and b/cm_main/locale/de/LC_MESSAGES/django.mo differ diff --git a/cm_main/locale/de/LC_MESSAGES/django.po b/cm_main/locale/de/LC_MESSAGES/django.po index d01263da..f8374fbf 100644 --- a/cm_main/locale/de/LC_MESSAGES/django.po +++ b/cm_main/locale/de/LC_MESSAGES/django.po @@ -12,7 +12,7 @@ msgid "" msgstr "" "Project-Id-Version: Cousins Matter 0.1.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-11 19:57+0100\n" +"POT-Creation-Date: 2024-12-30 16:46+0100\n" "PO-Revision-Date: 2024-10-21 09:03+00:00\n" "Last-Translator: Olivier LEVILLAIN\n" "Language-Team: German \n" @@ -111,23 +111,23 @@ msgstr "Diese Seite zeigt einige Statistiken über diese Website an:" msgid "Statistics" msgstr "Statistiken" -#: templates/cm_main/base.html:8 +#: templates/cm_main/base.html:9 msgid "Home" msgstr "Heim" -#: templates/cm_main/base.html:68 +#: templates/cm_main/base.html:69 msgid "This site proudly built using" msgstr "Diese Website stolz gebaut mit" -#: templates/cm_main/base.html:70 +#: templates/cm_main/base.html:71 msgid "The source code is licensed" msgstr "Die Quellcode ist lizenziert" -#: templates/cm_main/base.html:76 +#: templates/cm_main/base.html:77 msgid "Copyright © 2024 Cousins Matter. All rights reserved." msgstr "Urheberrecht © 2024 Cousins Matter. Alle Rechte vorbehalten." -#: templates/cm_main/base.html:79 templates/cm_main/navbar.html:138 +#: templates/cm_main/base.html:80 templates/cm_main/navbar.html:145 msgid "Contact the site admin" msgstr "Kontaktiere den Site-Administrator" @@ -166,25 +166,25 @@ msgstr "Schalten Sie die Notizbearbeitungs-Werkzeugleiste ein" msgid "Remaining characters:" msgstr "Verbleibende Zeichen:" -#: templates/cm_main/common/paginate_template.html:27 +#: templates/cm_main/common/paginate_template.html:25 msgid "Items per page:" msgstr "Items pro Seite:" -#: templates/cm_main/common/paginate_template.html:47 +#: templates/cm_main/common/paginate_template.html:45 msgid "go to first page" msgstr "Gehe zur ersten Seite" -#: templates/cm_main/common/paginate_template.html:59 +#: templates/cm_main/common/paginate_template.html:57 #, python-format msgid "go to page #%(page_num)s" msgstr "Gehe zur Seite #%(page_num)s" -#: templates/cm_main/common/paginate_template.html:62 +#: templates/cm_main/common/paginate_template.html:60 #, python-format msgid "page #%(page_num)s" msgstr "Seite #%(page_num)s" -#: templates/cm_main/common/paginate_template.html:75 +#: templates/cm_main/common/paginate_template.html:73 msgid "go to last page" msgstr "Gehe zur letzten Seite" @@ -279,95 +279,99 @@ msgstr "Stopp Folgen" msgid "Follow" msgstr "Folgen" -#: templates/cm_main/navbar.html:26 views/views_stats.py:106 +#: templates/cm_main/navbar.html:28 views/views_stats.py:106 msgid "Members" msgstr "Mitglieder" -#: templates/cm_main/navbar.html:29 +#: templates/cm_main/navbar.html:31 msgid "Birthdays" msgstr "Geburtstag" -#: templates/cm_main/navbar.html:31 +#: templates/cm_main/navbar.html:33 msgid "Show directory" msgstr "Zeige Verzeichnis" -#: templates/cm_main/navbar.html:34 +#: templates/cm_main/navbar.html:36 msgid "Create Member" msgstr "Erstellen Sie Mitglied" -#: templates/cm_main/navbar.html:38 +#: templates/cm_main/navbar.html:40 msgid "Invite Member" msgstr "Einladung zum Mitglied werden" -#: templates/cm_main/navbar.html:45 views/views_stats.py:114 +#: templates/cm_main/navbar.html:47 views/views_stats.py:114 msgid "Galleries" msgstr "Gallerie" -#: templates/cm_main/navbar.html:48 +#: templates/cm_main/navbar.html:50 msgid "Create Gallery" msgstr "Erstelle Galerie" -#: templates/cm_main/navbar.html:50 +#: templates/cm_main/navbar.html:52 msgid "Bulk Upload" msgstr "Maschinelle Datenübertragung" -#: templates/cm_main/navbar.html:56 +#: templates/cm_main/navbar.html:58 msgid "Forum" msgstr "Forum" -#: templates/cm_main/navbar.html:59 +#: templates/cm_main/navbar.html:61 msgid "Create Post" msgstr "Erstelle einen Beitrag" -#: templates/cm_main/navbar.html:64 +#: templates/cm_main/navbar.html:66 msgid "Chat" msgstr "Chat" -#: templates/cm_main/navbar.html:67 +#: templates/cm_main/navbar.html:69 msgid "Public Chat Rooms" msgstr "Öffentliche Chaträume" -#: templates/cm_main/navbar.html:69 +#: templates/cm_main/navbar.html:71 msgid "Private Chat Rooms" msgstr "Privat-Chat-Rooms" -#: templates/cm_main/navbar.html:92 +#: templates/cm_main/navbar.html:77 +msgid "Troves" +msgstr "Schätze" + +#: templates/cm_main/navbar.html:99 msgid "Change language" msgstr "Sprache ändern" -#: templates/cm_main/navbar.html:112 +#: templates/cm_main/navbar.html:119 msgid "Edit Pages" msgstr "Bearbeiten Sie Seiten" -#: templates/cm_main/navbar.html:114 +#: templates/cm_main/navbar.html:121 msgid "Import members from CSV" msgstr "Importiere Mitglieder aus CSV" -#: templates/cm_main/navbar.html:115 +#: templates/cm_main/navbar.html:122 msgid "Admin site" msgstr "Verwaltungsseite" -#: templates/cm_main/navbar.html:118 +#: templates/cm_main/navbar.html:125 msgid "Export Members as CSV" msgstr "Exportiere Mitglieder als CSV" -#: templates/cm_main/navbar.html:136 +#: templates/cm_main/navbar.html:143 msgid "About the site" msgstr "Über den Site" -#: templates/cm_main/navbar.html:147 +#: templates/cm_main/navbar.html:154 msgid "Sign in" msgstr "Anmelden" -#: templates/cm_main/navbar.html:151 +#: templates/cm_main/navbar.html:158 msgid "Request invitation link" msgstr "Bitte um Einladungslink" -#: templates/cm_main/navbar.html:160 +#: templates/cm_main/navbar.html:167 msgid "Profile" msgstr "Profil" -#: templates/cm_main/navbar.html:162 +#: templates/cm_main/navbar.html:169 msgid "Log Out" msgstr "Ausloggen" diff --git a/cm_main/locale/de/LC_MESSAGES/djangojs.mo b/cm_main/locale/de/LC_MESSAGES/djangojs.mo index d331451e..f6f0a36f 100644 Binary files a/cm_main/locale/de/LC_MESSAGES/djangojs.mo and b/cm_main/locale/de/LC_MESSAGES/djangojs.mo differ diff --git a/cm_main/locale/de/LC_MESSAGES/djangojs.po b/cm_main/locale/de/LC_MESSAGES/djangojs.po index 32508153..16044858 100644 --- a/cm_main/locale/de/LC_MESSAGES/djangojs.po +++ b/cm_main/locale/de/LC_MESSAGES/djangojs.po @@ -5,17 +5,16 @@ # using the qwen2.5:3b model. Depending on the model, it may contain some errors and should be reviewed # by a human translator. Also depending on the model, each translation can be preceded by an explanation provided # by the model. -# , 2024. +# Olivier LEVILLAIN , 2024. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: Cousins Matter 0.1.0\n" -"Report-Msgid-Bugs-To: \n" +"Report-Msgid-Bugs-To: Olivier LEVILLAIN \n" "POT-Creation-Date: 2024-04-28 16:06+0200\n" -"PO-Revision-Date: 2024-10-21 09:03+00:00\n" -"\n" -"Last-Translator: Auto-po-lyglot using qwen2.5:3b (https://github.com/leolivier/auto-po-lyglot)\n" +"PO-Revision-Date: 2024-12-30 12:56+0100\n" +"Last-Translator: Olivier LEVILLAIN \n" "Language-Team: French \n" "Language: DE\n" "MIME-Version: 1.0\n" @@ -136,3 +135,6 @@ msgstr[1] "%s commentaires" #. meaning. msgid "profile" msgstr "Profil" + +msgid "Are you sure you want to delete this treasure?" +msgstr "Bist du sicher, dass du diesen Schatz löschen möchtest?" diff --git a/cm_main/locale/es/LC_MESSAGES/django.mo b/cm_main/locale/es/LC_MESSAGES/django.mo index e38ed702..91de1b9b 100644 Binary files a/cm_main/locale/es/LC_MESSAGES/django.mo and b/cm_main/locale/es/LC_MESSAGES/django.mo differ diff --git a/cm_main/locale/es/LC_MESSAGES/django.po b/cm_main/locale/es/LC_MESSAGES/django.po index a5edb7bf..2232fa33 100644 --- a/cm_main/locale/es/LC_MESSAGES/django.po +++ b/cm_main/locale/es/LC_MESSAGES/django.po @@ -12,7 +12,7 @@ msgid "" msgstr "" "Project-Id-Version: Cousins Matter 0.1.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-11 19:58+0100\n" +"POT-Creation-Date: 2024-12-30 16:46+0100\n" "PO-Revision-Date: 2024-10-21 09:13+00:00\n" "Last-Translator: Auto-po-lyglot using qwen2.5:3b (https://github.com/" "leolivier/auto-po-lyglot)\n" @@ -24,16 +24,16 @@ msgstr "" "\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: cm_main/apps.py:8 +#: apps.py:8 msgid "Cousins Matter!" msgstr "Los Cousinés Contan!" -#: cm_main/followers.py:61 cm_main/tests/tests_followers.py:80 +#: followers.py:61 tests/tests_followers.py:80 #, python-format msgid "New %(followed_type)s \"%(followed_object_name)s\"" msgstr "Nuevo %(followed_type)s \"%(followed_object_name)s" -#: cm_main/followers.py:65 +#: followers.py:65 #, python-format msgid "" "%(follower_name)s has created a new %(followed_type)s " @@ -42,14 +42,14 @@ msgstr "" "%(follower_name)s ha creado un nuevo %(followed_type)s " "\"%(followed_object_name)s\"" -#: cm_main/followers.py:70 cm_main/tests/tests_followers.py:92 +#: followers.py:70 tests/tests_followers.py:92 #, python-format msgid "" "New %(obj_type)s added to %(followed_type)s \"%(followed_object_name)s\"" msgstr "" "Nuevo %(obj_type)s añadido al %(followed_type)s \"%(followed_object_name)s\"" -#: cm_main/followers.py:75 +#: followers.py:75 #, python-format msgid "" "%(follower_name)s has added a new %(obj_type)s in the %(followed_type)s " @@ -58,22 +58,22 @@ msgstr "" "%(follower_name)s ha añadido un nuevo %(obj_type)s al %(followed_type)s " "\"%(followed_object_name)s\"" -#: cm_main/followers.py:108 +#: followers.py:108 #, python-brace-format msgid "You are no longer following this {followed_type}" msgstr "¡No sigues más este {followed_type}" -#: cm_main/followers.py:111 +#: followers.py:111 #, python-brace-format msgid "You are now following this {followed_type}" msgstr "¡Estás ahora siguiendo este {followed_type}" -#: cm_main/followers.py:119 cm_main/tests/tests_followers.py:29 +#: followers.py:119 tests/tests_followers.py:29 #, python-format msgid "New follower to your %(followed_type)s \"%(followed_object_name)s\"" msgstr "Nuevo seguidor a tu %(followed_type)s \"%(followed_object_name)s\"" -#: cm_main/followers.py:121 +#: followers.py:121 #, python-format msgid "" "%(follower_name)s is now following your %(followed_type)s " @@ -82,68 +82,67 @@ msgstr "" "%(follower_name)s ahora sigue tu %(followed_type)s " "\"%(followed_object_name)s\"" -#: cm_main/forms.py:11 +#: forms.py:11 msgid "Your message" msgstr "Tu mensaje" -#: cm_main/forms.py:12 +#: forms.py:12 msgid "Please keep it short and avoid images." msgstr "Por favor, manténlo breve y evita las imágenes." -#: cm_main/forms.py:13 +#: forms.py:13 msgid "Attach file" msgstr "Atachar archivo" -#: cm_main/forms.py:14 +#: forms.py:14 msgid "You can attach a file here if needed" msgstr "Puedes adjuntar un archivo aquí si es necesario" -#: cm_main/templates/cm_main/about/site-stats.html:3 -#: cm_main/templates/cm_main/about/site-stats.html:6 +#: templates/cm_main/about/site-stats.html:3 +#: templates/cm_main/about/site-stats.html:6 msgid "Site Statistics" msgstr "Estadísticas del sitio" -#: cm_main/templates/cm_main/about/site-stats.html:7 +#: templates/cm_main/about/site-stats.html:7 msgid "This page displays some statistics about this site:" msgstr "Esta página muestra algunas estadísticas sobre este sitio :" -#: cm_main/templates/cm_main/about/site-stats.html:9 +#: templates/cm_main/about/site-stats.html:9 msgid "Statistics" msgstr "Estadísticas" -#: cm_main/templates/cm_main/base.html:8 +#: templates/cm_main/base.html:9 msgid "Home" msgstr "Aceptar" -#: cm_main/templates/cm_main/base.html:68 +#: templates/cm_main/base.html:69 msgid "This site proudly built using" msgstr "This site is proudly powered by" -#: cm_main/templates/cm_main/base.html:70 +#: templates/cm_main/base.html:71 msgid "The source code is licensed" msgstr "El código fuente está bajo licencia" -#: cm_main/templates/cm_main/base.html:76 +#: templates/cm_main/base.html:77 msgid "Copyright © 2024 Cousins Matter. All rights reserved." msgstr "Copyright © 2024 Cousins Matter. Todos los derechos reservados." -#: cm_main/templates/cm_main/base.html:79 -#: cm_main/templates/cm_main/navbar.html:138 +#: templates/cm_main/base.html:80 templates/cm_main/navbar.html:145 msgid "Contact the site admin" msgstr "Contatar al administrador del sitio" -#: cm_main/templates/cm_main/common/confirm-delete-modal.html:28 -#: cm_main/templates/cm_main/common/confirm-delete-modal.html:54 +#: templates/cm_main/common/confirm-delete-modal.html:28 +#: templates/cm_main/common/confirm-delete-modal.html:54 msgid "Confirm" msgstr "Confirmar" -#: cm_main/templates/cm_main/common/confirm-delete-modal.html:32 -#: cm_main/templates/cm_main/common/confirm-delete-modal.html:58 -#: cm_main/templates/cm_main/contact/contact-form.html:16 +#: templates/cm_main/common/confirm-delete-modal.html:32 +#: templates/cm_main/common/confirm-delete-modal.html:58 +#: templates/cm_main/contact/contact-form.html:16 msgid "Cancel" msgstr "Anular" -#: cm_main/templates/cm_main/common/confirm-delete-modal.html:40 +#: templates/cm_main/common/confirm-delete-modal.html:40 #, python-format msgid "" "Enter \"%(expected_value)s\" in the " @@ -152,71 +151,71 @@ msgstr "" "Saisí \"%(expected_value)s\" en el " "campo debajo antes de presionar confirmar." -#: cm_main/templates/cm_main/common/confirm-delete-modal.html:48 +#: templates/cm_main/common/confirm-delete-modal.html:48 msgid "" "Mandatory. Deletion will not take place until the correct value is entered." msgstr "" "Obligatorio. La eliminación no tendrá lugar hasta que se ingrese la valor " "correcta." -#: cm_main/templates/cm_main/common/include-summernote.html:6 +#: templates/cm_main/common/include-summernote.html:6 msgid "Toggle Note Editor Toolbar" msgstr "Affiché/Ocultar la barra de herramientas" -#: cm_main/templates/cm_main/common/include-summernote.html:7 +#: templates/cm_main/common/include-summernote.html:7 msgid "Remaining characters:" msgstr "Caracteres restantes:" -#: cm_main/templates/cm_main/common/paginate_template.html:20 +#: templates/cm_main/common/paginate_template.html:25 msgid "Items per page:" msgstr "Items por página" -#: cm_main/templates/cm_main/common/paginate_template.html:40 +#: templates/cm_main/common/paginate_template.html:45 msgid "go to first page" msgstr "ir a la primera página" -#: cm_main/templates/cm_main/common/paginate_template.html:52 +#: templates/cm_main/common/paginate_template.html:57 #, python-format msgid "go to page #%(page_num)s" msgstr "go a la página #%(page_num)s" -#: cm_main/templates/cm_main/common/paginate_template.html:55 +#: templates/cm_main/common/paginate_template.html:60 #, python-format msgid "page #%(page_num)s" msgstr "página #%(page_num)s" -#: cm_main/templates/cm_main/common/paginate_template.html:68 +#: templates/cm_main/common/paginate_template.html:73 msgid "go to last page" msgstr "ir a la última página" -#: cm_main/templates/cm_main/contact/contact-form.html:6 -#: cm_main/templates/cm_main/contact/contact-form.html:9 +#: templates/cm_main/contact/contact-form.html:6 +#: templates/cm_main/contact/contact-form.html:9 msgid "Contact Form" msgstr "Formulario de contacto" -#: cm_main/templates/cm_main/contact/contact-form.html:10 +#: templates/cm_main/contact/contact-form.html:10 msgid "Please fill out the form below to contact the admin of this site:" msgstr "" "Por favor complete el formulario debajo para contactar al administrador de " "este sitio :" -#: cm_main/templates/cm_main/contact/contact-form.html:15 +#: templates/cm_main/contact/contact-form.html:15 msgid "Send" msgstr "Enviar" -#: cm_main/templates/cm_main/contact/email-contact-form.html:19 -#: cm_main/tests/test_contactform.py:42 +#: templates/cm_main/contact/email-contact-form.html:19 +#: tests/test_contactform.py:42 #, python-format msgid "%(sender_name)s sent you the following message from %(site_name)s:" msgstr "%(sender_name)s te envió el siguiente mensaje desde %(site_name)s." -#: cm_main/templates/cm_main/contact/email-contact-form.html:26 +#: templates/cm_main/contact/email-contact-form.html:26 msgid "You can directly reply to this email to answer him/her." msgstr "" "Puedes responder directamente a este correo electrónico para responderle." -#: cm_main/templates/cm_main/followers/email-followers-on-change.html:20 -#: cm_main/tests/tests_followers.py:84 +#: templates/cm_main/followers/email-followers-on-change.html:20 +#: tests/tests_followers.py:84 #, python-format msgid "" "%(author_name)s created the following %(followed_type)s %(followed_object_name)s:" -#: cm_main/templates/cm_main/followers/email-followers-on-change.html:24 -#: cm_main/tests/tests_followers.py:97 +#: templates/cm_main/followers/email-followers-on-change.html:24 +#: tests/tests_followers.py:97 #, python-format msgid "" "%(author_name)s added the following %(obj_type)s on %(followed_type)s %(followed_object_name)s:" -#: cm_main/templates/cm_main/followers/email-followers-on-change.html:32 -#: cm_main/templates/cm_main/followers/new_follower.html:27 +#: templates/cm_main/followers/email-followers-on-change.html:32 +#: templates/cm_main/followers/new_follower.html:27 msgid "Do not reply to this mail, it is machine generated." msgstr "No responder a este correo electrónico, es generado por una máquina." -#: cm_main/templates/cm_main/followers/followers-count-tag.html:4 +#: templates/cm_main/followers/followers-count-tag.html:4 #, python-format msgid "%(nfollowers)s member" msgid_plural "%(nfollowers)s members" msgstr[0] "%(nfollowers)s miembro" msgstr[1] "%(nfollowers)s miembros" -#: cm_main/templates/cm_main/followers/followers-count-tag.html:10 +#: templates/cm_main/followers/followers-count-tag.html:10 #, python-format msgid "%(nfollowers)s follower" msgid_plural "%(nfollowers)s followers" msgstr[0] "%(nfollowers)s follower" msgstr[1] "%(nfollowers)s followers" -#: cm_main/templates/cm_main/followers/new_follower.html:14 +#: templates/cm_main/followers/new_follower.html:14 #, python-format msgid "Your %(followed_type)s '%(followed_object_name)s' has a new follower!" msgstr "" "¡Tu %(followed_type)s '%(followed_object_name)s' tiene un nuevo seguidor!" -#: cm_main/templates/cm_main/followers/new_follower.html:22 -#: cm_main/tests/tests_followers.py:46 +#: templates/cm_main/followers/new_follower.html:22 tests/tests_followers.py:46 #, python-format msgid "" "%(follower_name)s is now following your %(followed_type)s %(followed_object_name)s en %(site_name)s" -#: cm_main/templates/cm_main/followers/toggle-follow-button.html:4 +#: templates/cm_main/followers/toggle-follow-button.html:4 msgid "Stop Following" msgstr "Detener de seguir" -#: cm_main/templates/cm_main/followers/toggle-follow-button.html:11 +#: templates/cm_main/followers/toggle-follow-button.html:11 msgid "Follow" msgstr "Suscribirse" -#: cm_main/templates/cm_main/navbar.html:26 cm_main/views/views_stats.py:106 +#: templates/cm_main/navbar.html:28 views/views_stats.py:106 msgid "Members" msgstr "Miembros" -#: cm_main/templates/cm_main/navbar.html:29 +#: templates/cm_main/navbar.html:31 msgid "Birthdays" msgstr "Aniversarios" -#: cm_main/templates/cm_main/navbar.html:31 +#: templates/cm_main/navbar.html:33 msgid "Show directory" msgstr "Abrir el directorio" -#: cm_main/templates/cm_main/navbar.html:34 +#: templates/cm_main/navbar.html:36 msgid "Create Member" msgstr "Criar un Miembro" -#: cm_main/templates/cm_main/navbar.html:38 +#: templates/cm_main/navbar.html:40 msgid "Invite Member" msgstr "Invitar miembro" -#: cm_main/templates/cm_main/navbar.html:45 cm_main/views/views_stats.py:114 +#: templates/cm_main/navbar.html:47 views/views_stats.py:114 msgid "Galleries" msgstr "Galerías" -#: cm_main/templates/cm_main/navbar.html:48 +#: templates/cm_main/navbar.html:50 msgid "Create Gallery" msgstr "Criar una Galería" -#: cm_main/templates/cm_main/navbar.html:50 +#: templates/cm_main/navbar.html:52 msgid "Bulk Upload" msgstr "Carga masiva" -#: cm_main/templates/cm_main/navbar.html:56 +#: templates/cm_main/navbar.html:58 msgid "Forum" msgstr "Forum" -#: cm_main/templates/cm_main/navbar.html:59 +#: templates/cm_main/navbar.html:61 msgid "Create Post" msgstr "Nueva Publicación" -#: cm_main/templates/cm_main/navbar.html:64 +#: templates/cm_main/navbar.html:66 msgid "Chat" msgstr "Chat" -#: cm_main/templates/cm_main/navbar.html:67 +#: templates/cm_main/navbar.html:69 msgid "Public Chat Rooms" msgstr "Salones de discusión pública" -#: cm_main/templates/cm_main/navbar.html:69 +#: templates/cm_main/navbar.html:71 msgid "Private Chat Rooms" msgstr "Sala de chat privada" -#: cm_main/templates/cm_main/navbar.html:93 +#: templates/cm_main/navbar.html:77 +msgid "Troves" +msgstr "Troves" + +#: templates/cm_main/navbar.html:99 msgid "Change language" msgstr "Cambiar idioma" -#: cm_main/templates/cm_main/navbar.html:113 +#: templates/cm_main/navbar.html:119 msgid "Edit Pages" msgstr "Editar las Páginas" -#: cm_main/templates/cm_main/navbar.html:115 +#: templates/cm_main/navbar.html:121 msgid "Import members from CSV" msgstr "Importar miembros desde un CSV" -#: cm_main/templates/cm_main/navbar.html:118 +#: templates/cm_main/navbar.html:122 +#, fuzzy +#| msgid "Administrator" +msgid "Admin site" +msgstr "Administrador" + +#: templates/cm_main/navbar.html:125 msgid "Export Members as CSV" msgstr "Exportar los miembros como CSV" -#: cm_main/templates/cm_main/navbar.html:136 +#: templates/cm_main/navbar.html:143 msgid "About the site" msgstr "Apropos del sitio" -#: cm_main/templates/cm_main/navbar.html:147 +#: templates/cm_main/navbar.html:154 msgid "Sign in" msgstr "Conexión" -#: cm_main/templates/cm_main/navbar.html:151 +#: templates/cm_main/navbar.html:158 msgid "Request invitation link" msgstr "Demandar un enlace de invitación" -#: cm_main/templates/cm_main/navbar.html:160 +#: templates/cm_main/navbar.html:167 msgid "Profile" msgstr "Perfil" -#: cm_main/templates/cm_main/navbar.html:162 +#: templates/cm_main/navbar.html:169 msgid "Log Out" msgstr "Déconexion" -#: cm_main/tests/test_contactform.py:23 +#: tests/test_contactform.py:23 msgid "This field is required." msgstr "Este campo es obligatorio." -#: cm_main/tests/test_contactform.py:29 cm_main/views/views_contact.py:65 +#: tests/test_contactform.py:29 views/views_contact.py:65 msgid "Your message has been sent" msgstr "Tu mensaje ha sido enviado" -#: cm_main/tests/test_contactform.py:32 cm_main/views/views_contact.py:40 +#: tests/test_contactform.py:32 views/views_contact.py:40 msgid "Contact form" msgstr "Formulario de contacto" -#: cm_main/tests/test_contactform.py:39 cm_main/views/views_contact.py:37 +#: tests/test_contactform.py:39 views/views_contact.py:37 #, python-format msgid "You have a new message from %(name)s (%(email)s). " msgstr "Tienes un nuevo mensaje de %(name)s (%(email)s)." -#: cm_main/tests/tests_followers.py:27 +#: tests/tests_followers.py:27 msgid "You have a new follower!" msgstr "¡Tienes un nuevo seguidor!" -#: cm_main/tests/tests_followers.py:38 +#: tests/tests_followers.py:38 #, python-format msgid "" "Hi %(followed_name)s,
%(follower_name)s " @@ -396,27 +404,27 @@ msgstr "" "Hola %(followed_name)s,
%(follower_name)s te sigue ahora en %(site_name)s!" -#: cm_main/views/views_contact.py:41 +#: views/views_contact.py:41 msgid "But your mailer tools is too old to show it :'(" msgstr "Más tu herramienta de correo es demasiado antigua para mostrarlo :'(" -#: cm_main/views/views_contact.py:60 +#: views/views_contact.py:60 msgid "This file type is not supported" msgstr "Este tipo de archivo no está soportado" -#: cm_main/views/views_general.py:52 +#: views/views_general.py:52 msgid "Media not found" msgstr "Média no encontrada" -#: cm_main/views/views_stats.py:41 cm_main/views/views_stats.py:81 +#: views/views_stats.py:41 views/views_stats.py:81 msgid "Version not found" msgstr "Versión no encontrada" -#: cm_main/views/views_stats.py:55 +#: views/views_stats.py:55 msgid "Your version is not up-to-date." msgstr "Tu versión no está actualizada." -#: cm_main/views/views_stats.py:58 +#: views/views_stats.py:58 msgid "" "Please update it by running the following command:
docker-start.sh -" "u" @@ -424,106 +432,106 @@ msgstr "" "Por favor, actualícelo ejecutando el siguiente comando:
docker-" "start.sh -u" -#: cm_main/views/views_stats.py:69 +#: views/views_stats.py:69 msgid "Your version is up-to-date." msgstr "Tu versión está actualizada." -#: cm_main/views/views_stats.py:75 +#: views/views_stats.py:75 msgid "Your version is newer than the latest release (?!?)" msgstr "Tus versión es más reciente que la versión más reciente (?!?)" -#: cm_main/views/views_stats.py:97 +#: views/views_stats.py:97 msgid "Site" msgstr "Sitio" -#: cm_main/views/views_stats.py:99 +#: views/views_stats.py:99 msgid "Site name" msgstr "Nombre del sitio" -#: cm_main/views/views_stats.py:100 +#: views/views_stats.py:100 msgid "Site URL" msgstr "URL del sitio" -#: cm_main/views/views_stats.py:101 +#: views/views_stats.py:101 msgid "Application Version" msgstr "Versión de la aplicación" -#: cm_main/views/views_stats.py:102 +#: views/views_stats.py:102 msgid "Latest release" msgstr "Dernière versión" -#: cm_main/views/views_stats.py:108 +#: views/views_stats.py:108 msgid "Total number of members" msgstr "Nombre total de miembros" -#: cm_main/views/views_stats.py:109 +#: views/views_stats.py:109 msgid "Number of active members" msgstr "Número de miembros activos" -#: cm_main/views/views_stats.py:110 +#: views/views_stats.py:110 msgid "Number of managed members" msgstr "Nombre de miembros gestionados" -#: cm_main/views/views_stats.py:116 +#: views/views_stats.py:116 msgid "Number of galleries" msgstr "Número de galerías" -#: cm_main/views/views_stats.py:117 +#: views/views_stats.py:117 msgid "Number of photos" msgstr "Número de fotos" -#: cm_main/views/views_stats.py:121 +#: views/views_stats.py:121 msgid "Forums" msgstr "Forums" -#: cm_main/views/views_stats.py:123 +#: views/views_stats.py:123 msgid "Number of posts" msgstr "Número de publicaciones" -#: cm_main/views/views_stats.py:124 +#: views/views_stats.py:124 msgid "Number of post messages" msgstr "Número de mensajes en las discusiones" -#: cm_main/views/views_stats.py:125 +#: views/views_stats.py:125 msgid "Number of message comments" msgstr "Número de comentarios de mensajes" -#: cm_main/views/views_stats.py:129 +#: views/views_stats.py:129 msgid "Chats" msgstr "Salones de discusión" -#: cm_main/views/views_stats.py:131 +#: views/views_stats.py:131 msgid "Number of chat rooms" msgstr "Número de salones de discusión" -#: cm_main/views/views_stats.py:132 +#: views/views_stats.py:132 msgid "Number of public chat rooms" msgstr "Número de salones de discusión pública" -#: cm_main/views/views_stats.py:133 +#: views/views_stats.py:133 msgid "Number of private chat rooms" msgstr "Número de salas de chat privadas" -#: cm_main/views/views_stats.py:134 +#: views/views_stats.py:134 msgid "Number of chat messages" msgstr "Número de mensajes en los chats" -#: cm_main/views/views_stats.py:135 +#: views/views_stats.py:135 msgid "Number of private chat messages" msgstr "Número de mensajes privados" -#: cm_main/views/views_stats.py:136 +#: views/views_stats.py:136 msgid "Number of public chat messages" msgstr "Número de mensajes en los chats públicos" -#: cm_main/views/views_stats.py:140 +#: views/views_stats.py:140 msgid "Administrator" msgstr "Administrador" -#: cm_main/views/views_stats.py:142 +#: views/views_stats.py:142 msgid "This site is managed by" msgstr "Este sitio está gestionado por" -#: cm_main/views/views_stats.py:143 +#: views/views_stats.py:143 msgid "Administrator email" msgstr "Correo electrónico del administrador" diff --git a/cm_main/locale/es/LC_MESSAGES/djangojs.mo b/cm_main/locale/es/LC_MESSAGES/djangojs.mo index 74492b28..47a2d032 100644 Binary files a/cm_main/locale/es/LC_MESSAGES/djangojs.mo and b/cm_main/locale/es/LC_MESSAGES/djangojs.mo differ diff --git a/cm_main/locale/es/LC_MESSAGES/djangojs.po b/cm_main/locale/es/LC_MESSAGES/djangojs.po index 01ac960c..25c45de6 100644 --- a/cm_main/locale/es/LC_MESSAGES/djangojs.po +++ b/cm_main/locale/es/LC_MESSAGES/djangojs.po @@ -5,158 +5,76 @@ # using the qwen2.5:3b model. Depending on the model, it may contain some errors and should be reviewed # by a human translator. Also depending on the model, each translation can be preceded by an explanation provided # by the model. -# , 2024. +# Olivier LEVILLAIN , 2024. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: Cousins Matter 0.1.0\n" -"Report-Msgid-Bugs-To: \n" +"Report-Msgid-Bugs-To: Olivier LEVILLAIN \n" "POT-Creation-Date: 2024-04-28 16:06+0200\n" -"PO-Revision-Date: 2024-10-21 09:12+00:00\n" -"\n" -"Last-Translator: Auto-po-lyglot using qwen2.5:3b (https://github.com/leolivier/auto-po-lyglot)\n" -"Language-Team: French \n" +"PO-Revision-Date: 2024-12-30 12:56+0100\n" +"Last-Translator: Olivier LEVILLAIN \n" +"Language-Team: Spanish \n" "Language: ES\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -#. Explanation: The provided English and French sentences are not directly -#. related. The English sentence "Today" translates to "Aujourd'hui" in -#. French, but the context translation given is for "Aujourd'hui," which means -#. "Today." Therefore, there seems to be a mismatch or error in the input data -#. as both sentences should ideally match. Given the instructions, I have -#. provided the Spanish translation of "Today" based on the French sentence, -#. which would be "Aujourd'hui." However, since this does not align with the -#. context translation, an explanation is necessary. msgid "Today" -msgstr "Ayer" - -#. Explanation: The English word "Clear" can have multiple meanings depending -#. on the context. In this case, given the French translation which is -#. "Effacer," it's clear that "Clear" refers to the action of erasing or -#. removing something, such as clearing a screen, document, or memory. -#. Therefore, in Spanish, "Eliminar" accurately conveys this meaning and -#. usage. +msgstr "Hoy" + msgid "Clear" -msgstr "Eliminar" +msgstr "Claro" -#. Explanation: The English word "Cancel" and the French word "Annuler" both -#. refer to the action of voiding or stopping a previously scheduled event, -#. order, or payment. Therefore, the Spanish term "Anular" accurately captures -#. this meaning without any ambiguity. msgid "Cancel" -msgstr "Anular" +msgstr "Cancelar" -#. Explanation: This Spanish translation accurately conveys the meaning of the -#. French phrase "Maintenant," which simply means "now." The term "Now" in -#. English is used similarly to express a sense of immediacy or current time, -#. and it maintains the same straightforwardness as its French counterpart. msgid "Now" msgstr "Ahora" -#. Explanation: The English word "Validate" and its French counterpart -#. "Valider" both mean the same thing in this context. They are used to -#. indicate the act of confirming or verifying the accuracy, truthfulness, or -#. validity of something. Therefore, no translation is needed as they maintain -#. identical meanings across these languages. msgid "Validate" msgstr "Validar" -#. Explanation: The English word "Delete" and the French word "Supprimer" both -#. convey the meaning of erasing or removing something. In Spanish, this -#. concept is expressed with the term "Eliminar," which maintains the same -#. core meaning as its counterparts in English and French. msgid "Delete" -msgstr "Eliminar" +msgstr "Borrar" -#. Explanation: This Spanish translation accurately conveys the meaning of the -#. provided French sentence. The key elements are preserved, including the use -#. of "sûr(e)" for both masculine and feminine forms to match the English -#. "sure," and "commentaire" is translated as "comentario." The structure and -#. tone remain consistent with the original sentences. msgid "Are you sure you want to delete this comment?" msgstr "¿Estás seguro(a) de querer eliminar este comentario?" -#. Explanation: This Spanish translation accurately conveys the meaning of the -#. provided French sentence. The key elements are preserved, including "Are -#. you sure," "you want to delete," and the specific mention of a reply along -#. with its associated comments. The tone remains formal and requests -#. confirmation on an action that involves removing both a message and related -#. feedback. msgid "Are you sure you want to delete this reply and its comments?" msgstr "¿Estás seguro(a) de querer eliminar esta respuesta y sus comentarios?" -#. Explanation: This Spanish translation accurately conveys the meaning of the -#. provided English sentence and its French context translation. Both -#. sentences convey that the message content is excessively large and -#. instructs the user to select smaller images. The structure and phrasing are -#. maintained to ensure consistency with the original text. msgid "The message content is too large. Please select smaller images." msgstr "" "El contenido del mensaje es demasiado grande. Por favor, seleccione imágenes" " más pequeñas." -#. Explanation: This Spanish translation accurately conveys the meaning of the -#. original English sentence, maintaining the same tone and nuance. The phrase -#. "Are you sure" is translated as "¿Estás seguro(a)?", which in this context -#. means "¿Estás seguro(a)?" (are you sure). Similarly, "want to delete" -#. becomes "de querer eliminar," preserving the meaning of wanting or being -#. inclined to perform an action. The French translation "Es-tu sûr(e) de -#. vouloir supprimer ce message?" is used as a contextual reference, ensuring -#. that the core meaning and structure are preserved in Spanish. msgid "Are you sure you want to delete this message?" msgstr "¿Estás seguro(a) de querer eliminar este mensaje?" -#. Explanation: This Spanish translation accurately reflects the meaning of -#. the French phrase, which indicates that a deletion has been confirmed. The -#. structure and wording are consistent between both languages, maintaining -#. the formal tone appropriate for such an official communication. msgid "Deletion confirmed..." msgstr "Supresión confirmada..." -#. Explanation: This Spanish translation accurately conveys the meaning of the -#. French phrase, which indicates that the message is devoid of content or -#. information. The English exclamation mark at the end of the sentence in the -#. original text is preserved to maintain its tone and emphasis. msgid "The message was empty!" msgstr "El mensaje está vacío!" -#. Explanation: This Spanish translation accurately conveys the meaning of the -#. French phrase, which indicates that something is invalid. The English -#. phrase "Not valid!" also means that something is invalid or not acceptable, -#. so the Spanish version "No válido!" maintains this meaning and tone. msgid "Not valid!" msgstr "No válido!" -#. Explanation: The placeholder format used in the English and French -#. sentences is identical ("%s"). This means that both sentences are -#. placeholders for a word or phrase. Since the context translations are also -#. identical, it indicates that the content of the placeholders is likely to -#. be the same (e.g., "answer" in this case). Therefore, the Spanish -#. translation should maintain the same structure without changing the -#. placeholder format. msgid "%s answer" msgid_plural "%s answers" -msgstr[0] "%s réponse" -msgstr[1] "%s réponses" +msgstr[0] "%s respuesta" +msgstr[1] "%s repuestas" -#. Explanation: The placeholder format used in the English and French -#. sentences is identical ("%s"). This indicates that both sentences are -#. placeholders for a comment or remark. Therefore, the Spanish translation -#. remains unchanged to maintain consistency with the original meaning. msgid "%s comment" msgid_plural "%s comments" -msgstr[0] "%s commentaire" -msgstr[1] "%s commentaires" - -#. Explanation: This translation is straightforward as both the English and -#. French terms are direct translations of each other. The term "profile" in -#. English corresponds directly to "profil" in French, referring to a profile -#. or an account profile typically found on social media platforms, websites, -#. or applications where users can create their profiles with personal -#. information and settings. +msgstr[0] "%s comentario" +msgstr[1] "%s comentarios" + msgid "profile" -msgstr "profile" +msgstr "perfil" + +msgid "Are you sure you want to delete this treasure?" +msgstr "¿Estás seguro(a) de querer eliminar este tesoro?" diff --git a/cm_main/locale/fr/LC_MESSAGES/django.mo b/cm_main/locale/fr/LC_MESSAGES/django.mo index 89c6ba6e..442984f6 100644 Binary files a/cm_main/locale/fr/LC_MESSAGES/django.mo and b/cm_main/locale/fr/LC_MESSAGES/django.mo differ diff --git a/cm_main/locale/fr/LC_MESSAGES/django.po b/cm_main/locale/fr/LC_MESSAGES/django.po index 0a468fe1..ef086ce7 100644 --- a/cm_main/locale/fr/LC_MESSAGES/django.po +++ b/cm_main/locale/fr/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Cousins Matter 0.1.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-21 08:54+0200\n" +"POT-Creation-Date: 2024-12-30 16:46+0100\n" "PO-Revision-Date: 2024-12-24 12:09+0100\n" "Last-Translator: Olivier LEVILLAIN \n" "Language-Team: French \n" @@ -106,23 +106,23 @@ msgstr "Cette page affiche quelques statistiques sur ce site :" msgid "Statistics" msgstr "Statistiques" -#: templates/cm_main/base.html:8 +#: templates/cm_main/base.html:9 msgid "Home" msgstr "Accueil" -#: templates/cm_main/base.html:68 +#: templates/cm_main/base.html:69 msgid "This site proudly built using" msgstr "Ce site est fièrement propulsé par" -#: templates/cm_main/base.html:70 +#: templates/cm_main/base.html:71 msgid "The source code is licensed" msgstr "Le code source est sous licence" -#: templates/cm_main/base.html:76 +#: templates/cm_main/base.html:77 msgid "Copyright © 2024 Cousins Matter. All rights reserved." msgstr "Copyright © 2024 Cousins Matter. Tous droits réservés." -#: templates/cm_main/base.html:79 templates/cm_main/navbar.html:138 +#: templates/cm_main/base.html:80 templates/cm_main/navbar.html:145 msgid "Contact the site admin" msgstr "Contacter l'administrateur du site" @@ -161,25 +161,25 @@ msgstr "Afficher/Masquer la barre d'outils" msgid "Remaining characters:" msgstr "Caractères restants:" -#: templates/cm_main/common/paginate_template.html:27 +#: templates/cm_main/common/paginate_template.html:25 msgid "Items per page:" msgstr "Items par page" -#: templates/cm_main/common/paginate_template.html:47 +#: templates/cm_main/common/paginate_template.html:45 msgid "go to first page" msgstr "aller à la 1ère page" -#: templates/cm_main/common/paginate_template.html:59 +#: templates/cm_main/common/paginate_template.html:57 #, python-format msgid "go to page #%(page_num)s" msgstr "aller à la page #%(page_num)s" -#: templates/cm_main/common/paginate_template.html:62 +#: templates/cm_main/common/paginate_template.html:60 #, python-format msgid "page #%(page_num)s" msgstr "page #%(page_num)s" -#: templates/cm_main/common/paginate_template.html:75 +#: templates/cm_main/common/paginate_template.html:73 msgid "go to last page" msgstr "aller à la dernière page" @@ -270,95 +270,99 @@ msgstr "Arréter de suivre" msgid "Follow" msgstr "Suivre" -#: templates/cm_main/navbar.html:26 views/views_stats.py:106 +#: templates/cm_main/navbar.html:28 views/views_stats.py:106 msgid "Members" msgstr "Membres" -#: templates/cm_main/navbar.html:29 +#: templates/cm_main/navbar.html:31 msgid "Birthdays" msgstr "Anniversaires" -#: templates/cm_main/navbar.html:31 +#: templates/cm_main/navbar.html:33 msgid "Show directory" msgstr "Afficher l'annuaire" -#: templates/cm_main/navbar.html:34 +#: templates/cm_main/navbar.html:36 msgid "Create Member" msgstr "Créer un Membre" -#: templates/cm_main/navbar.html:38 +#: templates/cm_main/navbar.html:40 msgid "Invite Member" msgstr "Inviter un Membre" -#: templates/cm_main/navbar.html:45 views/views_stats.py:114 +#: templates/cm_main/navbar.html:47 views/views_stats.py:114 msgid "Galleries" msgstr "Galeries" -#: templates/cm_main/navbar.html:48 +#: templates/cm_main/navbar.html:50 msgid "Create Gallery" msgstr "Créer une Galerie" -#: templates/cm_main/navbar.html:50 +#: templates/cm_main/navbar.html:52 msgid "Bulk Upload" msgstr "Chargement en masse" -#: templates/cm_main/navbar.html:56 +#: templates/cm_main/navbar.html:58 msgid "Forum" msgstr "Forum" -#: templates/cm_main/navbar.html:59 +#: templates/cm_main/navbar.html:61 msgid "Create Post" msgstr "Nouvelle Discussion" -#: templates/cm_main/navbar.html:64 +#: templates/cm_main/navbar.html:66 msgid "Chat" msgstr "Chat" -#: templates/cm_main/navbar.html:67 +#: templates/cm_main/navbar.html:69 msgid "Public Chat Rooms" msgstr "Salons de discussion publique" -#: templates/cm_main/navbar.html:69 +#: templates/cm_main/navbar.html:71 msgid "Private Chat Rooms" msgstr "Salons de discussion privée" -#: templates/cm_main/navbar.html:92 +#: templates/cm_main/navbar.html:77 +msgid "Troves" +msgstr "Trésors" + +#: templates/cm_main/navbar.html:99 msgid "Change language" msgstr "Changer de langue" -#: templates/cm_main/navbar.html:112 +#: templates/cm_main/navbar.html:119 msgid "Edit Pages" msgstr "Editer les Pages" -#: templates/cm_main/navbar.html:114 +#: templates/cm_main/navbar.html:121 msgid "Import members from CSV" msgstr "Importer des membres depuis un CSV" -#: templates/cm_main/navbar.html:115 +#: templates/cm_main/navbar.html:122 msgid "Admin site" msgstr "Site d'Administration" -#: templates/cm_main/navbar.html:118 +#: templates/cm_main/navbar.html:125 msgid "Export Members as CSV" msgstr "Exporter les membres en CSV" -#: templates/cm_main/navbar.html:136 +#: templates/cm_main/navbar.html:143 msgid "About the site" msgstr "A propos du site" -#: templates/cm_main/navbar.html:147 +#: templates/cm_main/navbar.html:154 msgid "Sign in" msgstr "Connexion" -#: templates/cm_main/navbar.html:151 +#: templates/cm_main/navbar.html:158 msgid "Request invitation link" msgstr "Demander un lien d'invitation" -#: templates/cm_main/navbar.html:160 +#: templates/cm_main/navbar.html:167 msgid "Profile" msgstr "Profil" -#: templates/cm_main/navbar.html:162 +#: templates/cm_main/navbar.html:169 msgid "Log Out" msgstr "Déconnexion" diff --git a/cm_main/locale/fr/LC_MESSAGES/djangojs.mo b/cm_main/locale/fr/LC_MESSAGES/djangojs.mo index b22a86b8..6257859f 100644 Binary files a/cm_main/locale/fr/LC_MESSAGES/djangojs.mo and b/cm_main/locale/fr/LC_MESSAGES/djangojs.mo differ diff --git a/cm_main/locale/fr/LC_MESSAGES/djangojs.po b/cm_main/locale/fr/LC_MESSAGES/djangojs.po index 5e44459e..8e0a9e13 100644 --- a/cm_main/locale/fr/LC_MESSAGES/djangojs.po +++ b/cm_main/locale/fr/LC_MESSAGES/djangojs.po @@ -1,15 +1,15 @@ # French translation of cousins_matter for javascript usage. # Copyright (C) 2024 Olivier LEVILLAIN # This file is distributed under the same license as the Cousins Matter package. -# , 2024. +# Olivier LEVILLAIN , 2024. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: Cousins Matter 0.1.0\n" -"Report-Msgid-Bugs-To: \n" +"Report-Msgid-Bugs-To: Olivier LEVILLAIN \n" "POT-Creation-Date: 2024-04-28 16:06+0200\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"PO-Revision-Date: 2024-12-30 12:56+0100\n" "Last-Translator: Olivier LEVILLAIN \n" "Language-Team: French \n" "Language: \n" @@ -69,3 +69,6 @@ msgstr[1] "%s commentaires" msgid "profile" msgstr "profil" + +msgid "Are you sure you want to delete this treasure?" +msgstr "Es-tu sûr(e) de vouloir supprimer ce trésor?" diff --git a/cm_main/locale/it/LC_MESSAGES/django.mo b/cm_main/locale/it/LC_MESSAGES/django.mo index c74c1573..4fe74689 100644 Binary files a/cm_main/locale/it/LC_MESSAGES/django.mo and b/cm_main/locale/it/LC_MESSAGES/django.mo differ diff --git a/cm_main/locale/it/LC_MESSAGES/django.po b/cm_main/locale/it/LC_MESSAGES/django.po index 85fdabb8..e51887dd 100644 --- a/cm_main/locale/it/LC_MESSAGES/django.po +++ b/cm_main/locale/it/LC_MESSAGES/django.po @@ -12,7 +12,7 @@ msgid "" msgstr "" "Project-Id-Version: Cousins Matter 0.1.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-11 19:57+0100\n" +"POT-Creation-Date: 2024-12-30 16:46+0100\n" "PO-Revision-Date: 2024-10-21 10:16+00:00\n" "Last-Translator: Auto-po-lyglot using qwen2.5:3b (https://github.com/" "leolivier/auto-po-lyglot)\n" @@ -111,23 +111,23 @@ msgstr "Questa pagina mostra alcune statistiche su questo sito:" msgid "Statistics" msgstr "Statistiche" -#: templates/cm_main/base.html:8 +#: templates/cm_main/base.html:9 msgid "Home" msgstr "Home" -#: templates/cm_main/base.html:68 +#: templates/cm_main/base.html:69 msgid "This site proudly built using" msgstr "Questo sito è orgogliosamente realizzato con" -#: templates/cm_main/base.html:70 +#: templates/cm_main/base.html:71 msgid "The source code is licensed" msgstr "Il codice sorgente è concesso in licenza" -#: templates/cm_main/base.html:76 +#: templates/cm_main/base.html:77 msgid "Copyright © 2024 Cousins Matter. All rights reserved." msgstr "Copyright © 2024 Cousins Matter. Tutti i diritti riservati." -#: templates/cm_main/base.html:79 templates/cm_main/navbar.html:138 +#: templates/cm_main/base.html:80 templates/cm_main/navbar.html:145 msgid "Contact the site admin" msgstr "Contattare l'amministratore del sito" @@ -166,25 +166,25 @@ msgstr "Mostra/Nascondi la barra degli strumenti" msgid "Remaining characters:" msgstr "Caratteri rimanenti:" -#: templates/cm_main/common/paginate_template.html:27 +#: templates/cm_main/common/paginate_template.html:25 msgid "Items per page:" msgstr "Elementi per pagina:" -#: templates/cm_main/common/paginate_template.html:47 +#: templates/cm_main/common/paginate_template.html:45 msgid "go to first page" msgstr "vai alla prima pagina" -#: templates/cm_main/common/paginate_template.html:59 +#: templates/cm_main/common/paginate_template.html:57 #, python-format msgid "go to page #%(page_num)s" msgstr "vai alla pagina #%(page_num)s" -#: templates/cm_main/common/paginate_template.html:62 +#: templates/cm_main/common/paginate_template.html:60 #, python-format msgid "page #%(page_num)s" msgstr "pagina #%(page_num)s" -#: templates/cm_main/common/paginate_template.html:75 +#: templates/cm_main/common/paginate_template.html:73 msgid "go to last page" msgstr "Vai all'ultima pagina" @@ -275,95 +275,99 @@ msgstr "Smetti di seguire" msgid "Follow" msgstr "Segui" -#: templates/cm_main/navbar.html:26 views/views_stats.py:106 +#: templates/cm_main/navbar.html:28 views/views_stats.py:106 msgid "Members" msgstr "Membri" -#: templates/cm_main/navbar.html:29 +#: templates/cm_main/navbar.html:31 msgid "Birthdays" msgstr "Compleanni" -#: templates/cm_main/navbar.html:31 +#: templates/cm_main/navbar.html:33 msgid "Show directory" msgstr "Mostra l'elenco" -#: templates/cm_main/navbar.html:34 +#: templates/cm_main/navbar.html:36 msgid "Create Member" msgstr "Crea Membro" -#: templates/cm_main/navbar.html:38 +#: templates/cm_main/navbar.html:40 msgid "Invite Member" msgstr "Invita un Membro" -#: templates/cm_main/navbar.html:45 views/views_stats.py:114 +#: templates/cm_main/navbar.html:47 views/views_stats.py:114 msgid "Galleries" msgstr "Gallerie" -#: templates/cm_main/navbar.html:48 +#: templates/cm_main/navbar.html:50 msgid "Create Gallery" msgstr "Crea Galleria" -#: templates/cm_main/navbar.html:50 +#: templates/cm_main/navbar.html:52 msgid "Bulk Upload" msgstr "Caricamento di massa" -#: templates/cm_main/navbar.html:56 +#: templates/cm_main/navbar.html:58 msgid "Forum" msgstr "Forum" -#: templates/cm_main/navbar.html:59 +#: templates/cm_main/navbar.html:61 msgid "Create Post" msgstr "Crea discussione" -#: templates/cm_main/navbar.html:64 +#: templates/cm_main/navbar.html:66 msgid "Chat" msgstr "Chat" -#: templates/cm_main/navbar.html:67 +#: templates/cm_main/navbar.html:69 msgid "Public Chat Rooms" msgstr "Chat room pubbliche" -#: templates/cm_main/navbar.html:69 +#: templates/cm_main/navbar.html:71 msgid "Private Chat Rooms" msgstr "Stanze di chat private" -#: templates/cm_main/navbar.html:92 +#: templates/cm_main/navbar.html:77 +msgid "Troves" +msgstr "Tesori" + +#: templates/cm_main/navbar.html:99 msgid "Change language" msgstr "Cambia lingua" -#: templates/cm_main/navbar.html:112 +#: templates/cm_main/navbar.html:119 msgid "Edit Pages" msgstr "Modifica Pagine" -#: templates/cm_main/navbar.html:114 +#: templates/cm_main/navbar.html:121 msgid "Import members from CSV" msgstr "Importa membri da CSV" -#: templates/cm_main/navbar.html:115 +#: templates/cm_main/navbar.html:122 msgid "Admin site" msgstr "Sito amministratore" -#: templates/cm_main/navbar.html:118 +#: templates/cm_main/navbar.html:125 msgid "Export Members as CSV" msgstr "Esporta membri come CSV" -#: templates/cm_main/navbar.html:136 +#: templates/cm_main/navbar.html:143 msgid "About the site" msgstr "Informazioni sul sito" -#: templates/cm_main/navbar.html:147 +#: templates/cm_main/navbar.html:154 msgid "Sign in" msgstr "Accedi" -#: templates/cm_main/navbar.html:151 +#: templates/cm_main/navbar.html:158 msgid "Request invitation link" msgstr "Richiedi link di invito" -#: templates/cm_main/navbar.html:160 +#: templates/cm_main/navbar.html:167 msgid "Profile" msgstr "Profilo" -#: templates/cm_main/navbar.html:162 +#: templates/cm_main/navbar.html:169 msgid "Log Out" msgstr "Esci" diff --git a/cm_main/locale/it/LC_MESSAGES/djangojs.mo b/cm_main/locale/it/LC_MESSAGES/djangojs.mo index 15240f3f..04f50b24 100644 Binary files a/cm_main/locale/it/LC_MESSAGES/djangojs.mo and b/cm_main/locale/it/LC_MESSAGES/djangojs.mo differ diff --git a/cm_main/locale/it/LC_MESSAGES/djangojs.po b/cm_main/locale/it/LC_MESSAGES/djangojs.po index 3eb8dc86..8e811f55 100644 --- a/cm_main/locale/it/LC_MESSAGES/djangojs.po +++ b/cm_main/locale/it/LC_MESSAGES/djangojs.po @@ -5,18 +5,17 @@ # using the qwen2.5:3b model. Depending on the model, it may contain some errors and should be reviewed # by a human translator. Also depending on the model, each translation can be preceded by an explanation provided # by the model. -# , 2024. +# Olivier LEVILLAIN , 2024. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: Cousins Matter 0.1.0\n" -"Report-Msgid-Bugs-To: \n" +"Report-Msgid-Bugs-To: Olivier LEVILLAIN \n" "POT-Creation-Date: 2024-04-28 16:06+0200\n" -"PO-Revision-Date: 2024-10-21 10:16+00:00\n" -"\n" -"Last-Translator: Auto-po-lyglot using qwen2.5:3b (https://github.com/leolivier/auto-po-lyglot)\n" -"Language-Team: French \n" +"PO-Revision-Date: 2024-12-30 12:56+0100\n" +"Last-Translator: Olivier LEVILLAIN \n" +"Language-Team: Italian \n" "Language: IT\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -76,3 +75,6 @@ msgstr[1] "%s commentaires" msgid "profile" msgstr "profilo" + +msgid "Are you sure you want to delete this treasure?" +msgstr "Sei sicuro/a di voler eliminare questo tesoro?" diff --git a/cm_main/locale/pt/LC_MESSAGES/django.mo b/cm_main/locale/pt/LC_MESSAGES/django.mo index bed8989d..25006daf 100644 Binary files a/cm_main/locale/pt/LC_MESSAGES/django.mo and b/cm_main/locale/pt/LC_MESSAGES/django.mo differ diff --git a/cm_main/locale/pt/LC_MESSAGES/django.po b/cm_main/locale/pt/LC_MESSAGES/django.po index f95793a6..ccdd0358 100644 --- a/cm_main/locale/pt/LC_MESSAGES/django.po +++ b/cm_main/locale/pt/LC_MESSAGES/django.po @@ -12,7 +12,7 @@ msgid "" msgstr "" "Project-Id-Version: Cousins Matter 0.1.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-11-11 19:57+0100\n" +"POT-Creation-Date: 2024-12-30 16:46+0100\n" "PO-Revision-Date: 2024-12-24 12:10+0100\n" "Last-Translator: Olivier LEVILLAIN\n" "Language-Team: Portuguese \n" @@ -109,23 +109,23 @@ msgstr "Esta página exibe algumas estatísticas sobre este site :" msgid "Statistics" msgstr "Estatísticas" -#: templates/cm_main/base.html:8 +#: templates/cm_main/base.html:9 msgid "Home" msgstr "Casa" -#: templates/cm_main/base.html:68 +#: templates/cm_main/base.html:69 msgid "This site proudly built using" msgstr "Este site foi construído com orgulho" -#: templates/cm_main/base.html:70 +#: templates/cm_main/base.html:71 msgid "The source code is licensed" msgstr "O código-fonte está licenciado." -#: templates/cm_main/base.html:76 +#: templates/cm_main/base.html:77 msgid "Copyright © 2024 Cousins Matter. All rights reserved." msgstr "Copyright 2024 Cousins Matter. Todos os direitos reservados." -#: templates/cm_main/base.html:79 templates/cm_main/navbar.html:138 +#: templates/cm_main/base.html:80 templates/cm_main/navbar.html:145 msgid "Contact the site admin" msgstr "Contate o administrador do site" @@ -163,25 +163,25 @@ msgstr "Barra de ferramentas do Editor de Notas" msgid "Remaining characters:" msgstr "Caracteres restantes:" -#: templates/cm_main/common/paginate_template.html:27 +#: templates/cm_main/common/paginate_template.html:25 msgid "Items per page:" msgstr "Quantidade por página" -#: templates/cm_main/common/paginate_template.html:47 +#: templates/cm_main/common/paginate_template.html:45 msgid "go to first page" msgstr "Ir para a primeira página" -#: templates/cm_main/common/paginate_template.html:59 +#: templates/cm_main/common/paginate_template.html:57 #, python-format msgid "go to page #%(page_num)s" msgstr "ir para a página #%(page_num)s" -#: templates/cm_main/common/paginate_template.html:62 +#: templates/cm_main/common/paginate_template.html:60 #, python-format msgid "page #%(page_num)s" msgstr "página #( %(page_num)s )" -#: templates/cm_main/common/paginate_template.html:75 +#: templates/cm_main/common/paginate_template.html:73 msgid "go to last page" msgstr "Ir para a última página" @@ -271,95 +271,99 @@ msgstr "Parar de seguir" msgid "Follow" msgstr "Seguir" -#: templates/cm_main/navbar.html:26 views/views_stats.py:106 +#: templates/cm_main/navbar.html:28 views/views_stats.py:106 msgid "Members" msgstr "membros" -#: templates/cm_main/navbar.html:29 +#: templates/cm_main/navbar.html:31 msgid "Birthdays" msgstr "Data de nascimento" -#: templates/cm_main/navbar.html:31 +#: templates/cm_main/navbar.html:33 msgid "Show directory" msgstr "Mostrar diretório" -#: templates/cm_main/navbar.html:34 +#: templates/cm_main/navbar.html:36 msgid "Create Member" msgstr "Criar Membro" -#: templates/cm_main/navbar.html:38 +#: templates/cm_main/navbar.html:40 msgid "Invite Member" msgstr "Convidar um Membro" -#: templates/cm_main/navbar.html:45 views/views_stats.py:114 +#: templates/cm_main/navbar.html:47 views/views_stats.py:114 msgid "Galleries" msgstr "Galérias" -#: templates/cm_main/navbar.html:48 +#: templates/cm_main/navbar.html:50 msgid "Create Gallery" msgstr "Criar uma Galeria" -#: templates/cm_main/navbar.html:50 +#: templates/cm_main/navbar.html:52 msgid "Bulk Upload" msgstr "Carregamento em massa" -#: templates/cm_main/navbar.html:56 +#: templates/cm_main/navbar.html:58 msgid "Forum" msgstr "Forum" -#: templates/cm_main/navbar.html:59 +#: templates/cm_main/navbar.html:61 msgid "Create Post" msgstr "Criar Post" -#: templates/cm_main/navbar.html:64 +#: templates/cm_main/navbar.html:66 msgid "Chat" msgstr "Chate" -#: templates/cm_main/navbar.html:67 +#: templates/cm_main/navbar.html:69 msgid "Public Chat Rooms" msgstr "Salas de chat público" -#: templates/cm_main/navbar.html:69 +#: templates/cm_main/navbar.html:71 msgid "Private Chat Rooms" msgstr "Salas de privacidade para conversação" -#: templates/cm_main/navbar.html:92 +#: templates/cm_main/navbar.html:77 +msgid "Troves" +msgstr "Tesouros" + +#: templates/cm_main/navbar.html:99 msgid "Change language" msgstr "Alterar idioma" -#: templates/cm_main/navbar.html:112 +#: templates/cm_main/navbar.html:119 msgid "Edit Pages" msgstr "Editar Páginas" -#: templates/cm_main/navbar.html:114 +#: templates/cm_main/navbar.html:121 msgid "Import members from CSV" msgstr "Importar membros de um CSV" -#: templates/cm_main/navbar.html:115 +#: templates/cm_main/navbar.html:122 msgid "Admin site" msgstr "Sito amministratore" -#: templates/cm_main/navbar.html:118 +#: templates/cm_main/navbar.html:125 msgid "Export Members as CSV" msgstr "Exportar Membros como CSV" -#: templates/cm_main/navbar.html:136 +#: templates/cm_main/navbar.html:143 msgid "About the site" msgstr "Em respeito ao site" -#: templates/cm_main/navbar.html:147 +#: templates/cm_main/navbar.html:154 msgid "Sign in" msgstr "Inscreva-se" -#: templates/cm_main/navbar.html:151 +#: templates/cm_main/navbar.html:158 msgid "Request invitation link" msgstr "Pedir um link de convite" -#: templates/cm_main/navbar.html:160 +#: templates/cm_main/navbar.html:167 msgid "Profile" msgstr "Perfil" -#: templates/cm_main/navbar.html:162 +#: templates/cm_main/navbar.html:169 msgid "Log Out" msgstr "Desconectar" diff --git a/cm_main/locale/pt/LC_MESSAGES/djangojs.mo b/cm_main/locale/pt/LC_MESSAGES/djangojs.mo index 358b1f2a..015fd1fe 100644 Binary files a/cm_main/locale/pt/LC_MESSAGES/djangojs.mo and b/cm_main/locale/pt/LC_MESSAGES/djangojs.mo differ diff --git a/cm_main/locale/pt/LC_MESSAGES/djangojs.po b/cm_main/locale/pt/LC_MESSAGES/djangojs.po index 98318b58..3183b187 100644 --- a/cm_main/locale/pt/LC_MESSAGES/djangojs.po +++ b/cm_main/locale/pt/LC_MESSAGES/djangojs.po @@ -5,145 +5,75 @@ # using the qwen2.5:3b model. Depending on the model, it may contain some errors and should be reviewed # by a human translator. Also depending on the model, each translation can be preceded by an explanation provided # by the model. -# , 2024. +# Olivier LEVILLAIN , 2024. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: Cousins Matter 0.1.0\n" -"Report-Msgid-Bugs-To: \n" +"Report-Msgid-Bugs-To: Olivier LEVILLAIN \n" "POT-Creation-Date: 2024-04-28 16:06+0200\n" -"PO-Revision-Date: 2024-10-21 10:36+00:00\n" -"\n" -"Last-Translator: Auto-po-lyglot using qwen2.5:3b (https://github.com/leolivier/auto-po-lyglot)\n" -"Language-Team: French \n" +"PO-Revision-Date: 2024-12-30 12:56+0100\n" +"Last-Translator: Olivier LEVILLAIN \n" +"Language-Team: Portuguese \n" "Language: PT\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -#. Explanation: The French translation "Aujourd'hui" is used to indicate the -#. current day, whereas the English phrase "Today" can be used in various -#. contexts. In Portuguese, "Hoje" is a more general term that encompasses -#. both the current day and other instances of today, such as referring to a -#. specific date or time period. msgid "Today" msgstr "Hoje" -#. Explanation: The English word "Clear" can have different meanings, such as -#. being transparent or free from obstacles. In this context, the Portuguese -#. translation "Limpo" is more literal and conveys the idea of something being -#. clean or free from impurities, which aligns with the French translation -#. "Effacer", suggesting to remove or erase something. msgid "Clear" -msgstr "Limpo" +msgstr "Eliminar" -#. Explanation: The English word "Cancel" can have different meanings, such as -#. to stop or to void an action. However, in the context of the French -#. translation "Annuler", it seems that the intended meaning is to cancel or -#. void an action, which is a common usage in both languages. msgid "Cancel" msgstr "Cancelar" -#. Explanation: The French translation "Maintenant" can also mean "now" in a -#. more formal or literal sense, but the Portuguese translation "Agora" is -#. more commonly used to indicate the present moment. msgid "Now" msgstr "Agora" -#. Explanation: The English word "Validate" and the French word "Valider" both -#. convey the meaning of making something valid or true. There is no -#. significant difference in their usage, so the Portuguese translation -#. "Validar" maintains consistency with the French translation. msgid "Validate" msgstr "Validar" -#. Explanation: The English word "Delete" can have different meanings -#. depending on the context, such as deleting a file or deleting an account. -#. However, in general, it means to remove or get rid of something. In -#. Portuguese, the word "Apagar" is used to convey this meaning, and it is -#. consistent with the French translation "Supprimer", which also implies -#. removal or elimination. msgid "Delete" msgstr "Apagar" -#. Explanation: This Portuguese translation reflects the meaning of the French -#. phrase, which is a polite and formal way of asking if someone is sure they -#. want to delete a comment. The English sentence is also phrased in a similar -#. manner, making it clear that the question is about deleting a comment. msgid "Are you sure you want to delete this comment?" msgstr "Você está seguro de querer apagar esse comentário?" -#. Explanation: This Portuguese translation reflects the meaning of the French -#. phrase, which is a more formal and polite way to ask if someone is sure -#. they want to delete something. The English sentence is a bit more direct -#. and informal, while the French translation adds a touch of politeness with -#. the use of "sûr(e)" (sure) and the question mark at the end. msgid "Are you sure you want to delete this reply and its comments?" msgstr "Você está seguro de querer apagar essa resposta e seus comentários?" -#. Explanation: This Portuguese translation reflects the meaning of the French -#. phrase, which indicates that the content of the message is too large and -#. suggests selecting smaller images to resolve the issue. The original -#. English sentence can be ambiguous, but the French translation makes it -#. clear, so the Portuguese version follows this interpretation. msgid "The message content is too large. Please select smaller images." msgstr "" "O conteúdo do mensagem é muito grande. Por favor, selecione imagens menores." -#. Explanation: This Portuguese translation reflects the meaning of the French -#. phrase, which is a polite and formal way of asking if someone is sure they -#. want to delete a message. The English sentence is also phrased in a similar -#. manner, making it clear that the question is about deleting a message. msgid "Are you sure you want to delete this message?" msgstr "Você está seguro de querer apagar essa mensagem?" -#. Explanation: This Portuguese translation reflects the meaning of the French -#. phrase, which indicates that a deletion has been confirmed. The English -#. phrase and French translation convey a sense of finality and confirmation, -#. so the Portuguese version maintains this tone. msgid "Deletion confirmed..." -msgstr "Confirmação de exclusão..." +msgstr "Eliminação confirmada..." -#. Explanation: The Portuguese translation maintains the same tone and -#. emphasis as the original English sentence, conveying a sense of surprise or -#. frustration that the message was empty. msgid "The message was empty!" msgstr "O mensagem estava vazia!" -#. Explanation: The English phrase "Not valid!" can be interpreted in -#. different ways, but the French translation "Non valide !" makes it clear -#. that it's an expression of invalidity or unreliability. In Portuguese, the -#. word "não" is used to indicate negation, and "válido" means valid, so the -#. translation "Não válido" accurately conveys this meaning. msgid "Not valid!" msgstr "Não válido!" -#. Explanation: This Portuguese translation reflects the meaning of the French -#. phrase, which is a direct and literal translation of the English sentence. -#. The placeholder "%s" is kept in its original position to maintain -#. consistency with both the English and French translations. msgid "%s answer" msgid_plural "%s answers" msgstr[0] "%s resposta" msgstr[1] "%s responde" -#. Explanation: This Portuguese translation reflects the meaning of the French -#. phrase, which is a question about something. The English phrase "%s -#. comment" can be ambiguous, as it can mean both "how" and "what". However, -#. in this context, it seems to be asking for a commentary or an explanation. -#. The French translation makes it clear that it is a request for a -#. commentary, so the Portuguese version ":%s comentário" follows this -#. interpretation. msgid "%s comment" msgid_plural "%s comments" msgstr[0] "%s comentário" msgstr[1] "%s comentários" -#. Explanation: The English word "profile" and the French word "profil" both -#. refer to a person's or thing's outline, shape, or characteristics. In this -#. context, the Portuguese translation "perfil" maintains the same meaning as -#. the original words. msgid "profile" -msgstr "Perfil" +msgstr "perfil" + +msgid "Are you sure you want to delete this treasure?" +msgstr "Você está seguro de querer apagar esse tesouro?" diff --git a/cm_main/locale/sp/LC_MESSAGES/django.mo b/cm_main/locale/sp/LC_MESSAGES/django.mo deleted file mode 100644 index 45f59911..00000000 Binary files a/cm_main/locale/sp/LC_MESSAGES/django.mo and /dev/null differ diff --git a/cm_main/locale/sp/LC_MESSAGES/django.po b/cm_main/locale/sp/LC_MESSAGES/django.po deleted file mode 100644 index 60c8d68d..00000000 --- a/cm_main/locale/sp/LC_MESSAGES/django.po +++ /dev/null @@ -1,507 +0,0 @@ -# Spanish translation of cousins_matter. -# Copyright (C) 2024 Olivier LEVILLAIN -# This file is distributed under the same license as the Cousins Matter package. -# , 2024. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: Cousins Matter 0.1.0\n" -"Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-12-24 12:09+0100\n" -"PO-Revision-Date: 2024-12-24 12:09+0100\n" -"Last-Translator: Olivier LEVILLAIN \n" -"Language-Team: Spanish \n" -"Language: Spanish\n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" - -#: apps.py:8 -msgid "Cousins Matter!" -msgstr "Os Cousins Contam" - -#: followers.py:61 tests/tests_followers.py:80 -#, python-format -msgid "New %(followed_type)s \"%(followed_object_name)s\"" -msgstr "Nuevo %(followed_type)s \"%(followed_object_name)s\"" - -#: followers.py:65 -#, python-format -msgid "" -"%(follower_name)s has created a new %(followed_type)s " -"\"%(followed_object_name)s\"" -msgstr "%(follower_name)s ha creado un nuevo %(followed_type)s " -"\"%(followed_object_name)s\"" - -#: followers.py:70 tests/tests_followers.py:92 -#, python-format -msgid "" -"New %(obj_type)s added to %(followed_type)s \"%(followed_object_name)s\"" -msgstr "Nuevo %(obj_type)s agregado al %(followed_type)s " -"\"%(followed_object_name)s\"" - -#: followers.py:75 -#, python-format -msgid "" -"%(follower_name)s has added a new %(obj_type)s in the %(followed_type)s " -"\"%(followed_object_name)s\"" -msgstr "%(follower_name)s ha agregado un nuevo %(obj_type)s en %(followed_type)s " -"\"%(followed_object_name)s\"" - -#: followers.py:108 -#, python-brace-format -msgid "You are no longer following this {followed_type}" -msgstr "Ya no sigues este {followed_type}" - -#: followers.py:111 -#, python-brace-format -msgid "You are now following this {followed_type}" -msgstr "Ahora sigues este {followed_type}" - -#: followers.py:119 tests/tests_followers.py:29 -#, python-format -msgid "New follower to your %(followed_type)s \"%(followed_object_name)s\"" -msgstr "Nuevo seguidor a tu %(followed_type)s \"%(followed_object_name)s\"" - -#: followers.py:121 -#, python-format -msgid "" -"%(follower_name)s is now following your %(followed_type)s " -"\"%(followed_object_name)s\"" -msgstr "%(follower_name)s ahora sigue tu %(followed_type)s " -"\"%(followed_object_name)s\"" - -#: forms.py:11 -msgid "Your message" -msgstr "Tu mensaje" - -#: forms.py:12 -msgid "Please keep it short and avoid images." -msgstr "Por favor, manténgalo corto y evita las imágenes." - -#: forms.py:13 -msgid "Attach file" -msgstr "Adjuntar archivo" - -#: forms.py:14 -msgid "You can attach a file here if needed" -msgstr "Puedes adjuntar un archivo aqui si es necesario" - -#: templates/cm_main/about/site-stats.html:3 -#: templates/cm_main/about/site-stats.html:6 -msgid "Site Statistics" -msgstr "Estadísticas del Sitio" - -#: templates/cm_main/about/site-stats.html:7 -msgid "This page displays some statistics about this site:" -msgstr "Esta página muestra algunas estadísticas sobre este sitio:" - -#: templates/cm_main/about/site-stats.html:9 -msgid "Statistics" -msgstr "Estadísticas" - -#: templates/cm_main/base.html:8 -msgid "Home" -msgstr "Inicio" - -#: templates/cm_main/base.html:68 -msgid "This site proudly built using" -msgstr "Este sitio se ha construido con" - -#: templates/cm_main/base.html:70 -msgid "The source code is licensed" -msgstr "El código fuente está licenciado" - -#: templates/cm_main/base.html:76 -msgid "Copyright © 2024 Cousins Matter. All rights reserved." -msgstr "Copyright © 2024 Cousins Matter. Todos los derechos reservados." - -#: templates/cm_main/base.html:79 templates/cm_main/navbar.html:138 -msgid "Contact the site admin" -msgstr "Contacta al administrador del sitio" - -#: templates/cm_main/common/confirm-delete-modal.html:28 -#: templates/cm_main/common/confirm-delete-modal.html:54 -msgid "Confirm" -msgstr "Confirmar" - -#: templates/cm_main/common/confirm-delete-modal.html:32 -#: templates/cm_main/common/confirm-delete-modal.html:58 -#: templates/cm_main/contact/contact-form.html:16 -msgid "Cancel" -msgstr "Cancelar" - -#: templates/cm_main/common/confirm-delete-modal.html:40 -#, python-format -msgid "" -"Enter \"%(expected_value)s\" in the " -"field below before pressing confirm" -msgstr "Introduce \"%(expected_value)s\" en el campo de abajo antes de pulsar confirmar" - -#: templates/cm_main/common/confirm-delete-modal.html:48 -msgid "" -"Mandatory. Deletion will not take place until the correct value is entered." -msgstr "Necesario. La eliminación no se llevará a cabo hasta que se introduzca el valor correcto." - -#: templates/cm_main/common/include-summernote.html:6 -msgid "Toggle Note Editor Toolbar" -msgstr "Alternar la barra de herramientas del editor de notas" - -#: templates/cm_main/common/include-summernote.html:7 -msgid "Remaining characters:" -msgstr "Caracteres restantes:" - -#: templates/cm_main/common/paginate_template.html:27 -msgid "Items per page:" -msgstr "Elementos por página:" - -#: templates/cm_main/common/paginate_template.html:47 -msgid "go to first page" -msgstr "Ir a la primera página" - -#: templates/cm_main/common/paginate_template.html:59 -#, python-format -msgid "go to page #%(page_num)s" -msgstr "Ir a la página #%(page_num)s" - -#: templates/cm_main/common/paginate_template.html:62 -#, python-format -msgid "page #%(page_num)s" -msgstr "Página #%(page_num)s" - -#: templates/cm_main/common/paginate_template.html:75 -msgid "go to last page" -msgstr "Ir a la última página" - -#: templates/cm_main/contact/contact-form.html:6 -#: templates/cm_main/contact/contact-form.html:9 -msgid "Contact Form" -msgstr "Formulario de contacto" - -#: templates/cm_main/contact/contact-form.html:10 -msgid "Please fill out the form below to contact the admin of this site:" -msgstr "Por favor, rellena el formulario de abajo para contactar con el administrador de este sitio." - -#: templates/cm_main/contact/contact-form.html:15 -msgid "Send" -msgstr "Enviar" - -#: templates/cm_main/contact/email-contact-form.html:19 -#: tests/test_contactform.py:42 -#, python-format -msgid "%(sender_name)s sent you the following message from %(site_name)s:" -msgstr "El usuario %(sender_name)s te ha envíanos un mensaje desde %(site_name)s" - -#: templates/cm_main/contact/email-contact-form.html:26 -msgid "You can directly reply to this email to answer him/her." -msgstr "Puedes responder directamente a este correo." - -#: templates/cm_main/followers/email-followers-on-change.html:20 -#: tests/tests_followers.py:84 -#, python-format -msgid "" -"%(author_name)s created the following %(followed_type)s
'%(followed_object_name)s':" -msgstr "El usuario %(author_name)s ha creado el siguiente %(followed_type)s '%(followed_object_name)s'" - -#: templates/cm_main/followers/email-followers-on-change.html:24 -#: tests/tests_followers.py:97 -#, python-format -msgid "" -"%(author_name)s added the following %(obj_type)s on %(followed_type)s '%(followed_object_name)s':" -msgstr "El usuario %(author_name)s ha agregado el siguiente %(obj_type)s en %(followed_type)s '%(followed_object_name)s'" - -#: templates/cm_main/followers/email-followers-on-change.html:32 -#: templates/cm_main/followers/new_follower.html:27 -msgid "Do not reply to this mail, it is machine generated." -msgstr "No responda a este correo, es generado por la máquina." - -#: templates/cm_main/followers/followers-count-tag.html:4 -#, python-format -msgid "%(nfollowers)s member" -msgid_plural "%(nfollowers)s members" -msgstr[0] "%(nfollowers)s miembro" -msgstr[1] "%(nfollowers)s miembros" - -#: templates/cm_main/followers/followers-count-tag.html:10 -#, python-format -msgid "%(nfollowers)s follower" -msgid_plural "%(nfollowers)s followers" -msgstr[0] "%(nfollowers)s seguidor" -msgstr[1] "%(nfollowers)s seguidores" - -#: templates/cm_main/followers/new_follower.html:14 -#, python-format -msgid "Your %(followed_type)s '%(followed_object_name)s' has a new follower!" -msgstr "¡Tu %(followed_type)s '%(followed_object_name)s' tiene un nuevo seguidor!" - -#: templates/cm_main/followers/new_follower.html:22 tests/tests_followers.py:46 -#, python-format -msgid "" -"%(follower_name)s is now following your %(followed_type)s \"%(followed_object_name)s\" on %(site_name)s" -msgstr "El usuario %(follower_name)s ahora sigue tu %(followed_type)s " -"\"%(followed_object_name)s\" en %(site_name)s" - -#: templates/cm_main/followers/toggle-follow-button.html:4 -msgid "Stop Following" -msgstr "Dejar de seguir" - -#: templates/cm_main/followers/toggle-follow-button.html:11 -msgid "Follow" -msgstr "Seguir" - -#: templates/cm_main/navbar.html:26 views/views_stats.py:106 -msgid "Members" -msgstr "Miembros" - -#: templates/cm_main/navbar.html:29 -msgid "Birthdays" -msgstr "Cumpleaños" - -#: templates/cm_main/navbar.html:31 -msgid "Show directory" -msgstr "Mostrar directorio" - -#: templates/cm_main/navbar.html:34 -msgid "Create Member" -msgstr "Crear miembro" - -#: templates/cm_main/navbar.html:38 -msgid "Invite Member" -msgstr "Invitar miembro" - -#: templates/cm_main/navbar.html:45 views/views_stats.py:114 -msgid "Galleries" -msgstr "Galerías" - -#: templates/cm_main/navbar.html:48 -msgid "Create Gallery" -msgstr "Crear galería" - -#: templates/cm_main/navbar.html:50 -msgid "Bulk Upload" -msgstr "Subir en masa" - -#: templates/cm_main/navbar.html:56 -msgid "Forum" -msgstr "Foro" - -#: templates/cm_main/navbar.html:59 -msgid "Create Post" -msgstr "Crear publicación" - -#: templates/cm_main/navbar.html:64 -msgid "Chat" -msgstr "Chat" - -#: templates/cm_main/navbar.html:67 -msgid "Public Chat Rooms" -msgstr "Salas de chat públicas" - -#: templates/cm_main/navbar.html:69 -msgid "Private Chat Rooms" -msgstr "Salas de chat privadas" - -#: templates/cm_main/navbar.html:92 -msgid "Change language" -msgstr "Cambiar idioma" - -#: templates/cm_main/navbar.html:112 -msgid "Edit Pages" -msgstr "Editar páginas" - -#: templates/cm_main/navbar.html:114 -msgid "Import members from CSV" -msgstr "Importar miembros desde CSV" - -#: templates/cm_main/navbar.html:115 -msgid "Admin site" -msgstr "Administrar sitio" - -#: templates/cm_main/navbar.html:118 -msgid "Export Members as CSV" -msgstr "Exportar miembros como CSV" - -#: templates/cm_main/navbar.html:136 -msgid "About the site" -msgstr "Acerca del sitio" - -#: templates/cm_main/navbar.html:147 -msgid "Sign in" -msgstr "Iniciar sesión" - -#: templates/cm_main/navbar.html:151 -msgid "Request invitation link" -msgstr "Solicitar enlace de invitación" - -#: templates/cm_main/navbar.html:160 -msgid "Profile" -msgstr "Perfil" - -#: templates/cm_main/navbar.html:162 -msgid "Log Out" -msgstr "Cerrar sesión" - -#: tests/test_contactform.py:23 -msgid "This field is required." -msgstr "Este campo es obligatorio." - -#: tests/test_contactform.py:29 views/views_contact.py:65 -msgid "Your message has been sent" -msgstr "Tu mensaje ha sido enviado" - -#: tests/test_contactform.py:32 views/views_contact.py:40 -msgid "Contact form" -msgstr "Formulario de contacto" - -#: tests/test_contactform.py:39 views/views_contact.py:37 -#, python-format -msgid "You have a new message from %(name)s (%(email)s). " -msgstr "Tienes un nuevo mensaje de %(name)s (%(email)s). " - -#: tests/tests_followers.py:27 -msgid "You have a new follower!" -msgstr "Tienes un nuevo seguidor!" - -#: tests/tests_followers.py:38 -#, python-format -msgid "" -"Hi %(followed_name)s,
%(follower_name)s " -"is now following you on %(site_name)s!" -msgstr "¡Hola %(followed_name)s,
%(follower_name)s " -"ahora te sigue en %(site_name)s!" - -#: views/views_contact.py:41 -msgid "But your mailer tools is too old to show it :'(" -msgstr "Pero tus herramientas de correo electrónico son demasiado antiguas para mostrarlo :'(" - -#: views/views_contact.py:60 -msgid "This file type is not supported" -msgstr "Este tipo de archivo no está soportado" - -#: views/views_general.py:52 -msgid "Media not found" -msgstr "Archivo no encontrado" - -#: views/views_stats.py:41 views/views_stats.py:81 -msgid "Version not found" -msgstr "Versión no encontrada" - -#: views/views_stats.py:55 -msgid "Your version is not up-to-date." -msgstr "Tu versión no está actualizada." - -#: views/views_stats.py:58 -msgid "" -"Please update it by running the following command:
docker-start.sh -" -"u" -msgstr "Por favor, actualiza tu versión ejecutando el siguiente comando:
docker-start.sh -" -"u" - -#: views/views_stats.py:69 -msgid "Your version is up-to-date." -msgstr "Tu versión está actualizada." - -#: views/views_stats.py:75 -msgid "Your version is newer than the latest release (?!?)" -msgstr "Tu versión es más nueva que la versión más reciente (?!?)" - -#: views/views_stats.py:97 -msgid "Site" -msgstr "Sitio" - -#: views/views_stats.py:99 -msgid "Site name" -msgstr "Nombre del sitio" - -#: views/views_stats.py:100 -msgid "Site URL" -msgstr "URL del sitio" - -#: views/views_stats.py:101 -msgid "Application Version" -msgstr "Versión de la aplicación" - -#: views/views_stats.py:102 -msgid "Latest release" -msgstr "Ultima versión" - -#: views/views_stats.py:108 -msgid "Total number of members" -msgstr "Número total de miembros" - -#: views/views_stats.py:109 -msgid "Number of active members" -msgstr "Número de miembros activos" - -#: views/views_stats.py:110 -msgid "Number of managed members" -msgstr "Número de miembros administrados" - -#: views/views_stats.py:116 -msgid "Number of galleries" -msgstr "Número de galerías" - -#: views/views_stats.py:117 -msgid "Number of photos" -msgstr "Número de fotos" - -#: views/views_stats.py:121 -msgid "Forums" -msgstr "Foros" - -#: views/views_stats.py:123 -msgid "Number of posts" -msgstr "Número de mensajes" - -#: views/views_stats.py:124 -msgid "Number of post messages" -msgstr "Número de mensajes de post" - -#: views/views_stats.py:125 -msgid "Number of message comments" -msgstr "Número de comentarios de mensaje" - -#: views/views_stats.py:129 -msgid "Chats" -msgstr "Chats" - -#: views/views_stats.py:131 -msgid "Number of chat rooms" -msgstr "Número de salas de chat" - -#: views/views_stats.py:132 -msgid "Number of public chat rooms" -msgstr "Número de salas de chat publicas" - -#: views/views_stats.py:133 -msgid "Number of private chat rooms" -msgstr "Número de salas de chat privadas" - -#: views/views_stats.py:134 -msgid "Number of chat messages" -msgstr "Número de mensajes de chat" - -#: views/views_stats.py:135 -msgid "Number of private chat messages" -msgstr "Número de mensajes de chat privados" - -#: views/views_stats.py:136 -msgid "Number of public chat messages" -msgstr "Número de mensajes de chat publicos" - -#: views/views_stats.py:140 -msgid "Administrator" -msgstr "Administrador" - -#: views/views_stats.py:142 -msgid "This site is managed by" -msgstr "Este sitio está administrado por" - -#: views/views_stats.py:143 -msgid "Administrator email" -msgstr "Correo electrónico del administrador" diff --git a/cm_main/templates/cm_main/common/paginate_template.html b/cm_main/templates/cm_main/common/paginate_template.html index 67a852f6..b96faca1 100644 --- a/cm_main/templates/cm_main/common/paginate_template.html +++ b/cm_main/templates/cm_main/common/paginate_template.html @@ -7,9 +7,7 @@ new_location += '?fullscreen=true' next_par = '&' } - {%if not no_per_page%} - new_location += next_par + 'page_size=' + $('#page_size').val() - {%endif%} + new_location += `{%if not no_per_page%}${next_par}page_size=${$('#page_size').val()}{%endif%}` window.location = new_location } function get_prev_page_url() { @@ -37,7 +35,7 @@ {%endif%} + {% if prev_page_url %} onclick="goto_page_url('{{prev_page_url}}')"{% else %}disabled{%endif%}> {%icon "pagination-previous" %} @@ -78,7 +76,7 @@ {%endif%} + {% if next_page_url %} onclick="goto_page_url('{{next_page_url}}')"{% else %}disabled{%endif%}> {%icon "pagination-next" %} {%endwith%} diff --git a/cm_main/templates/cm_main/navbar.html b/cm_main/templates/cm_main/navbar.html index 15356207..53a56d80 100644 --- a/cm_main/templates/cm_main/navbar.html +++ b/cm_main/templates/cm_main/navbar.html @@ -72,6 +72,11 @@ + + {% comment %}
  • {%trans "Polls" %}
  • {%trans "Admin" %}
  • diff --git a/cousinsmatter/settings.py b/cousinsmatter/settings.py index f23a2d88..e3aa2c77 100644 --- a/cousinsmatter/settings.py +++ b/cousinsmatter/settings.py @@ -101,6 +101,7 @@ 'forum', 'chat', 'pages', + 'troves', 'crispy_forms', 'crispy_bulma', 'verify_email', @@ -249,6 +250,10 @@ "admin": "application-cog-outline", "stats": "chart-box-outline", "leave-group": "account-multiple-minus-outline", + # arrows + "arrow-down": "arrow-down-thin", + "arrow-up": "arrow-up-thin", + "arrow-up-down": "swap-vertical", # emoticons "thumb-up": "thumb-up-outline", "thumb-down": "thumb-down-outline", @@ -300,10 +305,10 @@ "page-level": "page-next-outline", "new-page": "book-open-page-variant-outline", "edit-page": "note-edit-outline", - # arrows - "arrow-down": "arrow-down-thin", - "arrow-up": "arrow-up-thin", - "arrow-up-down": "swap-vertical" + # troves icons + "new-treasure": "book-plus-outline", + "edit-treasure": "note-edit-outline", + "troves": "treasure-chest", } EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend' @@ -368,10 +373,15 @@ DARK_MODE = env.bool('DARK_MODE', False) +# members settings AUTH_USER_MODEL = 'members.Member' - DEFAULT_MEMBERS_PAGE_SIZE = env.int('DEFAULT_MEMBERS_PAGE_SIZE', 25) +ALLOW_MEMBERS_TO_CREATE_MEMBERS = env.bool('ALLOW_MEMBERS_TO_CREATE_MEMBERS', True) +ALLOW_MEMBERS_TO_INVITE_MEMBERS = env.bool('ALLOW_MEMBERS_TO_INVITE_MEMBERS', True) + +# forum settings DEFAULT_POSTS_PER_PAGE = env.int('DEFAULT_POSTS_PER_PAGE', 25) +# chat settings DEFAULT_CHATMESSAGES_PER_PAGE = env.int('DEFAULT_CHATMESSAGES_PER_PAGE', 25) DEFAULT_CHATROOMS_PER_PAGE = env.int('DEFAULT_CHATROOMS_PER_PAGE', 25) MESSAGE_MAX_SIZE = env.int('MESSAGE_MAX_SIZE', DATA_UPLOAD_MAX_MEMORY_SIZE) @@ -379,6 +389,7 @@ CONTACT_MAX_SIZE = env.int('CONTACT_MAX_SIZE', 1024*1024) # 1MB +# page settings PAGES_URL_PREFIX = 'pages/' PRIVACY_URL = '/about/privacy-policy/' MENU_PAGE_URL_PREFIX = '/publish' @@ -390,9 +401,25 @@ DATA_UPLOAD_MAX_MEMORY_SIZE = max(DATA_UPLOAD_MAX_MEMORY_SIZE, MESSAGE_MAX_SIZE, PAGE_MAX_SIZE) -ALLOW_MEMBERS_TO_CREATE_MEMBERS = env.bool('ALLOW_MEMBERS_TO_CREATE_MEMBERS', True) -ALLOW_MEMBERS_TO_INVITE_MEMBERS = env.bool('ALLOW_MEMBERS_TO_INVITE_MEMBERS', True) - # read version from release.txt with open(BASE_DIR / 'release.txt', 'r') as f: APP_VERSION = f.read().strip() + +# Troves settings +TROVE_DIRECTORY_REL = 'troves' +TROVE_DIRECTORY = Path(TROVE_DIRECTORY_REL) +TROVE_PICTURE_DIRECTORY_REL = 'pictures' +TROVE_THUMBNAIL_DIRECTORY_REL = 'thumbnails' +TROVE_FILES_DIRECTORY_REL = 'files' +TROVE_PICTURE_DIRECTORY = TROVE_DIRECTORY / TROVE_PICTURE_DIRECTORY_REL +TROVE_THUMBNAIL_DIRECTORY = TROVE_PICTURE_DIRECTORY / TROVE_THUMBNAIL_DIRECTORY_REL +TROVE_FILES_DIRECTORY = TROVE_DIRECTORY / TROVE_FILES_DIRECTORY_REL +TROVE_URL_PREFIX = f'{TROVE_DIRECTORY}/' +TROVE_PICTURE_URL_PREFIX = f'{TROVE_URL_PREFIX}{TROVE_PICTURE_DIRECTORY_REL}/' +TROVE_THUMBNAIL_URL_PREFIX = f'{TROVE_PICTURE_URL_PREFIX}{TROVE_THUMBNAIL_DIRECTORY_REL}/' +TROVE_FILE_URL_PREFIX = f'{TROVE_URL_PREFIX}{TROVE_FILES_DIRECTORY_REL}/' +TROVE_FILE_MAX_SIZE = env.int('TROVE_FILE_MAX_SIZE', 20*1024*1024) # 20MB +TROVE_PICTURE_FILE_MAX_SIZE = env.int('TROVE_PICTURE_FILE_MAX_SIZE', MAX_PHOTO_FILE_SIZE) +TROVE_THUMBNAIL_SIZE = env.int('TROVE_THUMBNAIL_SIZE', GALLERIES_THUMBNAIL_SIZE) +DEFAULT_TROVE_PAGE_SIZE = env.int('DEFAULT_TROVE_PAGE_SIZE', 10) +TROVE_DESCRIPTION_MAX_SIZE = MESSAGE_MAX_SIZE diff --git a/cousinsmatter/urls.py b/cousinsmatter/urls.py index dcb8e5f4..624b8d3f 100644 --- a/cousinsmatter/urls.py +++ b/cousinsmatter/urls.py @@ -61,4 +61,5 @@ path('protected_media/', download_protected_media, name="get_protected_media"), path(settings.PAGES_URL_PREFIX, include("django.contrib.flatpages.urls")), path("pages-edit/", include("pages.urls")), + path('troves/', include('troves.urls')), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/cousinsmatter/utils.py b/cousinsmatter/utils.py index d1526b60..076bca6e 100644 --- a/cousinsmatter/utils.py +++ b/cousinsmatter/utils.py @@ -1,18 +1,27 @@ -# util functions for member views +# util functions for cousinsmatter app -from contextlib import contextmanager import math +import os +from contextlib import contextmanager +from io import BytesIO +import shutil +from PIL import Image from pathlib import PosixPath +# import pprint +import sys import unicodedata from urllib.parse import urlencode from django.core import paginator +from django.core.files.uploadedfile import InMemoryUploadedFile from django.db import connections from django.forms import ValidationError from django.shortcuts import redirect from django.urls import reverse from django.utils.translation import gettext as _ +from cousinsmatter.context_processors import override_settings + # terrible hack to check if we are in testing mode!!! IS_TESTING = None @@ -55,6 +64,11 @@ def check_file_size(file, limit): raise ValidationError(_(f"Uploaded file {filename} is too big ({sizemb}MB), maximum is {limitmb}MB.")) +class PageOutOfBounds(ValueError): + def __init__(self, redirect_to): + self.redirect_to = redirect_to + + class Paginator(paginator.Paginator): possible_per_pages = [10, 25, 50, 100] max_pages = 2 # on both sides of the current page link @@ -72,14 +86,14 @@ def __init__(self, query_set, per_page, reverse_link=None, compute_link=None): def _get_link(self, idx): return self.compute_link(idx) if self.compute_link else reverse(self.reverse_link, args=[idx]) - def get_page_data(self, page_num): + def get_page_data(self, page_num, group_by=None): page_num = min(page_num, self.num_pages) page = self.page(page_num) # compute a page range from the initial range + or -max-pages page.first = max(0, page_num-self.max_pages-1) page.last = min(self.num_pages+1, page_num+self.max_pages) if page.first == 0: - page.last = min(self.num_pages+1, 2*self.max_pages+1) + page.last = min(self.num_pages, 2*self.max_pages)+1 elif page.last == self.num_pages+1: page.first = max(0, page.last-2*self.max_pages-1) page.page_range = self.page_range[page.first:page.last] @@ -89,18 +103,29 @@ def get_page_data(self, page_num): page.first_page_link = self._get_link(1) page.last_page_link = self._get_link(self.num_pages) page.possible_per_pages = self.possible_per_pages - # pprint(vars(page)) + if group_by: + grouped_object_list = {} + for obj in page.object_list: + if group_by not in obj.__dict__: + raise ValueError(f"Object {obj} has no attribute {group_by}") + group = getattr(obj, group_by) + if group not in grouped_object_list: + grouped_object_list[group] = [] + grouped_object_list[group].append(obj) + page.object_list = grouped_object_list + + # pprint.pprint(vars(page)) return page @staticmethod - def get_page(request, object_list, page_num, reverse_link, compute_link=None, default_page_size=100): + def get_page(request, object_list, page_num, reverse_link, compute_link=None, default_page_size=100, group_by=None): page_size = int(request.GET["page_size"]) if "page_size" in request.GET else default_page_size ptor = Paginator(object_list, page_size, reverse_link=reverse_link, compute_link=compute_link) page_num = page_num or ptor.num_pages if page_num > ptor.num_pages: - return redirect(ptor._get_link(ptor.num_pages) + '?' + urlencode({'page_size': page_size})) - return ptor.get_page_data(page_num) + raise PageOutOfBounds(ptor._get_link(ptor.num_pages) + '?' + urlencode({'page_size': page_size})) + return ptor.get_page_data(page_num, group_by=group_by) @contextmanager @@ -117,3 +142,54 @@ def remove_accents(input_str): """remove accents from a string, including diacritical marks""" nfkd_form = unicodedata.normalize('NFKD', input_str) return ''.join([c for c in nfkd_form if not unicodedata.combining(c)]) + + +def create_image(image_file, content_type='image/jpeg'): + membuf = BytesIO() + with Image.open(image_file) as img: + img.save(membuf, format='JPEG', quality=90) + membuf.seek(0) + size = sys.getsizeof(membuf) + return InMemoryUploadedFile(membuf, 'ImageField', os.path.basename(image_file), + content_type, size, None) + + +def test_resource_full_path(image_file_basename, file__): + return os.path.join(os.path.dirname(file__), 'resources', image_file_basename) + + +def create_test_image(file__, image_file_basename, content_type='image/jpeg'): + image_file = test_resource_full_path(image_file_basename, file__) + return create_image(image_file, content_type) + + +@contextmanager +def set_test_media_root(test_file): + test_media_root = os.path.join(os.path.dirname(test_file), "media") + os.makedirs(test_media_root, exist_ok=True) + try: + with override_settings(MEDIA_ROOT=test_media_root): + yield + finally: + if os.path.isdir(test_media_root): + shutil.rmtree(test_media_root) + + +def test_media_root_decorator(test_file): + def decorator(cls): + orig_setUp = cls.setUp + orig_tearDown = cls.tearDown + + def setUp(self, *args, **kwargs): + self.test_media_root_context = set_test_media_root(test_file) + self.test_media_root_context.__enter__() + orig_setUp(self, *args, **kwargs) + + def tearDown(self, *args, **kwargs): + self.test_media_root_context.__exit__(None, None, None) + orig_tearDown(self, *args, **kwargs) + + cls.setUp = setUp + cls.tearDown = tearDown + return cls + return decorator diff --git a/forum/views/views_post.py b/forum/views/views_post.py index 93f5ba07..758d2676 100644 --- a/forum/views/views_post.py +++ b/forum/views/views_post.py @@ -12,7 +12,7 @@ from django.db.models import Count from django.core.exceptions import RequestDataTooBig -from cousinsmatter.utils import Paginator, assert_request_is_ajax +from cousinsmatter.utils import PageOutOfBounds, Paginator, assert_request_is_ajax from forum.views.views_follow import check_followers_on_message, check_followers_on_new_post from ..models import Post, Message from ..forms import MessageForm, PostForm, CommentForm @@ -25,11 +25,14 @@ class PostsListView(LoginRequiredMixin, generic.ListView): def get(self, request, page=1): posts = Post.objects.select_related('first_message').annotate(num_messages=Count("message")) \ .all().order_by('-first_message__date') - page = Paginator.get_page(request, object_list=posts, - page_num=page, - reverse_link='forum:page', - default_page_size=settings.DEFAULT_POSTS_PER_PAGE) - return render(request, "forum/post_list.html", {"page": page}) + try: + page = Paginator.get_page(request, object_list=posts, + page_num=page, + reverse_link='forum:page', + default_page_size=settings.DEFAULT_POSTS_PER_PAGE) + return render(request, "forum/post_list.html", {"page": page}) + except PageOutOfBounds as exc: + return redirect(exc.redirect_to) class PostDisplayView(LoginRequiredMixin, generic.DetailView): @@ -39,19 +42,22 @@ def get(self, request, pk, page_num=1): post_id = pk post = get_object_or_404(Post, pk=post_id) replies = Message.objects.filter(post=post_id, first_of_post=None).all() - page = Paginator.get_page(request, - object_list=replies, - page_num=page_num, - reverse_link='forum:display_page', - compute_link=lambda page_num: reverse('forum:display_page', args=[post_id, page_num]), - default_page_size=settings.DEFAULT_POSTS_PER_PAGE) - return render(request, "forum/post_detail.html", { - "page": page, - "nreplies": replies.count(), - 'post': post, - 'comment_form': CommentForm(), - 'reply_form': MessageForm(), - }) + try: + page = Paginator.get_page(request, + object_list=replies, + page_num=page_num, + reverse_link='forum:display_page', + compute_link=lambda page_num: reverse('forum:display_page', args=[post_id, page_num]), + default_page_size=settings.DEFAULT_POSTS_PER_PAGE) + return render(request, "forum/post_detail.html", { + "page": page, + "nreplies": replies.count(), + 'post': post, + 'comment_form': CommentForm(), + 'reply_form': MessageForm(), + }) + except PageOutOfBounds as exc: + return redirect(exc.redirect_to) class PostCreateView(LoginRequiredMixin, generic.CreateView): diff --git a/galleries/templatetags/galleries_tags.py b/galleries/templatetags/galleries_tags.py index 6507ffe5..a26949fb 100644 --- a/galleries/templatetags/galleries_tags.py +++ b/galleries/templatetags/galleries_tags.py @@ -53,6 +53,8 @@ def include_photos(gallery, page_num, page_size): photos = get_gallery_photos(gallery) # print('query: ', photos.query) ptor = Paginator(photos, page_size, compute_link=lambda page: reverse('galleries:detail_page', args=[gallery.id, page])) + if page_num > ptor.num_pages: + page_num = ptor.num_pages page = ptor.get_page_data(page_num) return {"page": page} diff --git a/galleries/tests/tests_bulk_upload.py b/galleries/tests/tests_bulk_upload.py index 5a106bae..f68f5001 100644 --- a/galleries/tests/tests_bulk_upload.py +++ b/galleries/tests/tests_bulk_upload.py @@ -1,8 +1,10 @@ from django.urls import reverse from django.core.files.uploadedfile import SimpleUploadedFile + +from cousinsmatter.utils import test_resource_full_path from ..models import Gallery, Photo from ..views import views_bulk -from .tests_utils import GalleryBaseTestCase, test_file_full_path +from .tests_utils import GalleryBaseTestCase class TestBulkUpload(GalleryBaseTestCase): @@ -12,11 +14,13 @@ def test_bulk_upload(self): self.assertIs(response.resolver_match.func.view_class, views_bulk.BulkUploadPhotosView) self.assertTemplateUsed(response, 'galleries/bulk_upload.html') - zipfile = test_file_full_path('test_bulk_import.zip') + zipfile = test_resource_full_path('test_bulk_import.zip', __file__) + # print("zipfile:", zipfile) response = self.client.post(reverse('galleries:bulk_upload'), {'zipfile': SimpleUploadedFile('test_bulk_import.zip', open(zipfile, 'rb').read(), content_type='application/zip')}, follow=True) + # self.print_response(response) self.assertEqual(response.status_code, 200) self.assertEqual(Gallery.objects.count(), 2) self.assertEqual(Photo.objects.count(), 4) diff --git a/galleries/tests/tests_gallery.py b/galleries/tests/tests_gallery.py index f1258a45..24e8db08 100644 --- a/galleries/tests/tests_gallery.py +++ b/galleries/tests/tests_gallery.py @@ -7,10 +7,11 @@ from django.utils.translation import gettext as _ from cm_main.templatetags.cm_tags import icon +from cousinsmatter.utils import create_test_image +from galleries.models import Gallery, Photo +from galleries.views.views_gallery import GalleryCreateView, GalleryDetailView, GalleryUpdateView from members.tests.tests_member_base import TestLoginRequiredMixin -from ..models import Gallery, Photo -from .tests_utils import create_image, GalleryBaseTestCase -from ..views.views_gallery import GalleryCreateView, GalleryDetailView, GalleryUpdateView +from .tests_utils import GalleryBaseTestCase COUNTER = 0 @@ -54,7 +55,7 @@ def test_create_gallery_with_cover(self): gal = Gallery(name=get_gallery_name(), description="a gallery with a cover") gal.save() cover_file = "test-image-1.jpg" - cover_image = create_image(cover_file) + cover_image = create_test_image(__file__, cover_file) cover = Photo(name="a cover", image=cover_image, date=date.today(), gallery=gal) cover.save() gal.cover = cover @@ -161,7 +162,9 @@ def test_modify_gallery(self): rg = Gallery(name=gal_name) rg.save() # add 3 photos in it - photos = [Photo(name=f'Photo#{i+1}', image=create_image(f'test-image-{i+1}.jpg'), date=date.today(), gallery=rg) + photos = [Photo(name=f'Photo#{i+1}', + image=create_test_image(__file__, f'test-image-{i+1}.jpg'), + date=date.today(), gallery=rg) for i in range(3)] for p in photos: p.save() @@ -239,7 +242,7 @@ def rec_build_tree(self, n, parent, image): return [(gal.id, p.id)] + res def test_delete_gallery(self): - image = create_image("test-image-1.jpg") + image = create_test_image(__file__, "test-image-1.jpg") lst = self.rec_build_tree(3, None, image) # find the 1rst gallery below root gal = lst[1][0] diff --git a/galleries/tests/tests_photo.py b/galleries/tests/tests_photo.py index b1b417ac..97d972d2 100644 --- a/galleries/tests/tests_photo.py +++ b/galleries/tests/tests_photo.py @@ -4,10 +4,11 @@ from django.urls import reverse from django.utils.translation import gettext as _ from django.core.exceptions import ValidationError +from cousinsmatter.utils import create_test_image +from galleries.models import Photo, Gallery +from galleries.views.views_photo import PhotoAddView from members.tests.tests_member import TestLoginRequiredMixin -from ..models import Photo, Gallery -from ..views.views_photo import PhotoAddView -from .tests_utils import create_image, GalleryBaseTestCase +from .tests_utils import GalleryBaseTestCase from .tests_gallery import get_gallery_name COUNTER = 0 @@ -32,7 +33,7 @@ def setUp(self): self.root_gallery.save() self.sub_gallery = Gallery(name=get_gallery_name(), description="a test sub gallery", parent=self.root_gallery) self.sub_gallery.save() - self.image = create_image("test-image-1.jpg") + self.image = create_test_image(__file__, "test-image-1.jpg") class CreatePhotoTests(PhotoTestsBase): @@ -87,7 +88,7 @@ def get_several_photos(self, nb_photos, page_num, first, last, gallery): """ if page_num == 1: for i in range(nb_photos): - image = create_image(f"test-image-{i+1}.jpg") + image = create_test_image(__file__, f"test-image-{i+1}.jpg") p = Photo(name=get_photo_name(), gallery=gallery, date=date.today(), image=image) p.save() photos = Photo.objects.filter(gallery=gallery)[first:last] diff --git a/galleries/tests/tests_utils.py b/galleries/tests/tests_utils.py index 9d8c2fee..77215539 100644 --- a/galleries/tests/tests_utils.py +++ b/galleries/tests/tests_utils.py @@ -1,36 +1,9 @@ -from io import BytesIO -from PIL import Image -import os -import shutil -import sys - -from django.core.files.uploadedfile import InMemoryUploadedFile - -from cousinsmatter.context_processors import override_settings +from cousinsmatter.utils import test_media_root_decorator from galleries.models import Gallery, Photo from members.tests.tests_member_base import MemberTestCase -def test_file_full_path(image_file_basename): - return os.path.join(os.path.dirname(__file__), 'resources', image_file_basename) - - -def create_image(image_file_basename): - image_file = test_file_full_path(image_file_basename) - membuf = BytesIO() - with Image.open(image_file) as img: - img.save(membuf, format='JPEG', quality=90) - membuf.seek(0) - size = sys.getsizeof(membuf) - return InMemoryUploadedFile(membuf, 'ImageField', image_file_basename, - 'image/jpeg', size, None) - - -# puts MEDIA_ROOT under the test directory during tests -TEST_MEDIA_ROOT = os.path.join(os.path.dirname(__file__), "media") - - -@override_settings(MEDIA_ROOT=TEST_MEDIA_ROOT) +@test_media_root_decorator(__file__) class GalleryBaseTestCase(MemberTestCase): def setUp(self): @@ -38,9 +11,6 @@ def setUp(self): def tearDown(self): super().tearDown() - if os.path.isdir(TEST_MEDIA_ROOT): - shutil.rmtree(TEST_MEDIA_ROOT) - # print("deleted test media files") for gallery in Gallery.objects.filter(parent=None): gallery.delete() self.assertEqual(Gallery.objects.count(), 0) diff --git a/galleries/views/views_photo.py b/galleries/views/views_photo.py index 0baec09c..24abcec9 100644 --- a/galleries/views/views_photo.py +++ b/galleries/views/views_photo.py @@ -10,7 +10,7 @@ from django.contrib.auth.decorators import login_required from django.utils.translation import gettext as _ from django.core.paginator import Paginator as BasePaginator -from cousinsmatter.utils import Paginator, assert_request_is_ajax +from cousinsmatter.utils import PageOutOfBounds, Paginator, assert_request_is_ajax from galleries.templatetags.galleries_tags import get_gallery_photos from ..models import Photo from ..forms import PhotoForm @@ -56,10 +56,13 @@ def get(self, request, **kwargs): photos = get_gallery_photos(gallery_id) # Photo.objects.filter(gallery=gallery_id) photos_count = photos.count() - ptor = Paginator(photos, 1, - compute_link=lambda photo_num: reverse("galleries:photo_list", args=[gallery_id, photo_num])) - page = ptor.get_page_data(photo_num) - return render(request, self.template_name, {'page': page, 'gallery_id': gallery_id, 'photos_count': photos_count}) + try: + ptor = Paginator(photos, 1, + compute_link=lambda photo_num: reverse("galleries:photo_list", args=[gallery_id, photo_num])) + page = ptor.get_page_data(photo_num) + return render(request, self.template_name, {'page': page, 'gallery_id': gallery_id, 'photos_count': photos_count}) + except PageOutOfBounds as exc: + return redirect(exc.redirect_to) @login_required diff --git a/members/views/views_directory.py b/members/views/views_directory.py index 650c03ab..e44ea7ec 100644 --- a/members/views/views_directory.py +++ b/members/views/views_directory.py @@ -1,6 +1,6 @@ # util functions for member views from typing import Any -from django.shortcuts import render +from django.shortcuts import redirect, render from django.utils.translation import gettext as _ import io from reportlab.rl_config import defaultPageSize @@ -14,7 +14,7 @@ from django.utils.text import slugify from django.conf import settings -from cousinsmatter.utils import Paginator +from cousinsmatter.utils import PageOutOfBounds, Paginator from ..models import Member @@ -35,12 +35,15 @@ class MembersDirectoryView(LoginRequiredMixin, generic.View): def get(self, request, page_num=1) -> dict[str, Any]: members = Member.objects.alive() - page = Paginator.get_page(request, - object_list=members, - page_num=page_num, - reverse_link="members:directory_page", - default_page_size=100) - return render(request, self.template_name, {"page": page}) + try: + page = Paginator.get_page(request, + object_list=members, + page_num=page_num, + reverse_link="members:directory_page", + default_page_size=100) + return render(request, self.template_name, {"page": page}) + except PageOutOfBounds as exc: + return redirect(exc.redirect_to) class MembersPrintDirectoryView(LoginRequiredMixin, generic.View): diff --git a/members/views/views_member.py b/members/views/views_member.py index 07ebfaac..fa4a28d2 100644 --- a/members/views/views_member.py +++ b/members/views/views_member.py @@ -10,7 +10,7 @@ from django.contrib.auth.decorators import login_required from django.utils.translation import gettext as _ from django.http import HttpResponseForbidden, JsonResponse -from cousinsmatter.utils import Paginator, remove_accents +from cousinsmatter.utils import PageOutOfBounds, Paginator, remove_accents from verify_email.email_handler import send_verification_email from cousinsmatter.utils import assert_request_is_ajax, redirect_to_referer from ..models import Member @@ -81,12 +81,15 @@ def get(self, request, page_num=1): sort_by = [order + s for s in sort_by] members = members.order_by(*sort_by) # print('sort by: ', sort_by, ' order: "', order, '" members query: ', members.query) - page = Paginator.get_page(request, - object_list=members, - page_num=page_num, - reverse_link='members:members_page', - default_page_size=settings.DEFAULT_MEMBERS_PAGE_SIZE) - return render(request, self.template_name, {"page": page}) + try: + page = Paginator.get_page(request, + object_list=members, + page_num=page_num, + reverse_link='members:members_page', + default_page_size=settings.DEFAULT_MEMBERS_PAGE_SIZE) + return render(request, self.template_name, {"page": page}) + except PageOutOfBounds as exc: + return redirect(exc.redirect_to) class MemberDetailView(LoginRequiredMixin, generic.DetailView): diff --git a/troves/__init__.py b/troves/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/troves/admin.py b/troves/admin.py new file mode 100644 index 00000000..813df23d --- /dev/null +++ b/troves/admin.py @@ -0,0 +1,6 @@ +from django.contrib import admin + +# Register your models here. +from .models import Trove + +admin.site.register(Trove) diff --git a/troves/apps.py b/troves/apps.py new file mode 100644 index 00000000..c49571b6 --- /dev/null +++ b/troves/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class TrovesConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'troves' diff --git a/troves/forms.py b/troves/forms.py new file mode 100644 index 00000000..054d3393 --- /dev/null +++ b/troves/forms.py @@ -0,0 +1,12 @@ +from django import forms +from .models import Trove +from cm_main.widgets import RichTextarea + + +class TreasureForm(forms.ModelForm): + class Meta: + model = Trove + fields = ['description', 'picture', 'file', 'category'] + widgets = { + 'description': RichTextarea(), + } diff --git a/troves/locale/de/LC_MESSAGES/django.mo b/troves/locale/de/LC_MESSAGES/django.mo new file mode 100644 index 00000000..6ef69f6e Binary files /dev/null and b/troves/locale/de/LC_MESSAGES/django.mo differ diff --git a/troves/locale/de/LC_MESSAGES/django.po b/troves/locale/de/LC_MESSAGES/django.po new file mode 100644 index 00000000..f7ecc4db --- /dev/null +++ b/troves/locale/de/LC_MESSAGES/django.po @@ -0,0 +1,92 @@ +# German Translations for Troves app. +# Copyright (C) 2024 Olivier LEVILLAIN +# This file is distributed under the same license as the Troves package. +# Olivier LEVILLAIN , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: 0.1\n" +"Report-Msgid-Bugs-To: Olivier LEVILLAIN \n" +"POT-Creation-Date: 2024-12-30 12:56+0100\n" +"PO-Revision-Date: 2024-12-30 12:56+0100\n" +"Last-Translator: Olivier LEVILLAIN \n" +"Language-Team: German \n" +"Language: DE\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: models.py:19 +msgid "History & Stories" +msgstr "Geschichte und Geschichten" + +#: models.py:20 +msgid "Recipes" +msgstr "Rezepte" + +#: models.py:21 +msgid "Family meetings" +msgstr "Familientreffen" + +#: models.py:22 +msgid "Recollections" +msgstr "Erinnerungen" + +#: models.py:23 +msgid "Arts" +msgstr "Arts" + +#: models.py:24 +msgid "Miscellaneous" +msgstr "Verschiedenes" + +#: models.py:27 +msgid "Description of the treasure" +msgstr "Beschreibung des Schatzes" + +#: models.py:28 +msgid "Foto des Schatzes" +msgstr "Foto des Schatzes" + +#: models.py:31 +msgid "Treasure file" +msgstr "Schatzkartei" + +#: models.py:33 +msgid "Category" +msgstr "Kategorie" + +#: models.py:37 +msgid "trove" +msgstr "Schatz" + +#: models.py:38 +msgid "troves" +msgstr "Schaetze" + +#: models.py:52 +msgid "A treasure must have an owner." +msgstr "Ein Schatz muss einen Besitzer haben." + +#: templates/troves/treasure_form.html:8 templates/troves/treasure_form.html:20 +msgid "Modify treasure" +msgstr "Ändern Sie den Schatz" + +#: templates/troves/treasure_form.html:10 +#: templates/troves/treasure_form.html:22 templates/troves/trove_cave.html:17 +msgid "Add a treasure" +msgstr "Einen Schatz hinzufügen" + +#: templates/troves/treasure_form.html:34 +msgid "Submit" +msgstr "Einreichen" + +#: templates/troves/treasure_form.html:38 +msgid "Cancel" +msgstr "Abbrechen" + +#: templates/troves/trove_cave.html:15 templates/troves/trove_cave.html:21 +msgid "The treasure trove" +msgstr "Die Schatzthöle" diff --git a/troves/locale/es/LC_MESSAGES/django.mo b/troves/locale/es/LC_MESSAGES/django.mo new file mode 100644 index 00000000..a19d7927 Binary files /dev/null and b/troves/locale/es/LC_MESSAGES/django.mo differ diff --git a/troves/locale/es/LC_MESSAGES/django.po b/troves/locale/es/LC_MESSAGES/django.po new file mode 100644 index 00000000..4cef0eb9 --- /dev/null +++ b/troves/locale/es/LC_MESSAGES/django.po @@ -0,0 +1,93 @@ +# Spanish Translations for Troves app. +# Copyright (C) 2024 Olivier LEVILLAIN +# This file is distributed under the same license as the Troves package. +# Olivier LEVILLAIN , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: 0.1\n" +"Report-Msgid-Bugs-To: Olivier LEVILLAIN \n" +"POT-Creation-Date: 2024-12-30 12:56+0100\n" +"PO-Revision-Date: 2024-12-30 12:56+0100\n" +"Last-Translator: Olivier LEVILLAIN \n" +"Language-Team: Spanish \n" +"Language: ES\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + + +#: models.py:19 +msgid "History & Stories" +msgstr "Historia y anécdotas" + +#: models.py:20 +msgid "Recipes" +msgstr "Recetas" + +#: models.py:21 +msgid "Family meetings" +msgstr "Reuniones familiares" + +#: models.py:22 +msgid "Recollections" +msgstr "Recuerdos" + +#: models.py:23 +msgid "Arts" +msgstr "Artes" + +#: models.py:24 +msgid "Miscellaneous" +msgstr "Varios" + +#: models.py:27 +msgid "Description of the treasure" +msgstr "Descripción del tesoro" + +#: models.py:28 +msgid "Treasure photo" +msgstr "Foto del tesoro" + +#: models.py:31 +msgid "Treasure file" +msgstr "Archivo del tesoro" + +#: models.py:33 +msgid "Category" +msgstr "Categoría" + +#: models.py:37 +msgid "trove" +msgstr "tesoro" + +#: models.py:38 +msgid "troves" +msgstr "tesoros" + +#: models.py:52 +msgid "A treasure must have an owner." +msgstr "Un tesoro debe tener dueño." + +#: templates/troves/treasure_form.html:8 templates/troves/treasure_form.html:20 +msgid "Modify treasure" +msgstr "Modificar tesoro" + +#: templates/troves/treasure_form.html:10 +#: templates/troves/treasure_form.html:22 templates/troves/trove_cave.html:17 +msgid "Add a treasure" +msgstr "Añade un tesoro" + +#: templates/troves/treasure_form.html:34 +msgid "Submit" +msgstr "Enviar" + +#: templates/troves/treasure_form.html:38 +msgid "Cancel" +msgstr "Cancelar" + +#: templates/troves/trove_cave.html:15 templates/troves/trove_cave.html:21 +msgid "The treasure trove" +msgstr "la cueva del tesoro" diff --git a/troves/locale/fr/LC_MESSAGES/django.mo b/troves/locale/fr/LC_MESSAGES/django.mo new file mode 100644 index 00000000..0ab44d62 Binary files /dev/null and b/troves/locale/fr/LC_MESSAGES/django.mo differ diff --git a/troves/locale/fr/LC_MESSAGES/django.po b/troves/locale/fr/LC_MESSAGES/django.po new file mode 100644 index 00000000..ff468036 --- /dev/null +++ b/troves/locale/fr/LC_MESSAGES/django.po @@ -0,0 +1,93 @@ +# French Translations for Troves app. +# Copyright (C) 2024 Olivier LEVILLAIN +# This file is distributed under the same license as the Troves package. +# Olivier LEVILLAIN , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: 0.1\n" +"Report-Msgid-Bugs-To: Olivier LEVILLAIN \n" +"POT-Creation-Date: 2024-12-30 12:56+0100\n" +"PO-Revision-Date: 2024-12-30 12:56+0100\n" +"Last-Translator: Olivier LEVILLAIN \n" +"Language-Team: French \n" +"Language: FR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + + +#: models.py:19 +msgid "History & Stories" +msgstr "Histoire et histoires" + +#: models.py:20 +msgid "Recipes" +msgstr "Recettes" + +#: models.py:21 +msgid "Family meetings" +msgstr "Réunions de famille" + +#: models.py:22 +msgid "Recollections" +msgstr "Souvenirs" + +#: models.py:23 +msgid "Arts" +msgstr "Arts" + +#: models.py:24 +msgid "Miscellaneous" +msgstr "Divers" + +#: models.py:27 +msgid "Description of the treasure" +msgstr "Description du trésor" + +#: models.py:28 +msgid "Treasure photo" +msgstr "Photo du trésor" + +#: models.py:31 +msgid "Treasure file" +msgstr "Fichier contenant le trésor" + +#: models.py:33 +msgid "Category" +msgstr "Catégorie" + +#: models.py:37 +msgid "trove" +msgstr "trésor" + +#: models.py:38 +msgid "troves" +msgstr "trésors" + +#: models.py:52 +msgid "A treasure must have an owner." +msgstr "Un trésor doit avoir un propriétaire." + +#: templates/troves/treasure_form.html:8 templates/troves/treasure_form.html:20 +msgid "Modify treasure" +msgstr "Modifier le trésor" + +#: templates/troves/treasure_form.html:10 +#: templates/troves/treasure_form.html:22 templates/troves/trove_cave.html:17 +msgid "Add a treasure" +msgstr "Ajouter un trésor" + +#: templates/troves/treasure_form.html:34 +msgid "Submit" +msgstr "Valider" + +#: templates/troves/treasure_form.html:38 +msgid "Cancel" +msgstr "Annuler" + +#: templates/troves/trove_cave.html:15 templates/troves/trove_cave.html:21 +msgid "The treasure trove" +msgstr "La caverne aux trésors" diff --git a/troves/locale/it/LC_MESSAGES/django.mo b/troves/locale/it/LC_MESSAGES/django.mo new file mode 100644 index 00000000..a8279302 Binary files /dev/null and b/troves/locale/it/LC_MESSAGES/django.mo differ diff --git a/troves/locale/it/LC_MESSAGES/django.po b/troves/locale/it/LC_MESSAGES/django.po new file mode 100644 index 00000000..542b4c57 --- /dev/null +++ b/troves/locale/it/LC_MESSAGES/django.po @@ -0,0 +1,92 @@ +# Italian Translations for Troves app. +# Copyright (C) 2024 Olivier LEVILLAIN +# This file is distributed under the same license as the Troves package. +# Olivier LEVILLAIN , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: 0.1\n" +"Report-Msgid-Bugs-To: Olivier LEVILLAIN \n" +"POT-Creation-Date: 2024-12-30 12:56+0100\n" +"PO-Revision-Date: 2024-12-30 12:56+0100\n" +"Last-Translator: Olivier LEVILLAIN \n" +"Language-Team: Italian \n" +"Language: IT\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: models.py:19 +msgid "History & Stories" +msgstr "Storia e storie" + +#: models.py:20 +msgid "Recipes" +msgstr "Ricette" + +#: models.py:21 +msgid "Family meetings" +msgstr "Riunioni di famiglia" + +#: models.py:22 +msgid "Recollections" +msgstr "Ricordi" + +#: models.py:23 +msgid "Arts" +msgstr "Arte" + +#: models.py:24 +msgid "Miscellaneous" +msgstr "Varie" + +#: models.py:27 +msgid "Description of the treasure" +msgstr "Descrizione del tesoro" + +#: models.py:28 +msgid "Treasure photo" +msgstr "Foto del tesoro" + +#: models.py:31 +msgid "Treasure file" +msgstr "File del tesoro" + +#: models.py:33 +msgid "Category" +msgstr "Categoria" + +#: models.py:37 +msgid "trove" +msgstr "tesoro" + +#: models.py:38 +msgid "troves" +msgstr "tesori" + +#: models.py:52 +msgid "A treasure must have an owner." +msgstr "Un tesoro deve avere un proprietario." + +#: templates/troves/treasure_form.html:8 templates/troves/treasure_form.html:20 +msgid "Modify treasure" +msgstr "Modificare il tesoro" + +#: templates/troves/treasure_form.html:10 +#: templates/troves/treasure_form.html:22 templates/troves/trove_cave.html:17 +msgid "Add a treasure" +msgstr "Aggiungere un tesoro" + +#: templates/troves/treasure_form.html:34 +msgid "Submit" +msgstr "Invia" + +#: templates/troves/treasure_form.html:38 +msgid "Cancel" +msgstr "Annullamento" + +#: templates/troves/trove_cave.html:15 templates/troves/trove_cave.html:21 +msgid "The treasure trove" +msgstr "La grotta del tesoro" diff --git a/troves/locale/pt/LC_MESSAGES/django.mo b/troves/locale/pt/LC_MESSAGES/django.mo new file mode 100644 index 00000000..9273a9e8 Binary files /dev/null and b/troves/locale/pt/LC_MESSAGES/django.mo differ diff --git a/troves/locale/pt/LC_MESSAGES/django.po b/troves/locale/pt/LC_MESSAGES/django.po new file mode 100644 index 00000000..3f18d4c6 --- /dev/null +++ b/troves/locale/pt/LC_MESSAGES/django.po @@ -0,0 +1,92 @@ +# Portuguese Translations for Troves app. +# Copyright (C) 2024 Olivier LEVILLAIN +# This file is distributed under the same license as the Troves package. +# Olivier LEVILLAIN , 2024. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: 0.1\n" +"Report-Msgid-Bugs-To: Olivier LEVILLAIN \n" +"POT-Creation-Date: 2024-12-30 12:56+0100\n" +"PO-Revision-Date: 2024-12-30 12:56+0100\n" +"Last-Translator: Olivier LEVILLAIN \n" +"Language-Team: Portuguese \n" +"Language: PT\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: models.py:19 +msgid "History & Stories" +msgstr "História e histórias" + +#: models.py:20 +msgid "Recipes" +msgstr "Ricette" + +#: models.py:21 +msgid "Family meetings" +msgstr "Reuniões familiares" + +#: models.py:22 +msgid "Recollections" +msgstr "Recolhas" + +#: models.py:23 +msgid "Arts" +msgstr "Artes" + +#: models.py:24 +msgid "Miscellaneous" +msgstr "Diversos" + +#: models.py:27 +msgid "Description of the treasure" +msgstr "Descrição do tesouro" + +#: models.py:28 +msgid "Treasure photo" +msgstr "Foto do tesouro" + +#: models.py:31 +msgid "Treasure file" +msgstr "Ficheiro do tesouro" + +#: models.py:33 +msgid "Category" +msgstr "Categoria" + +#: models.py:37 +msgid "trove" +msgstr "tesouro" + +#: models.py:38 +msgid "troves" +msgstr "tesouro" + +#: models.py:52 +msgid "A treasure must have an owner." +msgstr "tesouros" + +#: templates/troves/treasure_form.html:8 templates/troves/treasure_form.html:20 +msgid "Modify treasure" +msgstr "Modificar o tesouro" + +#: templates/troves/treasure_form.html:10 +#: templates/troves/treasure_form.html:22 templates/troves/trove_cave.html:17 +msgid "Add a treasure" +msgstr "Adicionar um tesouro" + +#: templates/troves/treasure_form.html:34 +msgid "Submit" +msgstr "Enviar" + +#: templates/troves/treasure_form.html:38 +msgid "Cancel" +msgstr "Annullamento" + +#: templates/troves/trove_cave.html:15 templates/troves/trove_cave.html:21 +msgid "The treasure trove" +msgstr "A caverna do tesouro" diff --git a/troves/migrations/0001_initial.py b/troves/migrations/0001_initial.py new file mode 100644 index 00000000..f033dac9 --- /dev/null +++ b/troves/migrations/0001_initial.py @@ -0,0 +1,44 @@ +# Generated by Django 5.1.1 on 2024-12-29 09:47 + +import pathlib +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Trove', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('description', models.TextField(verbose_name='Description of the treasure')), + ('picture', models.ImageField(upload_to=pathlib.PurePosixPath('troves/pictures'), + verbose_name='Treasure photo')), + ('thumbnail', models.ImageField(upload_to=pathlib.PurePosixPath('troves/pictures/thumbnails'))), + ('file', models.FileField(blank=True, null=True, upload_to=pathlib.PurePosixPath('troves/files'), + verbose_name='Treasure file')), + ('category', models.CharField(choices=[('history', 'History & Stories'), + ('recipes', 'Recipes'), + ('cousinades', 'Family meetings'), + ('recollections', 'Recollections'), + ('arts', 'Arts'), + ('miscellaneous', 'Miscellaneous') + ], + max_length=20, verbose_name='Category')), + ('owner', models.ForeignKey(on_delete=models.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'trove', + 'verbose_name_plural': 'troves', + 'ordering': ['id'], + 'indexes': [models.Index(fields=['category'], name='troves_trov_categor_900909_idx')], + }, + ), + ] diff --git a/troves/migrations/0002_mkdirs.py b/troves/migrations/0002_mkdirs.py new file mode 100644 index 00000000..51034d10 --- /dev/null +++ b/troves/migrations/0002_mkdirs.py @@ -0,0 +1,21 @@ +import os +from django.conf import settings +from django.db import migrations + +def mkDirs(apps, schema_editor): + """Create directories for pictures, thumbnails and files. Used by migration 0002_mkdirs""" + for dir in [settings.TROVE_PICTURE_DIRECTORY, + settings.TROVE_THUMBNAIL_DIRECTORY, + settings.TROVE_FILES_DIRECTORY]: + os.makedirs(os.path.join(settings.MEDIA_ROOT, dir), exist_ok=True) + + +class Migration(migrations.Migration): + + dependencies = [ + ('troves', '0001_initial'), + ] + + operations = [ + migrations.RunPython(mkDirs), + ] diff --git a/troves/migrations/__init__.py b/troves/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/troves/models.py b/troves/models.py new file mode 100644 index 00000000..19158d2d --- /dev/null +++ b/troves/models.py @@ -0,0 +1,97 @@ +import logging +import os +import sys +from django.conf import settings +from django.core.files.uploadedfile import InMemoryUploadedFile +from django.core.exceptions import ValidationError +from django.db import models +from django.utils.translation import gettext_lazy as _ +from io import BytesIO +from PIL import Image, ImageOps, ImageFile + +logger = logging.getLogger(__name__) +# issue #120 try to avoid error about truncated images when creating thumbnails +ImageFile.LOAD_TRUNCATED_IMAGES = True + + +class Trove(models.Model): + CATEGORY_CHOICES = [ + ('history', _('History & Stories')), + ('recipes', _('Recipes')), + ('cousinades', _('Family meetings')), + ('recollections', _('Recollections')), + ('arts', _('Arts')), + ('miscellaneous', _('Miscellaneous')), + ] + + description = models.TextField(verbose_name=_("Description of the treasure"), null=False, blank=False) + picture = models.ImageField(upload_to=settings.TROVE_PICTURE_DIRECTORY, verbose_name=_("Treasure photo"), + null=False, blank=False) + thumbnail = models.ImageField(upload_to=settings.TROVE_THUMBNAIL_DIRECTORY, blank=True) + file = models.FileField(upload_to=settings.TROVE_FILES_DIRECTORY, verbose_name=_("Treasure file"), + blank=True, null=True) + category = models.CharField(max_length=20, choices=CATEGORY_CHOICES, verbose_name=_("Category")) + owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) + + class Meta: + verbose_name = _('trove') + verbose_name_plural = _('troves') + ordering = ['id'] + indexes = [ + models.Index(fields=["category"]), + ] + + def __str__(self): + return self.description + + @staticmethod + def translate_category(category): + return dict(Trove.CATEGORY_CHOICES)[category] + + def _thumbnail_path(self): + return os.path.join(settings.TROVE_THUMBNAIL_DIRECTORY, os.path.basename(self.picture.path)) + + def clean(self): + if self.owner is None: + raise ValidationError(_("A treasure must have an owner.")) + + def save(self, *args, **kwargs): + self.full_clean() # clean before save + super().save(*args, **kwargs) + try: + # create thumbnail + tns = settings.TROVE_THUMBNAIL_SIZE + output_thumb = BytesIO() + filename = os.path.basename(self.picture.path) + file, ext = os.path.splitext(filename) + with Image.open(self.picture.path) as img: + if img.height > tns or img.width > tns: + size = tns, tns + img.thumbnail(size) + img = ImageOps.exif_transpose(img) # avoid image rotating + img.save(output_thumb, format='JPEG', quality=90) + size = sys.getsizeof(output_thumb) + self.thumbnail = InMemoryUploadedFile(output_thumb, 'ImageField', f"{file}.jpg", + 'image/jpeg', size, None) + logger.debug(f"Resized and saved thumbnail for {self.picture.path} to {self.thumbnail.path}, size={size}") + else: # small photos are used directly as thumbnails + self.thumbnail = self.picture + + super().save(force_update=True, update_fields=['thumbnail']) + + except Exception as e: + # issue #120: if any exception during the thumbnail creation process, remove the photo from the database + self.delete() + raise ValidationError(f"Error during saving picture: {e}") + + def delete(self, *args, **kwargs): + self.delete_picture() + super().delete(*args, **kwargs) + + def delete_picture(self): + if os.path.isfile(self.picture.path): + os.remove(self.picture.path) + thumbnail_path = os.path.join(settings.TROVE_THUMBNAIL_DIRECTORY, os.path.basename(self.picture.path)) + if os.path.isfile(thumbnail_path): + os.remove(thumbnail_path) + self.picture = None diff --git a/troves/templates/troves/treasure_form.html b/troves/templates/troves/treasure_form.html new file mode 100644 index 00000000..628da5fe --- /dev/null +++ b/troves/templates/troves/treasure_form.html @@ -0,0 +1,45 @@ +{% extends "cm_main/base.html" %} +{% load i18n crispy_forms_tags cm_tags %} +{% block header %} + {%include "cm_main/common/include-summernote.html" with maxsize=settings.TROVE_DESCRIPTION_MAX_SIZE %} +{%endblock%} +{% block title %} + {% if form.instance.pk %} + {% title _("Modify treasure") %} + {%else%} + {% title _("Add a treasure") %} + {%endif%} +{% endblock %} + +{% block content %} +
    +
    +
    +

    + {% if form.instance.pk %} + {% title _("Modify treasure") %} + {%else%} + {% title _("Add a treasure") %} + {%endif%} +

    +
    +
    + {% csrf_token %} +
    + {{ form | crispy}} +
    + +
    +
    +
    +{% endblock %} diff --git a/troves/templates/troves/trove_cave.html b/troves/templates/troves/trove_cave.html new file mode 100644 index 00000000..1352689e --- /dev/null +++ b/troves/templates/troves/trove_cave.html @@ -0,0 +1,54 @@ +{% extends "cm_main/base.html" %} +{%load i18n cm_tags troves_tags%} +{%block header%} + +{%endblock%} +{% block title %}{% title _("The treasure trove") %}{% endblock %} +{% block content %} +{% trans 'Add a treasure' as create_label %} +
    +
    +
    + {% title _("The treasure trove") %} + + {%icon "new-treasure" %} {{create_label}} + +
    +
    + {%paginate page%} +
    +
    + {% for category, troves in page.object_list.items %} +

    {{ category|translate_category }}

    +
    + {% for trove in troves %} +
    +
    + + {{ trove.description }} + +
    +

    {% autoescape off %}{{ trove.description }}{% endautoescape %}

    + {%if trove.owner.id == user.id %} +

    + {%icon "edit"%} + {%url "troves:delete" trove.id as delete_url%} + {%icon "delete"%} +

    + {%endif%} +
    + {% endfor %} +
    + {% endfor %} +
    +{% endblock %} \ No newline at end of file diff --git a/troves/templatetags/__init__.py b/troves/templatetags/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/troves/templatetags/troves_tags.py b/troves/templatetags/troves_tags.py new file mode 100644 index 00000000..664389de --- /dev/null +++ b/troves/templatetags/troves_tags.py @@ -0,0 +1,11 @@ +from django import template + +from troves.models import Trove + +register = template.Library() + + +@register.filter(is_safe=True) +def translate_category(category): + """translate a category""" + return Trove.translate_category(category) diff --git a/troves/tests/__init__.py b/troves/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/troves/tests/resources/test-image-1.jpg b/troves/tests/resources/test-image-1.jpg new file mode 100644 index 00000000..6fcd7e1c Binary files /dev/null and b/troves/tests/resources/test-image-1.jpg differ diff --git a/troves/tests/resources/test-image-2.jpg b/troves/tests/resources/test-image-2.jpg new file mode 100644 index 00000000..6df24b24 Binary files /dev/null and b/troves/tests/resources/test-image-2.jpg differ diff --git a/troves/tests/resources/test-image-3.jpg b/troves/tests/resources/test-image-3.jpg new file mode 100644 index 00000000..430c1a8c Binary files /dev/null and b/troves/tests/resources/test-image-3.jpg differ diff --git a/troves/tests/tests.py b/troves/tests/tests.py new file mode 100644 index 00000000..3696248e --- /dev/null +++ b/troves/tests/tests.py @@ -0,0 +1,105 @@ +from django.urls import reverse +from cousinsmatter.utils import create_test_image, test_media_root_decorator +from members.tests.tests_member_base import MemberTestCase +from ..models import Trove + + +@test_media_root_decorator(__file__) +class TestTroveList(MemberTestCase): + def setUp(self): + self.treasures_data = [ + { + 'description': 'Description 1', + 'picture': create_test_image(__file__, "test-image-1.jpg"), + 'category': 'history', + 'owner': self.member, + }, + { + 'description': 'Description 2', + 'picture': create_test_image(__file__, "test-image-2.jpg"), + 'file': create_test_image(__file__, "test-image-3.jpg"), + 'category': 'recipes', + 'owner': self.member, + }, + ] + self.treasures = [] + super().setUp() + + def tearDown(self): + Trove.objects.all().delete() + self.treasures = [] + return super().tearDown() + + def check_treasure(self, treasure, treasure_data): + self.assertEqual(treasure.description, treasure_data['description']) + self.assertEqual(treasure.category, treasure_data['category']) + self.assertEqual(treasure.owner, self.member) + self.assertTrue(treasure.picture) + # self.assertEqual(treasure.picture.url, treasure_data['picture'].url) + self.assertTrue(treasure.thumbnail) + if 'file' in treasure_data: + self.assertTrue(treasure.file) + else: + self.assertFalse(treasure.file) + + def create_troves(self): + for treasure_data in self.treasures_data: + treasure = Trove.objects.create(**treasure_data) + self.check_treasure(treasure, treasure_data) + self.treasures.append(treasure) + + def check_treasure_in_response(self, treasure, response): + # Warning: this works only because there is only two objects and they are both in the same page + self.assertContains(response, treasure.description) + self.assertContains(response, Trove.translate_category(treasure.category)) + self.assertContains(response, treasure.thumbnail.url) + if treasure.file: + self.assertContains(response, treasure.file.url) + self.assertNotContains(response, treasure.picture.url) + else: + self.assertContains(response, treasure.picture.url) + + def test_create_troves(self): + response = self.client.get(reverse('troves:create'), follow=True) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'troves/treasure_form.html') + + for treasure_data in self.treasures_data: + response = self.client.post(reverse('troves:create'), treasure_data, follow=True) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'troves/trove_cave.html') + treasure = Trove.objects.get(description=treasure_data['description']) + self.check_treasure(treasure, treasure_data) + self.check_treasure_in_response(treasure, response) + + def test_trove_list(self): + self.create_troves() + response = self.client.get(reverse('troves:list'), follow=True) + self.assertEqual(response.status_code, 200) + for treasure in self.treasures: + self.check_treasure_in_response(treasure, response) + + def test_modify_treasure(self): + self.create_troves() + treasure = self.treasures[0] + updated_treasure_data = { + 'description': 'New Description', + 'picture': create_test_image(__file__, "test-image-3.jpg"), + 'category': 'history', + 'owner': self.member, + } + + response = self.client.get(reverse('troves:update', kwargs={'pk': treasure.id}), follow=True) + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, 'troves/treasure_form.html') + self.assertContains(response, treasure.description, html=True) + self.assertContains(response, Trove.translate_category(treasure.category), html=True) + + response = self.client.post(reverse('troves:update', kwargs={'pk': treasure.id}), + updated_treasure_data, follow=True) + self.assertEqual(response.status_code, 200) + # self.print_response(response) + self.assertTemplateUsed(response, 'troves/trove_cave.html') + treasure = Trove.objects.get(id=treasure.id) + self.check_treasure(treasure, updated_treasure_data) + self.check_treasure_in_response(treasure, response) diff --git a/troves/urls.py b/troves/urls.py new file mode 100644 index 00000000..8469656c --- /dev/null +++ b/troves/urls.py @@ -0,0 +1,12 @@ +from django.urls import path +from . import views + +app_name = "troves" + +urlpatterns = [ + path('', views.trove_cave, name='list'), + path("page/", views.trove_cave, name="page"), + path('create/', views.create_treasure, name='create'), + path('/update/', views.update_treasure, name='update'), + path('/delete/', views.delete_treasure, name='delete'), +] diff --git a/troves/views.py b/troves/views.py new file mode 100644 index 00000000..3da4614e --- /dev/null +++ b/troves/views.py @@ -0,0 +1,62 @@ +from django.conf import settings +from django.contrib.auth.decorators import login_required +from django.http import JsonResponse +from django.shortcuts import redirect, render, get_object_or_404 +from django.urls import reverse +from django.views.decorators.csrf import csrf_exempt +from cousinsmatter.utils import PageOutOfBounds, Paginator, assert_request_is_ajax +from members.models import Member +from .models import Trove +from .forms import TreasureForm + + +@login_required +def trove_cave(request, page=1): + treasures = Trove.objects.all().order_by('category', 'id') + try: + trove_page = Paginator.get_page(request, object_list=treasures, + page_num=page, + reverse_link='troves:page', + default_page_size=settings.DEFAULT_TROVE_PAGE_SIZE, + group_by='category') + return render(request, "troves/trove_cave.html", {"page": trove_page}) + except PageOutOfBounds as exc: + return redirect(exc.redirect_to) + + +@login_required +def create_treasure(request): + if request.method == 'POST': + form = TreasureForm(request.POST, request.FILES) + owner = Member.objects.only('id').get(id=request.user.id) + form.instance.owner_id = owner.id + if form.is_valid(): + form.save() + return redirect(reverse('troves:list')) # try to go to last page + else: + form = TreasureForm() + return render(request, 'troves/treasure_form.html', {'form': form}) + + +@login_required +def update_treasure(request, pk): + treasure = get_object_or_404(Trove, pk=pk) + if request.method == 'POST': + form = TreasureForm(request.POST, request.FILES, instance=treasure) + if form.is_valid(): + form.save() + return redirect(reverse('troves:list')) # try to go to last page + else: + form = TreasureForm(instance=treasure) + return render(request, 'troves/treasure_form.html', {'form': form}) + + +@csrf_exempt +@login_required +def delete_treasure(request, pk): + assert_request_is_ajax(request) + try: + get_object_or_404(Trove, pk=pk).delete() + return JsonResponse({'deleted': True}) + except Exception: + return JsonResponse({'deleted': False}) \ No newline at end of file