diff --git a/members/locale/de/LC_MESSAGES/django.mo b/members/locale/de/LC_MESSAGES/django.mo
index a8536595..688a4e57 100644
Binary files a/members/locale/de/LC_MESSAGES/django.mo and b/members/locale/de/LC_MESSAGES/django.mo differ
diff --git a/members/locale/de/LC_MESSAGES/django.po b/members/locale/de/LC_MESSAGES/django.po
index d078bd05..f5d9d092 100644
--- a/members/locale/de/LC_MESSAGES/django.po
+++ b/members/locale/de/LC_MESSAGES/django.po
@@ -12,7 +12,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 0.1\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-11-11 22:23+0100\n"
+"POT-Creation-Date: 2024-11-17 11:13+0100\n"
"PO-Revision-Date: 2024-10-21 09:04+00:00\n"
"Last-Translator: Auto-po-lyglot using qwen2.5:3b (https://github.com/"
"leolivier/auto-po-lyglot)\n"
@@ -34,9 +34,8 @@ msgid ""
"By checking this box, you agree to this site's privacy policy"
msgstr ""
-"Durch Anklicken dieses Kästchens erklärst du dich mit den Datenschutzrichtlinien "
-"dieser Website einverstanden."
+"Durch Anklicken dieses Kästchens erklärst du dich mit den Datenschutzrichtlinien dieser Website einverstanden."
#: members/forms.py:125
msgid "Name of the invited person (will appear in the received email)"
@@ -163,135 +162,140 @@ msgctxt "CSV Field"
msgid "deathdate"
msgstr "Todesdatum"
-#: members/models.py:45
+#: members/models.py:38
+msgctxt "CSV Field"
+msgid "managed_by"
+msgstr "verwaltet_von"
+
+#: members/models.py:46
#: members/templates/members/members/members_directory.html:29
#: members/views/views_directory.py:59
msgid "Name"
msgstr "Name"
-#: members/models.py:47 members/templates/members/family/family_detail.html:14
+#: members/models.py:48 members/templates/members/family/family_detail.html:14
msgid "Parent family"
msgstr "Elternfamilie"
-#: members/models.py:50
+#: members/models.py:51
msgid "family"
msgstr "Familie"
-#: members/models.py:51
+#: members/models.py:52
msgid "families"
msgstr "Familien"
-#: members/models.py:66 members/templates/members/address/address_detail.html:8
+#: members/models.py:67 members/templates/members/address/address_detail.html:8
msgid "Number & Street name"
msgstr "Nummer & Straße"
-#: members/models.py:68
+#: members/models.py:69
msgid "Complementary info"
msgstr "Komplizierte Informationen"
-#: members/models.py:70
+#: members/models.py:71
#: members/templates/members/address/address_detail.html:16
msgid "Zip code"
msgstr "Gebietscode"
-#: members/models.py:72
+#: members/models.py:73
#: members/templates/members/address/address_detail.html:20
msgid "City"
msgstr "Stadt"
-#: members/models.py:74
+#: members/models.py:75
#: members/templates/members/address/address_detail.html:24
msgid "Country"
msgstr "Land"
-#: members/models.py:88
+#: members/models.py:89
msgid "address"
msgstr "Adresse"
-#: members/models.py:89
+#: members/models.py:90
msgid "addresses"
msgstr "Adressen"
-#: members/models.py:97
-msgid "Managing member"
-msgstr "Geschäftsführer"
+#: members/models.py:98
+msgid "Member manager"
+msgstr "Mitglied Manager"
-#: members/models.py:102
+#: members/models.py:103
#: members/templates/members/members/member_detail.html:37
#: members/templates/members/members/members_directory.html:33
#: members/views/views_directory.py:59
msgid "Address"
msgstr "Adresse"
-#: members/models.py:104
+#: members/models.py:105
#: members/templates/members/members/member_detail.html:45
#: members/templates/members/members/members_directory.html:31
#: members/views/views_directory.py:59
msgid "Phone"
msgstr "Telefon"
-#: members/models.py:106
+#: members/models.py:107
#: members/templates/members/members/member_detail.html:49
#: members/templates/members/members/members_directory.html:30
msgid "Birthdate"
msgstr "Geburtsdatum"
-#: members/models.py:107
+#: members/models.py:108
msgid "Click on the month name or the year to change them quickly"
msgstr "Klicke auf den Monat oder das Jahr, um sie schnell zu ändern"
-#: members/models.py:110
+#: members/models.py:111
msgid "Is dead"
msgstr "Ist tot"
-#: members/models.py:111
+#: members/models.py:112
msgid "Death date"
msgstr "Todesdatum"
-#: members/models.py:113
+#: members/models.py:114
#: members/templates/members/members/member_detail.html:53
msgid "Website"
msgstr "Website"
-#: members/models.py:115
+#: members/models.py:116
#: members/templates/members/members/member_detail.html:57
msgid "Family"
msgstr "Familie"
-#: members/models.py:117
+#: members/models.py:118
#: members/templates/members/members/member_detail.html:61
msgid "Who I am"
msgstr "Wer ich bin"
-#: members/models.py:118
+#: members/models.py:119
msgid "Describe yourself, your likes and dislikes..."
msgstr "Beschreibe dich selbst, deine Vorlieben und Unvorlieben..."
-#: members/models.py:119
+#: members/models.py:120
msgid "My hobbies"
msgstr "Meine Hobbys"
-#: members/models.py:120
+#: members/models.py:121
msgid "Provide a list of hobbies separated by commas"
msgstr "Bitte gib eine Liste von Hobbys, getrennt durch Kommas, an."
-#: members/models.py:122
+#: members/models.py:123
msgid "Privacy consent"
msgstr "Datenschutz-Einwilligung"
-#: members/models.py:124
+#: members/models.py:125
msgid "Followers"
msgstr "Follower"
-#: members/models.py:133
+#: members/models.py:134
msgid "member"
msgstr "Mitglied"
-#: members/models.py:134
+#: members/models.py:135
msgid "members"
msgstr "Mitglieder"
-#: members/models.py:192
+#: members/models.py:195
#, python-brace-format
msgid "Death date {dd} is before birthdate {bd}"
msgstr "Todesdatum {dd} ist vor Geburtsdatum {bd}"
@@ -351,7 +355,7 @@ msgid "Greetings!"
msgstr "Hallo!"
#: members/templates/members/email/email_verification_msg.html:27
-#: members/tests/tests_member.py:330
+#: members/tests/tests_member.py:347
msgid ""
"You received this mail because you attempted to create an account on our "
"website or because a member created and activated your account"
@@ -360,7 +364,7 @@ msgstr ""
"Website zu erstellen oder weil ein Mitglied dein Konto aktiviert hat."
#: members/templates/members/email/email_verification_msg.html:28
-#: members/tests/tests_member.py:332
+#: members/tests/tests_member.py:349
msgid ""
"Please click on the link below to confirm the email and activate your "
"account."
@@ -850,15 +854,15 @@ msgstr "Mitglied Details"
#: members/templates/members/members/member_detail.html:26
#: members/templates/members/members/member_upsert.html:26
-#: members/tests/tests_member.py:238
+#: members/tests/tests_member.py:236
msgid "Active member"
msgstr "Aktive Mitglied"
#: members/templates/members/members/member_detail.html:28
#: members/templates/members/members/member_upsert.html:28
-#: members/tests/tests_member.py:238
+#: members/tests/tests_member.py:236
msgid "Managed member"
-msgstr "Ge manage Member"
+msgstr "Verwaltetes Mitglied"
#: members/templates/members/members/member_detail.html:41
#: members/templates/members/members/members_directory.html:32
@@ -996,7 +1000,7 @@ msgid "Show directory"
msgstr "Zeige Verzeichnis"
#: members/templates/members/members/members.html:19
-#: members/views/views_member.py:106
+#: members/views/views_member.py:122
msgid "Create Member"
msgstr "Erstellen Sie Mitglied"
@@ -1152,16 +1156,16 @@ msgstr "Ändern Sie die ausgewählten %(name)s"
msgid "Add another %(translated_name)s"
msgstr "Fügt eine weitere %(translated_name)s hinzu"
-#: members/tests/tests_member.py:100 members/views/views_member.py:194
+#: members/tests/tests_member.py:100 members/views/views_member.py:210
msgid "Member deleted"
msgstr "Mitglied gelöscht"
-#: members/tests/tests_member.py:213 members/tests/tests_member.py:218
-#: members/views/views_member.py:143 members/views/views_member.py:157
+#: members/tests/tests_member.py:211 members/tests/tests_member.py:216
+#: members/views/views_member.py:159 members/views/views_member.py:173
msgid "You do not have permission to edit this member."
msgstr "Du hast keine Berechtigung, diese Mitgliedschaft zu bearbeiten."
-#: members/tests/tests_member.py:392
+#: members/tests/tests_member.py:409
msgid "Error: Cannot activate a dead member "
msgstr "Fehler: Ein totes Mitglied kann nicht aktiviert werden "
@@ -1287,13 +1291,14 @@ msgstr ""
"Unbekannte Spalte in der CSV-Datei: \"%(fieldname)s\". Gültige Felder sind "
"%(all_names)s"
-#: members/views/views_import_export.py:98
+#: members/views/views_import_export.py:104
#, python-format
msgid "Avatar not found: %(avatar)s for username %(username)s. Ignored..."
msgstr ""
"Avatar nicht gefunden: %(avatar)s für Benutzername %(username)s. Ignoriert..."
-#: members/views/views_import_export.py:107
+#: members/views/views_import_export.py:113
+#, python-format
msgid ""
"Error saving avatar (%(warning)s): %(avatar)s for username %(username)s. "
"Ignored..."
@@ -1301,7 +1306,36 @@ msgstr ""
"Fehler beim Speichern des Avatar- (%(warning)s): %(avatar)s für Benutzername "
"%(username)s. Ignoriert..."
-#: members/views/views_import_export.py:220
+#: members/views/views_import_export.py:126
+msgid ""
+"You requested to activate imported members. All managers will be ignored."
+msgstr ""
+"Du hast beantragt, importierte Mitglieder zu aktivieren. Alle Mitglied Managers werden ignoriert."
+
+#: members/views/views_import_export.py:129
+#, python-format
+msgid "Manager %(manager)s not found for member %(member)s. Ignoring..."
+msgstr "Manager %(manager)s nicht gefunden für Mitglied %(member)s. Ignoriert..."
+
+#: members/views/views_import_export.py:136
+#, python-format
+msgid "Member %(member)s is active. Ignoring manager %(manager)s"
+msgstr "Das Mitglied %(member)s ist aktiv. Ignoriert Mitglied Manager %(manager)s"
+
+#: members/views/views_import_export.py:145
+#, python-format
+msgid ""
+"No manager provided for member %(member)s although inactive. Keeping existing "
+"one (%(manager)s)..."
+msgstr "Für %(member)s ist kein Manager vorgesehen, obwohl inaktiv. "
+"Bestehenden Mitglied Manager beibehalten (%(manager)s)..."
+
+#: members/views/views_import_export.py:148
+#, python-format
+msgid "Inactive member %(member)s has no manager. Please provide one!"
+msgstr "Inaktives Mitglied %(member)s hat keinen Mitglied Manager. Bitte stellen Sie einen zur Verf%C3%BCgung!"
+
+#: members/views/views_import_export.py:258
#, python-format
msgid ""
"CSV file uploaded: %(nbLines)i lines read, %(nbMembers)i members created or "
@@ -1310,45 +1344,46 @@ msgstr ""
"CSV-Datei hochgeladen: %(nbLines)i Zeilen gelesen, %(nbMembers)i Mitglieder "
"erstellt oder aktualisiert"
-#: members/views/views_import_export.py:223
+#: members/views/views_import_export.py:261
+#, python-format
msgid "Warning: %(warning)s"
msgstr "Warnung: %(warning)s"
-#: members/views/views_import_export.py:290
+#: members/views/views_import_export.py:322
msgid "Method not allowed"
msgstr "Methode nicht erlaubt"
-#: members/views/views_member.py:35
+#: members/views/views_member.py:34
msgid "You have been logged out"
msgstr "Du wurdest ausgeloggt"
-#: members/views/views_member.py:96
+#: members/views/views_member.py:112
msgid "Only superusers can create members"
msgstr "Nur Superbenutzer können Mitglieder erstellen"
-#: members/views/views_member.py:121
+#: members/views/views_member.py:137
msgid "Member successfully created"
msgstr "Mitglied wurde erfolgreich erstellt"
-#: members/views/views_member.py:129
+#: members/views/views_member.py:145
msgid "Update Member Details"
msgstr "Aktualisiere Mitgliedsdetails"
-#: members/views/views_member.py:130
+#: members/views/views_member.py:146
msgid "Member successfully updated"
msgstr "Mitglied erfolgreich aktualisiert"
-#: members/views/views_member.py:167
+#: members/views/views_member.py:183
msgid "A verification email has been sent to validate your new email address."
msgstr ""
"Eine E-Mail zur Überprüfung wurde an dich gesendet, um deine neue E-Mail-"
"Adresse zu bestätigen."
-#: members/views/views_member.py:181
+#: members/views/views_member.py:197
msgid "My Profile"
msgstr "Mein Profil"
-#: members/views/views_member.py:182
+#: members/views/views_member.py:198
msgid "Profile successfully updated"
msgstr "Profil erfolgreich aktualisiert"
diff --git a/members/locale/es/LC_MESSAGES/django.mo b/members/locale/es/LC_MESSAGES/django.mo
index c95b4156..a9f57dc9 100644
Binary files a/members/locale/es/LC_MESSAGES/django.mo and b/members/locale/es/LC_MESSAGES/django.mo differ
diff --git a/members/locale/es/LC_MESSAGES/django.po b/members/locale/es/LC_MESSAGES/django.po
index b8abc018..18c0bce9 100644
--- a/members/locale/es/LC_MESSAGES/django.po
+++ b/members/locale/es/LC_MESSAGES/django.po
@@ -12,7 +12,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 0.1\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-11-11 22:23+0100\n"
+"POT-Creation-Date: 2024-11-17 11:13+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"
@@ -29,12 +29,13 @@ msgid "Members"
msgstr "Membres"
#: members/forms.py:69
+#, python-brace-format
msgid ""
"By checking this box, you agree to this site's privacy policy"
msgstr ""
"Al marcar esta casilla, acepta que este sitio política de privacidad
+"href='{privacy_url}'>política de privacidad"
#: members/forms.py:125
msgid "Name of the invited person (will appear in the received email)"
@@ -159,139 +160,143 @@ msgctxt "CSV Field"
msgid "deathdate"
msgstr "fecha_de_defuncion"
-#: members/models.py:45
+#: members/models.py:38
+msgctxt "CSV Field"
+msgid "managed_by"
+msgstr "gestionado_por"
+
+#: members/models.py:46
#: members/templates/members/members/members_directory.html:29
#: members/views/views_directory.py:59
msgid "Name"
msgstr "Nombre"
-#: members/models.py:47 members/templates/members/family/family_detail.html:14
+#: members/models.py:48 members/templates/members/family/family_detail.html:14
msgid "Parent family"
msgstr "Familia paterna"
-#: members/models.py:50
+#: members/models.py:51
msgid "family"
msgstr "familia"
-#: members/models.py:51
+#: members/models.py:52
msgid "families"
msgstr "familias"
-#: members/models.py:66 members/templates/members/address/address_detail.html:8
+#: members/models.py:67 members/templates/members/address/address_detail.html:8
msgid "Number & Street name"
msgstr "Número y nombre de la calle"
-#: members/models.py:68
+#: members/models.py:69
msgid "Complementary info"
msgstr "Información complementaria"
-#: members/models.py:70
+#: members/models.py:71
#: members/templates/members/address/address_detail.html:16
msgid "Zip code"
msgstr "Código postal"
-#: members/models.py:72
+#: members/models.py:73
#: members/templates/members/address/address_detail.html:20
msgid "City"
msgstr "Ville"
-#: members/models.py:74
+#: members/models.py:75
#: members/templates/members/address/address_detail.html:24
msgid "Country"
msgstr "Pais"
-#: members/models.py:88
+#: members/models.py:89
msgid "address"
msgstr "dirección"
-#: members/models.py:89
+#: members/models.py:90
msgid "addresses"
msgstr "adres"
-#: members/models.py:97
-msgid "Managing member"
-msgstr "Comprador gestor de este miembro"
+#: members/models.py:98
+msgid "Member manager"
+msgstr "Gestor de miembro"
-#: members/models.py:102
+#: members/models.py:103
#: members/templates/members/members/member_detail.html:37
#: members/templates/members/members/members_directory.html:33
#: members/views/views_directory.py:59
msgid "Address"
msgstr "Dirección"
-#: members/models.py:104
+#: members/models.py:105
#: members/templates/members/members/member_detail.html:45
#: members/templates/members/members/members_directory.html:31
#: members/views/views_directory.py:59
msgid "Phone"
msgstr "Télefono"
-#: members/models.py:106
+#: members/models.py:107
#: members/templates/members/members/member_detail.html:49
#: members/templates/members/members/members_directory.html:30
msgid "Birthdate"
msgstr "Fecha de nacimiento"
-#: members/models.py:107
+#: members/models.py:108
msgid "Click on the month name or the year to change them quickly"
msgstr "Clickea en el nombre del mes o del año para modificarlos rápidamente"
-#: members/models.py:110
+#: members/models.py:111
msgid "Is dead"
msgstr "Ha muerto"
-#: members/models.py:111
+#: members/models.py:112
msgid "Death date"
msgstr "Fecha de defunción"
-#: members/models.py:113
+#: members/models.py:114
#: members/templates/members/members/member_detail.html:53
msgid "Website"
msgstr "Sitio web"
-#: members/models.py:115
+#: members/models.py:116
#: members/templates/members/members/member_detail.html:57
msgid "Family"
msgstr "Familia"
-#: members/models.py:117
+#: members/models.py:118
#: members/templates/members/members/member_detail.html:61
msgid "Who I am"
msgstr "Qui soyé"
-#: members/models.py:118
+#: members/models.py:119
msgid "Describe yourself, your likes and dislikes..."
msgstr "Describe-te, di qué te amas o no amas..."
-#: members/models.py:119
+#: members/models.py:120
msgid "My hobbies"
msgstr "Mis pasiones"
-#: members/models.py:120
+#: members/models.py:121
msgid "Provide a list of hobbies separated by commas"
msgstr "Dá una lista de hobbies separados por comas"
-#: members/models.py:122
+#: members/models.py:123
msgid "Privacy consent"
msgstr "Consentimiento a la protección de la privacidad"
-#: members/models.py:124
+#: members/models.py:125
msgid "Followers"
msgstr "Followers"
-#: members/models.py:133
+#: members/models.py:134
msgid "member"
msgstr "miembro"
-#: members/models.py:134
+#: members/models.py:135
msgid "members"
msgstr "miembros"
-#: members/models.py:192
+#: members/models.py:195
#, python-brace-format
msgid "Death date {dd} is before birthdate {bd}"
-msgstr "La fecha de defunción {dd} es anterior a "
-"la fecha de nacimiento {bd}."
+msgstr "La fecha de defunción {dd} es anterior a la fecha de nacimiento {bd}."
#: members/templates/members/address/address_detail.html:3
msgid "Address Detail"
@@ -348,7 +353,7 @@ msgid "Greetings!"
msgstr "Salutaciones!"
#: members/templates/members/email/email_verification_msg.html:27
-#: members/tests/tests_member.py:330
+#: members/tests/tests_member.py:347
msgid ""
"You received this mail because you attempted to create an account on our "
"website or because a member created and activated your account"
@@ -357,7 +362,7 @@ msgstr ""
"web o porque un miembro ha creado y activado tu cuenta"
#: members/templates/members/email/email_verification_msg.html:28
-#: members/tests/tests_member.py:332
+#: members/tests/tests_member.py:349
msgid ""
"Please click on the link below to confirm the email and activate your "
"account."
@@ -845,15 +850,15 @@ msgstr "Detalles de un Miembro"
#: members/templates/members/members/member_detail.html:26
#: members/templates/members/members/member_upsert.html:26
-#: members/tests/tests_member.py:238
+#: members/tests/tests_member.py:236
msgid "Active member"
msgstr "Membro activo"
#: members/templates/members/members/member_detail.html:28
#: members/templates/members/members/member_upsert.html:28
-#: members/tests/tests_member.py:238
+#: members/tests/tests_member.py:236
msgid "Managed member"
-msgstr "Membrecito"
+msgstr "Miembro gestionado"
#: members/templates/members/members/member_detail.html:41
#: members/templates/members/members/members_directory.html:32
@@ -991,7 +996,7 @@ msgid "Show directory"
msgstr "Mostrar el directorio"
#: members/templates/members/members/members.html:19
-#: members/views/views_member.py:106
+#: members/views/views_member.py:122
msgid "Create Member"
msgstr "Criar un Miembro"
@@ -1146,16 +1151,16 @@ msgstr "Modificar los %(name)s seleccionados"
msgid "Add another %(translated_name)s"
msgstr "Añadir otra %(translated_name)s"
-#: members/tests/tests_member.py:100 members/views/views_member.py:194
+#: members/tests/tests_member.py:100 members/views/views_member.py:210
msgid "Member deleted"
msgstr "Membres eliminados"
-#: members/tests/tests_member.py:213 members/tests/tests_member.py:218
-#: members/views/views_member.py:143 members/views/views_member.py:157
+#: members/tests/tests_member.py:211 members/tests/tests_member.py:216
+#: members/views/views_member.py:159 members/views/views_member.py:173
msgid "You do not have permission to edit this member."
msgstr "No tienes permiso para editar este miembro."
-#: members/tests/tests_member.py:392
+#: members/tests/tests_member.py:409
msgid "Error: Cannot activate a dead member "
msgstr "Error: No se puede activar un miembro muerto "
@@ -1281,14 +1286,15 @@ msgstr ""
"Fehlende Spalte in der CSV-Datei: \"%(fieldname)s\". Obligatorische Felder "
"sind %(all_names)s"
-#: members/views/views_import_export.py:98
+#: members/views/views_import_export.py:104
#, python-format
msgid "Avatar not found: %(avatar)s for username %(username)s. Ignored..."
msgstr ""
"Avatar no encontrado : %(avatar)s para el nombre de usuario %(username)s. "
"Ignorado..."
-#: members/views/views_import_export.py:107
+#: members/views/views_import_export.py:113
+#, python-format
msgid ""
"Error saving avatar (%(warning)s): %(avatar)s for username %(username)s. "
"Ignored..."
@@ -1296,7 +1302,39 @@ msgstr ""
"Error enregistrando el avatar (%(warning)s) : %(avatar)s para el nombre de "
"usuario %(username)s. Ignorado..."
-#: members/views/views_import_export.py:220
+#: members/views/views_import_export.py:126
+msgid ""
+"You requested to activate imported members. All managers will be ignored."
+msgstr ""
+"Ha solicitado la activación de miembros importados. Se ignorarán todos "
+"los gestores."
+
+#: members/views/views_import_export.py:129
+#, python-format
+msgid "Manager %(manager)s not found for member %(member)s. Ignoring..."
+msgstr ""
+"No se ha encontrado el gestor %(manager)s para el miembro %(member)s. Ignorado..."
+
+#: members/views/views_import_export.py:136
+#, python-format
+msgid "Member %(member)s is active. Ignoring manager %(manager)s"
+msgstr "El miembro %(member)s está activo. Ignorar gestor %(manager)s "
+
+#: members/views/views_import_export.py:145
+#, python-format
+msgid ""
+"No manager provided for member %(member)s although inactive. Keeping existing "
+"one (%(manager)s)..."
+msgstr ""
+"No se ha proporcionado ningún gestor para los %(member)s aunque estén inactivos. Mantener uno "
+"existente (%(manager)s)..."
+
+#: members/views/views_import_export.py:148
+#, python-format
+msgid "Inactive member %(member)s has no manager. Please provide one!"
+msgstr "El miembro inactivo %(member)s no tiene administrador. Por favor, ¡proporcione uno!"
+
+#: members/views/views_import_export.py:258
#, python-format
msgid ""
"CSV file uploaded: %(nbLines)i lines read, %(nbMembers)i members created or "
@@ -1305,45 +1343,46 @@ msgstr ""
"CSV-Datei hochgeladen: %(nbLines)i Zeilen gelesen, %(nbMembers)i Mitglieder "
"erstellt oder aktualisiert"
-#: members/views/views_import_export.py:223
+#: members/views/views_import_export.py:261
+#, python-format
msgid "Warning: %(warning)s"
msgstr "Alerta: %(warning)s"
-#: members/views/views_import_export.py:290
+#: members/views/views_import_export.py:322
msgid "Method not allowed"
msgstr "Método no permitido"
-#: members/views/views_member.py:35
+#: members/views/views_member.py:34
msgid "You have been logged out"
msgstr "Has sido deslogerado(a)"
-#: members/views/views_member.py:96
+#: members/views/views_member.py:112
msgid "Only superusers can create members"
msgstr "Solo los superusuarios pueden crear miembros"
-#: members/views/views_member.py:121
+#: members/views/views_member.py:137
msgid "Member successfully created"
msgstr "El miembro fue creado con éxito"
-#: members/views/views_member.py:129
+#: members/views/views_member.py:145
msgid "Update Member Details"
msgstr "Modificar los detalles de un Miembro"
-#: members/views/views_member.py:130
+#: members/views/views_member.py:146
msgid "Member successfully updated"
msgstr "El miembro fue actualizado con éxito"
-#: members/views/views_member.py:167
+#: members/views/views_member.py:183
msgid "A verification email has been sent to validate your new email address."
msgstr ""
"Se ha enviado un correo electrónico de verificación para validar tu nueva "
"dirección de correo electrónico."
-#: members/views/views_member.py:181
+#: members/views/views_member.py:197
msgid "My Profile"
msgstr "Mi Perfil"
-#: members/views/views_member.py:182
+#: members/views/views_member.py:198
msgid "Profile successfully updated"
msgstr "Perfil actualizado con éxito"
diff --git a/members/locale/fr/LC_MESSAGES/django.mo b/members/locale/fr/LC_MESSAGES/django.mo
index 89a5f39e..e79ef15f 100644
Binary files a/members/locale/fr/LC_MESSAGES/django.mo and b/members/locale/fr/LC_MESSAGES/django.mo differ
diff --git a/members/locale/fr/LC_MESSAGES/django.po b/members/locale/fr/LC_MESSAGES/django.po
index ff526ab0..eccbec5d 100644
--- a/members/locale/fr/LC_MESSAGES/django.po
+++ b/members/locale/fr/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 0.1\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-11-11 22:23+0100\n"
+"POT-Creation-Date: 2024-11-17 11:12+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Olivier LEVILLAIN \n"
"Language-Team: French \n"
@@ -23,6 +23,7 @@ msgid "Members"
msgstr "Membres"
#: members/forms.py:69
+#, python-brace-format
msgid ""
"By checking this box, you agree to this site's privacy policy"
@@ -153,135 +154,140 @@ msgctxt "CSV Field"
msgid "deathdate"
msgstr "date_de_deces"
-#: members/models.py:45
+#: members/models.py:38
+msgctxt "CSV Field"
+msgid "managed_by"
+msgstr "gere_par"
+
+#: members/models.py:46
#: members/templates/members/members/members_directory.html:29
#: members/views/views_directory.py:59
msgid "Name"
msgstr "Nom"
-#: members/models.py:47 members/templates/members/family/family_detail.html:14
+#: members/models.py:48 members/templates/members/family/family_detail.html:14
msgid "Parent family"
msgstr "Famille parente"
-#: members/models.py:50
+#: members/models.py:51
msgid "family"
msgstr "famille"
-#: members/models.py:51
+#: members/models.py:52
msgid "families"
msgstr "familles"
-#: members/models.py:66 members/templates/members/address/address_detail.html:8
+#: members/models.py:67 members/templates/members/address/address_detail.html:8
msgid "Number & Street name"
msgstr "N° et nom de la voie"
-#: members/models.py:68
+#: members/models.py:69
msgid "Complementary info"
msgstr "Infos complémentaires"
-#: members/models.py:70
+#: members/models.py:71
#: members/templates/members/address/address_detail.html:16
msgid "Zip code"
msgstr "Code postal"
-#: members/models.py:72
+#: members/models.py:73
#: members/templates/members/address/address_detail.html:20
msgid "City"
msgstr "Ville"
-#: members/models.py:74
+#: members/models.py:75
#: members/templates/members/address/address_detail.html:24
msgid "Country"
msgstr "Pays"
-#: members/models.py:88
+#: members/models.py:89
msgid "address"
msgstr "adresse"
-#: members/models.py:89
+#: members/models.py:90
msgid "addresses"
msgstr "adresses"
-#: members/models.py:97
-msgid "Managing member"
-msgstr "Compte gérant ce membre"
+#: members/models.py:98
+msgid "Member manager"
+msgstr "Gestionnaire de ce membre"
-#: members/models.py:102
+#: members/models.py:103
#: members/templates/members/members/member_detail.html:37
#: members/templates/members/members/members_directory.html:33
#: members/views/views_directory.py:59
msgid "Address"
msgstr "Adresse"
-#: members/models.py:104
+#: members/models.py:105
#: members/templates/members/members/member_detail.html:45
#: members/templates/members/members/members_directory.html:31
#: members/views/views_directory.py:59
msgid "Phone"
msgstr "Téléphone"
-#: members/models.py:106
+#: members/models.py:107
#: members/templates/members/members/member_detail.html:49
#: members/templates/members/members/members_directory.html:30
msgid "Birthdate"
msgstr "Date de naissance"
-#: members/models.py:107
+#: members/models.py:108
msgid "Click on the month name or the year to change them quickly"
msgstr "Cliquez sur le nom du mois ou de l'année pour les modifier rapidement"
-#: members/models.py:110
+#: members/models.py:111
msgid "Is dead"
msgstr "Est décédé(e)"
-#: members/models.py:111
+#: members/models.py:112
msgid "Death date"
msgstr "Date de décès"
-#: members/models.py:113
+#: members/models.py:114
#: members/templates/members/members/member_detail.html:53
msgid "Website"
msgstr "Site web"
-#: members/models.py:115
+#: members/models.py:116
#: members/templates/members/members/member_detail.html:57
msgid "Family"
msgstr "Famille"
-#: members/models.py:117
+#: members/models.py:118
#: members/templates/members/members/member_detail.html:61
msgid "Who I am"
msgstr "Qui je suis"
-#: members/models.py:118
+#: members/models.py:119
msgid "Describe yourself, your likes and dislikes..."
msgstr "Décris-toi, dis ce que tu aimes ou n'aimes pas..."
-#: members/models.py:119
+#: members/models.py:120
msgid "My hobbies"
msgstr "Mes loisirs"
-#: members/models.py:120
+#: members/models.py:121
msgid "Provide a list of hobbies separated by commas"
msgstr "Donne un liste de loisirs séparés par des virgules"
-#: members/models.py:122
+#: members/models.py:123
msgid "Privacy consent"
msgstr "Consentement à la protection de la vie privée"
-#: members/models.py:124
+#: members/models.py:125
msgid "Followers"
msgstr "Followers"
-#: members/models.py:133
+#: members/models.py:134
msgid "member"
msgstr "membre"
-#: members/models.py:134
+#: members/models.py:135
msgid "members"
msgstr "membres"
-#: members/models.py:192
+#: members/models.py:195
#, python-brace-format
msgid "Death date {dd} is before birthdate {bd}"
msgstr "La date de décès {dd} est avant la date de naissance {bd}"
@@ -341,7 +347,7 @@ msgid "Greetings!"
msgstr "Bonjour!"
#: members/templates/members/email/email_verification_msg.html:27
-#: members/tests/tests_member.py:330
+#: members/tests/tests_member.py:347
msgid ""
"You received this mail because you attempted to create an account on our "
"website or because a member created and activated your account"
@@ -350,7 +356,7 @@ msgstr ""
"bien parce qu'un membre a créé et activé ton compte"
#: members/templates/members/email/email_verification_msg.html:28
-#: members/tests/tests_member.py:332
+#: members/tests/tests_member.py:349
msgid ""
"Please click on the link below to confirm the email and activate your "
"account."
@@ -840,13 +846,13 @@ msgstr "Détail d'un Membre"
#: members/templates/members/members/member_detail.html:26
#: members/templates/members/members/member_upsert.html:26
-#: members/tests/tests_member.py:238
+#: members/tests/tests_member.py:236
msgid "Active member"
msgstr "Membre actif"
#: members/templates/members/members/member_detail.html:28
#: members/templates/members/members/member_upsert.html:28
-#: members/tests/tests_member.py:238
+#: members/tests/tests_member.py:236
msgid "Managed member"
msgstr "Membre géré"
@@ -986,7 +992,7 @@ msgid "Show directory"
msgstr "Afficher l'annuaire"
#: members/templates/members/members/members.html:19
-#: members/views/views_member.py:106
+#: members/views/views_member.py:122
msgid "Create Member"
msgstr "Créer un Membre"
@@ -1141,16 +1147,16 @@ msgstr "Modifier les %(name)s sélectionnées"
msgid "Add another %(translated_name)s"
msgstr "Ajouter une autre %(translated_name)s"
-#: members/tests/tests_member.py:100 members/views/views_member.py:194
+#: members/tests/tests_member.py:100 members/views/views_member.py:210
msgid "Member deleted"
msgstr "Membre supprimé"
-#: members/tests/tests_member.py:213 members/tests/tests_member.py:218
-#: members/views/views_member.py:143 members/views/views_member.py:157
+#: members/tests/tests_member.py:211 members/tests/tests_member.py:216
+#: members/views/views_member.py:159 members/views/views_member.py:173
msgid "You do not have permission to edit this member."
msgstr "Tu n'as pas la permission de modifier ce membre."
-#: members/tests/tests_member.py:392
+#: members/tests/tests_member.py:409
msgid "Error: Cannot activate a dead member "
msgstr "Erreur: Impossible d'activer un membre mort "
@@ -1276,22 +1282,53 @@ msgstr ""
"Colonne manquante dans le fichier CSV: \"%(fieldname)s\". Les champs "
"obligatoires sont %(all_names)s"
-#: members/views/views_import_export.py:98
+#: members/views/views_import_export.py:104
#, python-format
msgid "Avatar not found: %(avatar)s for username %(username)s. Ignored..."
msgstr ""
"Avatar introuvable : %(avatar)s pour le nom d'utilisateur %(username)s. "
"Ignoré..."
-#: members/views/views_import_export.py:107
+#: members/views/views_import_export.py:113
+#, python-format
msgid ""
"Error saving avatar (%(warning)s): %(avatar)s for username %(username)s. "
"Ignored..."
msgstr ""
-"Erreur lors de l'enregistrement de l'avatar (%(warning)s) : %(avatar)s pour le "
-"nom d'utilisateur %(username)s. Ignoré..."
+"Erreur lors de l'enregistrement de l'avatar (%(warning)s) : %(avatar)s pour "
+"le nom d'utilisateur %(username)s. Ignoré..."
-#: members/views/views_import_export.py:220
+#: members/views/views_import_export.py:126
+msgid ""
+"You requested to activate imported members. All managers will be ignored."
+msgstr ""
+"Tu as demandé à activer les membres importés. Tous les gestionnaires de membres seront ignorés..."
+
+#: members/views/views_import_export.py:129
+#, python-format
+msgid "Manager %(manager)s not found for member %(member)s. Ignoring..."
+msgstr "Gestionnaire %(manager)s non trouvé pour le membre %(member)s. Ignoré..."
+
+#: members/views/views_import_export.py:136
+#, python-format
+msgid "Member %(member)s is active. Ignoring manager %(manager)s"
+msgstr "Le membre %(member)s est actif. Gestionnaire %(manager)s ignoré."
+
+#: members/views/views_import_export.py:145
+#, python-format
+msgid ""
+"No manager provided for member %(member)s although inactive. Keeping existing "
+"one (%(manager)s)..."
+msgstr ""
+"Pas de gestionnaire fourni pour le membre %(member)s bien qu'il soit inactif. "
+"Le gestionnaire existant (%(manager)s) est conservé."
+
+#: members/views/views_import_export.py:148
+#, python-format
+msgid "Inactive member %(member)s has no manager. Please provide one!"
+msgstr "Le membre inactif %(member)s n'a pas de gestionnaire. Merci d'en fournir un!"
+
+#: members/views/views_import_export.py:258
#, python-format
msgid ""
"CSV file uploaded: %(nbLines)i lines read, %(nbMembers)i members created or "
@@ -1300,45 +1337,46 @@ msgstr ""
"Fichier CSV téléchargé : %(nbLines)i lignes lues, %(nbMembers)i membres "
"créés ou mis à jour"
-#: members/views/views_import_export.py:223
+#: members/views/views_import_export.py:261
+#, python-format
msgid "Warning: %(warning)s"
msgstr "Attention: %(warning)s"
-#: members/views/views_import_export.py:290
+#: members/views/views_import_export.py:322
msgid "Method not allowed"
msgstr "Méthode non autorisée"
-#: members/views/views_member.py:35
+#: members/views/views_member.py:34
msgid "You have been logged out"
msgstr "Tu as été déconnecté(e)"
-#: members/views/views_member.py:96
+#: members/views/views_member.py:112
msgid "Only superusers can create members"
msgstr "Seuls les superutilisateurs peuvent créer des membres"
-#: members/views/views_member.py:121
+#: members/views/views_member.py:137
msgid "Member successfully created"
msgstr "Le membre a été créé avec succès"
-#: members/views/views_member.py:129
+#: members/views/views_member.py:145
msgid "Update Member Details"
msgstr "Modifier les détails d'un Membre"
-#: members/views/views_member.py:130
+#: members/views/views_member.py:146
msgid "Member successfully updated"
msgstr "Le membre a été mis à jour avec succès"
-#: members/views/views_member.py:167
+#: members/views/views_member.py:183
msgid "A verification email has been sent to validate your new email address."
msgstr ""
"Un courriel de vérification a été envoyé pour valider ta nouvelle adresse "
"électronique."
-#: members/views/views_member.py:181
+#: members/views/views_member.py:197
msgid "My Profile"
msgstr "Mon Profil"
-#: members/views/views_member.py:182
+#: members/views/views_member.py:198
msgid "Profile successfully updated"
msgstr "Profil mis à jour avec succès"
diff --git a/members/locale/it/LC_MESSAGES/django.mo b/members/locale/it/LC_MESSAGES/django.mo
index cdaf78bb..977c6865 100644
Binary files a/members/locale/it/LC_MESSAGES/django.mo and b/members/locale/it/LC_MESSAGES/django.mo differ
diff --git a/members/locale/it/LC_MESSAGES/django.po b/members/locale/it/LC_MESSAGES/django.po
index 0dca3c69..e27fe87e 100644
--- a/members/locale/it/LC_MESSAGES/django.po
+++ b/members/locale/it/LC_MESSAGES/django.po
@@ -12,7 +12,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 0.1\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-11-11 22:23+0100\n"
+"POT-Creation-Date: 2024-11-17 12:15+0100\n"
"PO-Revision-Date: 2024-10-21 10:17+00:00\n"
"Last-Translator: Auto-po-lyglot using qwen2.5:3b (https://github.com/"
"leolivier/auto-po-lyglot)\n"
@@ -29,6 +29,7 @@ msgid "Members"
msgstr "Membri"
#: members/forms.py:69
+#, python-brace-format
msgid ""
"By checking this box, you agree to this site's privacy policy"
@@ -160,137 +161,142 @@ msgctxt "CSV Field"
msgid "deathdate"
msgstr "data_di_morte"
-#: members/models.py:45
+#: members/models.py:38
+msgctxt "CSV Field"
+msgid "managed_by"
+msgstr "gestito_da"
+
+#: members/models.py:46
#: members/templates/members/members/members_directory.html:29
#: members/views/views_directory.py:59
msgid "Name"
msgstr "Nome"
-#: members/models.py:47 members/templates/members/family/family_detail.html:14
+#: members/models.py:48 members/templates/members/family/family_detail.html:14
msgid "Parent family"
msgstr "Famiglia d'origine"
-#: members/models.py:50
+#: members/models.py:51
msgid "family"
msgstr "famiglia"
-#: members/models.py:51
+#: members/models.py:52
msgid "families"
msgstr "famiglie"
-#: members/models.py:66 members/templates/members/address/address_detail.html:8
+#: members/models.py:67 members/templates/members/address/address_detail.html:8
msgid "Number & Street name"
msgstr "Numero civico e nome della via"
-#: members/models.py:68
+#: members/models.py:69
msgid "Complementary info"
msgstr "Informazioni complementari"
-#: members/models.py:70
+#: members/models.py:71
#: members/templates/members/address/address_detail.html:16
msgid "Zip code"
msgstr "Codice postale"
-#: members/models.py:72
+#: members/models.py:73
#: members/templates/members/address/address_detail.html:20
msgid "City"
msgstr "Città"
-#: members/models.py:74
+#: members/models.py:75
#: members/templates/members/address/address_detail.html:24
msgid "Country"
msgstr "Paese"
-#: members/models.py:88
+#: members/models.py:89
msgid "address"
msgstr "indirizzo"
-#: members/models.py:89
+#: members/models.py:90
msgid "addresses"
msgstr "indirizzi"
-#: members/models.py:97
-msgid "Managing member"
-msgstr "Membro gestore"
+#: members/models.py:98
+msgid "Member manager"
+msgstr "Membro manager"
-#: members/models.py:102
+#: members/models.py:103
#: members/templates/members/members/member_detail.html:37
#: members/templates/members/members/members_directory.html:33
#: members/views/views_directory.py:59
msgid "Address"
msgstr "Indirizzo"
-#: members/models.py:104
+#: members/models.py:105
#: members/templates/members/members/member_detail.html:45
#: members/templates/members/members/members_directory.html:31
#: members/views/views_directory.py:59
msgid "Phone"
msgstr "Telefono"
-#: members/models.py:106
+#: members/models.py:107
#: members/templates/members/members/member_detail.html:49
#: members/templates/members/members/members_directory.html:30
msgid "Birthdate"
msgstr "Data di nascita"
-#: members/models.py:107
+#: members/models.py:108
msgid "Click on the month name or the year to change them quickly"
msgstr "Clicca sul nome del mese o dell'anno per modificarli rapidamente"
-#: members/models.py:110
+#: members/models.py:111
msgid "Is dead"
msgstr "E' morto"
-#: members/models.py:111
+#: members/models.py:112
#, fuzzy
#| msgid "Birthdate"
msgid "Death date"
msgstr "Data di morte"
-#: members/models.py:113
+#: members/models.py:114
#: members/templates/members/members/member_detail.html:53
msgid "Website"
msgstr "Sito web"
-#: members/models.py:115
+#: members/models.py:116
#: members/templates/members/members/member_detail.html:57
msgid "Family"
msgstr "Famiglia"
-#: members/models.py:117
+#: members/models.py:118
#: members/templates/members/members/member_detail.html:61
msgid "Who I am"
msgstr "Chi sono"
-#: members/models.py:118
+#: members/models.py:119
msgid "Describe yourself, your likes and dislikes..."
msgstr "Descriviti, parla di ciò che ti piace e non ti piace..."
-#: members/models.py:119
+#: members/models.py:120
msgid "My hobbies"
msgstr "I miei passatempi"
-#: members/models.py:120
+#: members/models.py:121
msgid "Provide a list of hobbies separated by commas"
msgstr "Fornisci un elenco di hobby separati da virgole"
-#: members/models.py:122
+#: members/models.py:123
msgid "Privacy consent"
msgstr "Consenso sulla privacy"
-#: members/models.py:124
+#: members/models.py:125
msgid "Followers"
msgstr "Followers"
-#: members/models.py:133
+#: members/models.py:134
msgid "member"
msgstr "membro"
-#: members/models.py:134
+#: members/models.py:135
msgid "members"
msgstr "membri"
-#: members/models.py:192
+#: members/models.py:195
#, python-brace-format
msgid "Death date {dd} is before birthdate {bd}"
msgstr "Data di morte {dd} precede la data di nascita {bd}"
@@ -350,7 +356,7 @@ msgid "Greetings!"
msgstr "Saluti!"
#: members/templates/members/email/email_verification_msg.html:27
-#: members/tests/tests_member.py:330
+#: members/tests/tests_member.py:347
msgid ""
"You received this mail because you attempted to create an account on our "
"website or because a member created and activated your account"
@@ -359,7 +365,7 @@ msgstr ""
"sito web o perche' un membro ha creato e attivato il tuo account"
#: members/templates/members/email/email_verification_msg.html:28
-#: members/tests/tests_member.py:332
+#: members/tests/tests_member.py:349
msgid ""
"Please click on the link below to confirm the email and activate your "
"account."
@@ -844,13 +850,13 @@ msgstr "Dettaglio del Membro"
#: members/templates/members/members/member_detail.html:26
#: members/templates/members/members/member_upsert.html:26
-#: members/tests/tests_member.py:238
+#: members/tests/tests_member.py:236
msgid "Active member"
msgstr "Membro attivo"
#: members/templates/members/members/member_detail.html:28
#: members/templates/members/members/member_upsert.html:28
-#: members/tests/tests_member.py:238
+#: members/tests/tests_member.py:236
msgid "Managed member"
msgstr "Membro gestito"
@@ -989,7 +995,7 @@ msgid "Show directory"
msgstr "Mostra l'elenco"
#: members/templates/members/members/members.html:19
-#: members/views/views_member.py:106
+#: members/views/views_member.py:122
msgid "Create Member"
msgstr "Crea Membro"
@@ -1146,16 +1152,16 @@ msgstr "Modifica %(name)s selezionati"
msgid "Add another %(translated_name)s"
msgstr "Aggiungi un'altra %(translated_name)s"
-#: members/tests/tests_member.py:100 members/views/views_member.py:194
+#: members/tests/tests_member.py:100 members/views/views_member.py:210
msgid "Member deleted"
msgstr "Membro eliminato"
-#: members/tests/tests_member.py:213 members/tests/tests_member.py:218
-#: members/views/views_member.py:143 members/views/views_member.py:157
+#: members/tests/tests_member.py:211 members/tests/tests_member.py:216
+#: members/views/views_member.py:159 members/views/views_member.py:173
msgid "You do not have permission to edit this member."
msgstr "Non hai il permesso di modificare questo membro."
-#: members/tests/tests_member.py:392
+#: members/tests/tests_member.py:409
msgid "Error: Cannot activate a dead member "
msgstr "Errore: Impossibile attivare un membro morto "
@@ -1281,13 +1287,13 @@ msgstr ""
"Colonna mancante nel file CSV: \"%(fieldname)s\". I campi obbligatori sono "
"%(all_names)s"
-#: members/views/views_import_export.py:98
+#: members/views/views_import_export.py:104
#, python-format
msgid "Avatar not found: %(avatar)s for username %(username)s. Ignored..."
msgstr ""
"Avatar non trovato: %(avatar)s per il nome utente %(username)s. Ignorato..."
-#: members/views/views_import_export.py:107
+#: members/views/views_import_export.py:113
#, python-format
msgid ""
"Error saving avatar (%(warning)s): %(avatar)s for username %(username)s. "
@@ -1296,7 +1302,37 @@ msgstr ""
"Errore nel salvare l'avatar (%(warning)s): %(avatar)s per il nome utente "
"%(username)s. Ignorato..."
-#: members/views/views_import_export.py:220
+#: members/views/views_import_export.py:126
+msgid ""
+"You requested to activate imported members. All managers will be ignored."
+msgstr ""
+"È stato richiesto di attivare i membri importati. Tutti i gestori verranno ignorati."
+
+#: members/views/views_import_export.py:129
+#, python-format
+msgid "Manager %(manager)s not found for member %(member)s. Ignoring..."
+msgstr "Il manager %(manager)s non è stato trovato per il membro %(member)s. Ignorare..."
+
+#: members/views/views_import_export.py:136
+#, python-format
+msgid "Member %(member)s is active. Ignoring manager %(manager)s"
+msgstr "Il membro %(membro)s è attivo. Ignorare il manager %(manager)s."
+
+#: members/views/views_import_export.py:145
+#, python-format
+msgid ""
+"No manager provided for member %(member)s although inactive. Keeping "
+"existing one (%(manager)s)..."
+msgstr ""
+"Non è previsto nessun gestore per i membri %(member)s, anche se inattivi. "
+"Mantenere quello esistente (%(manager)s)..."
+
+#: members/views/views_import_export.py:148
+#, python-format
+msgid "Inactive member %(member)s has no manager. Please provide one!"
+msgstr "Il membro inattivo %(member)s non ha un manager. Si prega di fornirne uno!"
+
+#: members/views/views_import_export.py:258
#, python-format
msgid ""
"CSV file uploaded: %(nbLines)i lines read, %(nbMembers)i members created or "
@@ -1305,46 +1341,46 @@ msgstr ""
"File CSV caricato: %(nbLines)i righe lette, %(nbMembers)i membri creati o "
"aggiornati"
-#: members/views/views_import_export.py:223
+#: members/views/views_import_export.py:261
#, python-format
msgid "Warning: %(warning)s"
msgstr "Attenzione: %(warning)s"
-#: members/views/views_import_export.py:290
+#: members/views/views_import_export.py:322
msgid "Method not allowed"
msgstr "Metodo non consentito"
-#: members/views/views_member.py:35
+#: members/views/views_member.py:34
msgid "You have been logged out"
msgstr "Sei stato disconnesso"
-#: members/views/views_member.py:96
+#: members/views/views_member.py:112
msgid "Only superusers can create members"
msgstr "Solo i superutenti possono creare membri"
-#: members/views/views_member.py:121
+#: members/views/views_member.py:137
msgid "Member successfully created"
msgstr "Il membro è stato creato con successo"
-#: members/views/views_member.py:129
+#: members/views/views_member.py:145
msgid "Update Member Details"
msgstr "Aggiorna i dettagli del membro"
-#: members/views/views_member.py:130
+#: members/views/views_member.py:146
msgid "Member successfully updated"
msgstr "Il membro è stato aggiornato con successo"
-#: members/views/views_member.py:167
+#: members/views/views_member.py:183
msgid "A verification email has been sent to validate your new email address."
msgstr ""
"Un'email di verifica è stata inviata per convalidare il tuo nuovo indirizzo "
"email."
-#: members/views/views_member.py:181
+#: members/views/views_member.py:197
msgid "My Profile"
msgstr "Il mio profilo"
-#: members/views/views_member.py:182
+#: members/views/views_member.py:198
msgid "Profile successfully updated"
msgstr "Profilo aggiornato con successo"
diff --git a/members/locale/pt/LC_MESSAGES/django.mo b/members/locale/pt/LC_MESSAGES/django.mo
index a629f7c5..ce98ec21 100644
Binary files a/members/locale/pt/LC_MESSAGES/django.mo and b/members/locale/pt/LC_MESSAGES/django.mo differ
diff --git a/members/locale/pt/LC_MESSAGES/django.po b/members/locale/pt/LC_MESSAGES/django.po
index a511f9ea..cc5a7770 100644
--- a/members/locale/pt/LC_MESSAGES/django.po
+++ b/members/locale/pt/LC_MESSAGES/django.po
@@ -12,7 +12,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 0.1\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2024-11-11 22:23+0100\n"
+"POT-Creation-Date: 2024-11-17 12:15+0100\n"
"PO-Revision-Date: 2024-10-21 10:37+00:00\n"
"Last-Translator: Auto-po-lyglot using qwen2.5:3b (https://github.com/"
"leolivier/auto-po-lyglot)\n"
@@ -160,137 +160,142 @@ msgctxt "CSV Field"
msgid "deathdate"
msgstr "data_de_morte"
-#: members/models.py:45
+#: members/models.py:38
+msgctxt "CSV Field"
+msgid "managed_by"
+msgstr "gerido_por"
+
+#: members/models.py:46
#: members/templates/members/members/members_directory.html:29
#: members/views/views_directory.py:59
msgid "Name"
msgstr "Nome"
-#: members/models.py:47 members/templates/members/family/family_detail.html:14
+#: members/models.py:48 members/templates/members/family/family_detail.html:14
msgid "Parent family"
msgstr "Familia parental"
-#: members/models.py:50
+#: members/models.py:51
msgid "family"
msgstr "Família"
-#: members/models.py:51
+#: members/models.py:52
msgid "families"
msgstr "Famílias"
-#: members/models.py:66 members/templates/members/address/address_detail.html:8
+#: members/models.py:67 members/templates/members/address/address_detail.html:8
msgid "Number & Street name"
msgstr "Número e nome da rua"
-#: members/models.py:68
+#: members/models.py:69
msgid "Complementary info"
msgstr "Informações complementares"
-#: members/models.py:70
+#: members/models.py:71
#: members/templates/members/address/address_detail.html:16
msgid "Zip code"
msgstr "Numéro de código postal"
-#: members/models.py:72
+#: members/models.py:73
#: members/templates/members/address/address_detail.html:20
msgid "City"
msgstr "Cidade"
-#: members/models.py:74
+#: members/models.py:75
#: members/templates/members/address/address_detail.html:24
msgid "Country"
msgstr "País"
-#: members/models.py:88
+#: members/models.py:89
msgid "address"
msgstr "Endereço"
-#: members/models.py:89
+#: members/models.py:90
msgid "addresses"
msgstr "endereços"
-#: members/models.py:97
-msgid "Managing member"
-msgstr "Membro gerente"
+#: members/models.py:98
+msgid "Member manager"
+msgstr "Gestor de membros"
-#: members/models.py:102
+#: members/models.py:103
#: members/templates/members/members/member_detail.html:37
#: members/templates/members/members/members_directory.html:33
#: members/views/views_directory.py:59
msgid "Address"
msgstr "Endereço"
-#: members/models.py:104
+#: members/models.py:105
#: members/templates/members/members/member_detail.html:45
#: members/templates/members/members/members_directory.html:31
#: members/views/views_directory.py:59
msgid "Phone"
msgstr "Telefone"
-#: members/models.py:106
+#: members/models.py:107
#: members/templates/members/members/member_detail.html:49
#: members/templates/members/members/members_directory.html:30
msgid "Birthdate"
msgstr "Data do nascimento"
-#: members/models.py:107
+#: members/models.py:108
msgid "Click on the month name or the year to change them quickly"
msgstr "Clique em nome do mês ou do ano para mudá-los rapidamente"
-#: members/models.py:110
+#: members/models.py:111
msgid "Is dead"
msgstr "Está morto"
-#: members/models.py:111
+#: members/models.py:112
#, fuzzy
#| msgid "Birthdate"
msgid "Death date"
msgstr "Data de morte"
-#: members/models.py:113
+#: members/models.py:114
#: members/templates/members/members/member_detail.html:53
msgid "Website"
msgstr "Site"
-#: members/models.py:115
+#: members/models.py:116
#: members/templates/members/members/member_detail.html:57
msgid "Family"
msgstr "Família"
-#: members/models.py:117
+#: members/models.py:118
#: members/templates/members/members/member_detail.html:61
msgid "Who I am"
msgstr "Quem sou"
-#: members/models.py:118
+#: members/models.py:119
msgid "Describe yourself, your likes and dislikes..."
msgstr "Descreva-se, seus gostos e desgostos..."
-#: members/models.py:119
+#: members/models.py:120
msgid "My hobbies"
msgstr "Meus hobbies"
-#: members/models.py:120
+#: members/models.py:121
msgid "Provide a list of hobbies separated by commas"
msgstr "Liste os hobbies separados por vírgulas."
-#: members/models.py:122
+#: members/models.py:123
msgid "Privacy consent"
msgstr "Consentimento à proteção da vida privada"
-#: members/models.py:124
+#: members/models.py:125
msgid "Followers"
msgstr "Seguidores"
-#: members/models.py:133
+#: members/models.py:134
msgid "member"
msgstr "membro"
-#: members/models.py:134
+#: members/models.py:135
msgid "members"
msgstr "membros"
-#: members/models.py:192
+#: members/models.py:195
#, python-brace-format
msgid "Death date {dd} is before birthdate {bd}"
msgstr "Data de morte {dd} é antes da data de nascimento {bd}"
@@ -350,7 +355,7 @@ msgid "Greetings!"
msgstr "Ossé!"
#: members/templates/members/email/email_verification_msg.html:27
-#: members/tests/tests_member.py:330
+#: members/tests/tests_member.py:347
msgid ""
"You received this mail because you attempted to create an account on our "
"website or because a member created and activated your account"
@@ -359,7 +364,7 @@ msgstr ""
"porque um membro criou e ativou sua conta."
#: members/templates/members/email/email_verification_msg.html:28
-#: members/tests/tests_member.py:332
+#: members/tests/tests_member.py:349
msgid ""
"Please click on the link below to confirm the email and activate your "
"account."
@@ -845,13 +850,13 @@ msgstr "Detalhe do Membro"
#: members/templates/members/members/member_detail.html:26
#: members/templates/members/members/member_upsert.html:26
-#: members/tests/tests_member.py:238
+#: members/tests/tests_member.py:236
msgid "Active member"
msgstr "Membro ativo"
#: members/templates/members/members/member_detail.html:28
#: members/templates/members/members/member_upsert.html:28
-#: members/tests/tests_member.py:238
+#: members/tests/tests_member.py:236
msgid "Managed member"
msgstr "Membro gerido"
@@ -991,7 +996,7 @@ msgid "Show directory"
msgstr "Mostrar diretório"
#: members/templates/members/members/members.html:19
-#: members/views/views_member.py:106
+#: members/views/views_member.py:122
msgid "Create Member"
msgstr "Criar Membro"
@@ -1143,16 +1148,16 @@ msgstr "Alterar os %(name)s selecionados"
msgid "Add another %(translated_name)s"
msgstr "Adicionar mais %(translated_name)s"
-#: members/tests/tests_member.py:100 members/views/views_member.py:194
+#: members/tests/tests_member.py:100 members/views/views_member.py:210
msgid "Member deleted"
msgstr "Membro excluído"
-#: members/tests/tests_member.py:213 members/tests/tests_member.py:218
-#: members/views/views_member.py:143 members/views/views_member.py:157
+#: members/tests/tests_member.py:211 members/tests/tests_member.py:216
+#: members/views/views_member.py:159 members/views/views_member.py:173
msgid "You do not have permission to edit this member."
msgstr "Você não tem permissão para editar esse membro."
-#: members/tests/tests_member.py:392
+#: members/tests/tests_member.py:409
msgid "Error: Cannot activate a dead member "
msgstr "Erro: não pode ativar um membro morto "
@@ -1277,14 +1282,14 @@ msgstr ""
"Coluna faltante no arquivo CSV: '%(fieldname)s'. Campos obrigatórios são "
"%(all_names)s"
-#: members/views/views_import_export.py:98
+#: members/views/views_import_export.py:104
#, python-format
msgid "Avatar not found: %(avatar)s for username %(username)s. Ignored..."
msgstr ""
"Avatar não encontrado: %(avatar)s para o nome de usuário %(username)s. "
"Ignorado..."
-#: members/views/views_import_export.py:107
+#: members/views/views_import_export.py:113
#, python-format
msgid ""
"Error saving avatar (%(warning)s): %(avatar)s for username %(username)s. "
@@ -1293,7 +1298,37 @@ msgstr ""
"Erro ao salvar avatar (%(warning)s): %(avatar)s para o usuário %(username)s. "
"Ignorado..."
-#: members/views/views_import_export.py:220
+#: members/views/views_import_export.py:126
+msgid ""
+"You requested to activate imported members. All managers will be ignored."
+msgstr ""
+"Pediu para ativar membros importados. Todos os gestores serão ignorados."
+
+#: members/views/views_import_export.py:129
+#, python-format
+msgid "Manager %(manager)s not found for member %(member)s. Ignoring..."
+msgstr "Gestor %(manager)s não encontrado para membro %(member)s. Ignorando..."
+
+#: members/views/views_import_export.py:136
+#, python-format
+msgid "Member %(member)s is active. Ignoring manager %(manager)s"
+msgstr "O membro %(member)s está ativo. Ignorar o gestor %(manager)s."
+
+#: members/views/views_import_export.py:145
+#, python-format
+msgid ""
+"No manager provided for member %(member)s although inactive. Keeping "
+"existing one (%(manager)s)..."
+msgstr ""
+"Não é atribuído nenhum gestor aos %(member)s membros, embora estejam inativos. "
+"Manter o atual (%(manager)s)."
+
+#: members/views/views_import_export.py:148
+#, python-format
+msgid "Inactive member %(member)s has no manager. Please provide one!"
+msgstr "O membro inativo %(member)s não tem gestor. Por favor, indique um!"
+
+#: members/views/views_import_export.py:258
#, python-format
msgid ""
"CSV file uploaded: %(nbLines)i lines read, %(nbMembers)i members created or "
@@ -1302,46 +1337,46 @@ msgstr ""
"Arquivo CSV carregado: %(nbLines)i linhas lidas, %(nbMembers)i membros "
"criados ou atualizados"
-#: members/views/views_import_export.py:223
+#: members/views/views_import_export.py:261
#, python-format
msgid "Warning: %(warning)s"
msgstr "Alerta: %(warning)s"
-#: members/views/views_import_export.py:290
+#: members/views/views_import_export.py:322
msgid "Method not allowed"
msgstr "Método não permitido"
-#: members/views/views_member.py:35
+#: members/views/views_member.py:34
msgid "You have been logged out"
msgstr "Você foi desconectado(a)."
-#: members/views/views_member.py:96
+#: members/views/views_member.py:112
msgid "Only superusers can create members"
msgstr "Somente os superusuários podem criar membros."
-#: members/views/views_member.py:121
+#: members/views/views_member.py:137
msgid "Member successfully created"
msgstr "O membro foi criado com sucesso."
-#: members/views/views_member.py:129
+#: members/views/views_member.py:145
msgid "Update Member Details"
msgstr "Atualizar Detalhes de Membro"
-#: members/views/views_member.py:130
+#: members/views/views_member.py:146
msgid "Member successfully updated"
msgstr "O membro foi atualizado com sucesso."
-#: members/views/views_member.py:167
+#: members/views/views_member.py:183
msgid "A verification email has been sent to validate your new email address."
msgstr ""
"Um e-mail de verificação foi enviado para validar sua nova endereço "
"eletrônico."
-#: members/views/views_member.py:181
+#: members/views/views_member.py:197
msgid "My Profile"
msgstr "Minha Conta de Usuário"
-#: members/views/views_member.py:182
+#: members/views/views_member.py:198
msgid "Profile successfully updated"
msgstr "Perfil atualizado com sucesso"
diff --git a/members/migrations/0008_alter_member_managing_member.py b/members/migrations/0008_alter_member_managing_member.py
new file mode 100644
index 00000000..9e80c50d
--- /dev/null
+++ b/members/migrations/0008_alter_member_managing_member.py
@@ -0,0 +1,25 @@
+# Generated by Django 5.1.1 on 2024-11-17 11:45
+
+import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('members', '0007_member_deathdate_member_is_dead'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='member',
+ name='managing_member',
+ field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='managed_members', to=settings.AUTH_USER_MODEL, verbose_name='Member manager'),
+ ),
+ migrations.RenameField(
+ model_name='member',
+ old_name='managing_member',
+ new_name='member_manager',
+ ),
+ ]
diff --git a/members/models.py b/members/models.py
index 77ab66a0..b32fdd7d 100644
--- a/members/models.py
+++ b/members/models.py
@@ -35,6 +35,7 @@
'family': pgettext_lazy('CSV Field', 'family'),
'avatar': pgettext_lazy('CSV Field', 'avatar'),
'deathdate': pgettext_lazy('CSV Field', 'deathdate'),
+ 'managed_by': pgettext_lazy('CSV Field', 'managed_by')
}
@@ -94,8 +95,8 @@ class Meta:
class Member(AbstractUser):
- managing_member = models.ForeignKey('self', verbose_name=_('Managing member'), on_delete=models.CASCADE,
- related_name='managed_members', null=True, blank=True, default=None)
+ member_manager = models.ForeignKey('self', verbose_name=_('Member manager'), on_delete=models.CASCADE,
+ related_name='managed_members', null=True, blank=True, default=None)
avatar = models.ImageField(upload_to=settings.AVATARS_DIR, blank=True, null=True)
@@ -184,7 +185,7 @@ def age(self) -> int:
return years if (today >= self.next_birthday) else years-1
def get_manager(self):
- return self.managing_member if self.managing_member else self
+ return self.member_manager if self.member_manager else self
def clean(self):
if self.deathdate:
@@ -197,14 +198,14 @@ def clean(self):
self.is_active = False
else:
self.is_dead = False
- # If member is active, set managing member to None
- if self.is_active and self.managing_member is not None:
- logger.debug(f"Cleaning member {self.full_name}: removing managing member")
- self.managing_member = None
- elif not self.is_active and self.managing_member is None:
- # If no managing member and member is inactive, use admin member
- logger.debug(f"Cleaning member {self.full_name}: changing managing member to admin")
- self.managing_member = Member.objects.filter(is_superuser=True).first()
+ # If member is active, set member manager to None
+ if self.is_active and self.member_manager is not None:
+ logger.debug(f"Cleaning member {self.full_name}: removing member manager")
+ self.member_manager = None
+ elif not self.is_active and self.member_manager is None:
+ # If no member manager and member is inactive, use admin member
+ logger.debug(f"Cleaning member {self.full_name}: changing member manager to admin")
+ self.member_manager = Member.objects.filter(is_superuser=True).first()
def _resize_avatar(self, max_size, save_path):
img = Image.open(self.avatar.path)
diff --git a/members/tests/resources/import_members-fr.csv b/members/tests/resources/import_members-fr.csv
index a0e81c02..6ab6b101 100644
--- a/members/tests/resources/import_members-fr.csv
+++ b/members/tests/resources/import_members-fr.csv
@@ -1,5 +1,5 @@
-"nom_de_compte","email","prenom","nom","date_de_naissance","telephone","ville","famille"
-"member-fr1","member1@test.com","John","Doe",2000-01-01,"+45 01 02 03","Manchester","Les Does"
-"member-fr2","member2@test.com","Jane","Doe",2000-01-02,"+45 01 02 04","Liverpool","Les Does"
-"member-fr3","member3@test.com","John","Smith",2000-01-03,"+45 01 02 05","London","Les Smiths"
-"member-fr4","member4@test.com","Jane","Smith",2000-01-04,"+45 01 02 06","Blackpool","Les Smiths"
+"nom_de_compte","email","prenom","nom","date_de_naissance","telephone","ville","famille","gere_par"
+"member-fr1","member1@test.com","John","Doe",2000-01-01,"+45 01 02 03","Manchester","Les Does",""
+"member-fr2","member2@test.com","Jane","Doe",2000-01-02,"+45 01 02 04","Liverpool","Les Does",""
+"member-fr3","member3@test.com","John","Smith",2000-01-03,"+45 01 02 05","London","Les Smiths","member-fr1"
+"member-fr4","member4@test.com","Jane","Smith",2000-01-04,"+45 01 02 06","Blackpool","Les Smiths",""
diff --git a/members/tests/tests_import_export.py b/members/tests/tests_import_export.py
index f20daf73..d13237ed 100644
--- a/members/tests/tests_import_export.py
+++ b/members/tests/tests_import_export.py
@@ -36,7 +36,7 @@ def do_upload_file(self, file, lang, activate_users=True):
def do_test_import(self, file, lang, expected_num, activate_users=True, member_prefix='member'):
prev_num = Member.objects.count()
- response = self.do_upload_file(file, lang)
+ response = self.do_upload_file(file, lang, activate_users)
self.assertEqual(response.status_code, 200)
# Check that expected_num members were imported
self.assertEqual(Member.objects.count(), prev_num+expected_num)
@@ -52,9 +52,9 @@ def do_test_import(self, file, lang, expected_num, activate_users=True, member_p
m = Member.objects.get(username=name)
if activate_users:
self.assertEqual(m.is_active, activate_users)
- self.assertIsNone(m.managing_member)
- else: # managing_member is None if and only if active
- self.assertEqual(m.managing_member is None, m.is_active)
+ self.assertIsNone(m.member_manager)
+ else: # member_manager is None if and only if active
+ self.assertEqual(m.member_manager is None, m.is_active)
class TestMemberImport(TestImportMixin, MemberTestCase):
@@ -92,6 +92,23 @@ def test_import_en(self):
def test_import_fr(self):
self.do_test_import('import_members-fr.csv', 'fr', 4, member_prefix='member-fr')
+ def test_import_manager(self):
+ # first make sure the member for which we change the managing member either does not exist or is not active
+ m3 = Member.objects.filter(username="member-fr3").first()
+ if m3 is not None:
+ # print("setting {m3.full_name} to inactive")
+ m3.is_active = False
+ m3.member_manager = None
+ m3.save()
+ # else:
+ # print("Member {m3.full_name} does not exist")
+
+ self.do_test_import('import_members-fr.csv', 'fr', 4, member_prefix='member-fr', activate_users=False)
+ # check member manager import
+ m1 = Member.objects.get(username="member-fr1")
+ m3 = Member.objects.get(username="member-fr3")
+ self.assertEqual(m3.member_manager, m1)
+
def test_wrong_field(self):
response = self.do_upload_file('import_members-wrong-field.csv', 'en-us')
self.assertEqual(response.status_code, 200)
diff --git a/members/tests/tests_member.py b/members/tests/tests_member.py
index 36956079..32709961 100644
--- a/members/tests/tests_member.py
+++ b/members/tests/tests_member.py
@@ -65,7 +65,7 @@ def create_member_by_view(self, member_data=None):
new_member = Member.objects.filter(username=member_data['username']).first()
self.assertIsNotNone(new_member)
self.assertFalse(new_member.is_active)
- self.assertEqual(new_member.managing_member, self.member)
+ self.assertEqual(new_member.member_manager, self.member)
self.created_members.append(new_member)
return new_member
@@ -83,7 +83,7 @@ def test_create_managed_member_in_view(self):
# a new member has been created
self.assertEqual(new_number, prev_number+1)
# managed is managed by the creating member
- self.assertEqual(managed.managing_member, self.member)
+ self.assertEqual(managed.member_manager, self.member)
class MemberDeleteTest(MemberViewTestMixin, MemberTestCase):
@@ -130,7 +130,7 @@ def test_member_profile_view(self):
maxlength="150" class="input" id="id_last_name" required>''', html=True)
self.assertTrue(self.member.is_active)
- self.assertIsNone(self.member.managing_member)
+ self.assertIsNone(self.member.member_manager)
new_data = self.get_changed_member_data(self.member)
response = self.client.post(profile_url, new_data, follow=True)
# print(vars(response))
@@ -139,7 +139,7 @@ def test_member_profile_view(self):
self.member.refresh_from_db()
# self.is_active becomes false because of the sending of the verification email (which changed)
# self.assertTrue(self.member.is_active)
- # self.assertIsNone(self.member.managing_member)
+ # self.assertIsNone(self.member.member_manager)
self.assertEqual(self.member.first_name, new_data['first_name'])
self.assertEqual(self.member.phone, new_data['phone'])
@@ -332,7 +332,7 @@ def test_activate(self):
managed = self.create_member()
self.assertIsNotNone(managed)
self.assertFalse(managed.is_active)
- self.assertEqual(managed.managing_member, self.member)
+ self.assertEqual(managed.member_manager, self.member)
mail.outbox = []
response = self.client.post(reverse("members:activate", args=[managed.id]), follow=True)
self.assertEqual(response.status_code, 200)
@@ -373,8 +373,8 @@ def get_dead_member_data(self):
def check_dead_member(self, member):
self.assertFalse(member.is_active)
self.assertTrue(member.is_dead)
- # managing member can be self.member (ie member creator) or superuser
- self.assertIn(member.managing_member.id, [self.member.id, self.superuser.id])
+ # member manager can be self.member (ie member creator) or superuser
+ self.assertIn(member.member_manager.id, [self.member.id, self.superuser.id])
self.assertIsNotNone(member.deathdate)
self.assertGreater(member.deathdate, member.birthdate)
diff --git a/members/tests/tests_member_base.py b/members/tests/tests_member_base.py
index d69f73f0..226b8b5b 100644
--- a/members/tests/tests_member_base.py
+++ b/members/tests/tests_member_base.py
@@ -150,7 +150,7 @@ def create_member(self, member_data=None, is_active=False):
else:
passwd = member_data['password']
new_member = Member.objects.create_member(**member_data, is_active=is_active,
- managing_member=None if is_active else self.member)
+ member_manager=None if is_active else self.member)
# store real password instead of hashed one so that we can login with it afterward
new_member.password = passwd
self.created_members.append(new_member)
diff --git a/members/views/views_activate.py b/members/views/views_activate.py
index 078c122f..7c79fce7 100644
--- a/members/views/views_activate.py
+++ b/members/views/views_activate.py
@@ -20,8 +20,8 @@ def activate_member(request, pk):
if member.is_dead:
messages.error(request, _("Error: Cannot activate a dead member"))
elif member.is_active:
- if member.managing_member is not None:
- member.managing_member = None
+ if member.member_manager is not None:
+ member.member_manager = None
member.save()
messages.error(request, _("Error: Member already active"))
elif not member.email:
diff --git a/members/views/views_import_export.py b/members/views/views_import_export.py
index a44e012c..3f524116 100644
--- a/members/views/views_import_export.py
+++ b/members/views/views_import_export.py
@@ -53,6 +53,15 @@ class CSVImportView(LoginRequiredMixin, generic.FormView):
template_name = "members/members/import_members.html"
form_class = CSVImportMembersForm
success_url = reverse_lazy("members:members")
+ warned_on_activate_users = False
+ warnings = []
+ errors = []
+ row = None
+ created_num = 0
+ updated_num = 0
+ rows_num = 0
+ changed = False
+ current_member = None
def get_context_data(self, *args, **kwargs):
optional_fields = {str(s) for s in ALL_FIELD_NAMES.values()} - {str(s) for s in MANDATORY_MEMBER_FIELD_NAMES.values()}
@@ -62,164 +71,190 @@ def get_context_data(self, *args, **kwargs):
'media_root': settings.MEDIA_ROOT,
}
- def _create_member(self, row, activate_users):
- """create new member based on row content.
+ def _create_member(self):
+ """create new member based on current row content.
returns created member and warnings if any
"""
- member = Member()
- warnings = []
+ self.current_member = Member(is_active=False)
+ self.changed = True
+ # print(f"newly created member is active:{member.is_active}")
for field in MEMBER_FIELD_NAMES:
trfield = t(field)
- if trfield in row and row[trfield]:
- if field == 'family':
- self._manage_family(member, row[trfield])
- elif field == 'avatar':
- warning = self._manage_avatar(member, row[trfield], row[t('username')])
- if warning:
- warnings.append(warning)
- elif member.__dict__[field] != row[trfield]:
- setattr(member, field, row[trfield])
-
- member.is_active = activate_users and not member.is_dead
- # set manager to people who imported the file if imported users are not activated
- member.managing_member = None if member.is_active else Member.objects.get(id=self.request.user.id)
- member.password = generate_random_string(16)
-
- return member, warnings
-
- def _manage_avatar(self, member, avatar_file, username):
+ if trfield in self.row and self.row[trfield]:
+ match field:
+ case 'family':
+ self._manage_family(self.row[trfield])
+ case 'managed_by':
+ self._manage_managed_by(self.row[trfield])
+ case 'avatar':
+ self._manage_avatar(self.row[trfield], self.row[t('username')])
+ case _:
+ if self.current_member.__dict__[field] != self.row[trfield]:
+ setattr(self.current_member, field, self.row[trfield])
+
+ self.current_member.is_active = self.activate_users and not self.current_member.is_dead
+ # set manager to people who imported the file if imported users are not activated and not yet mmanaged
+ if self.current_member.is_active:
+ self.current_member.member_manager = None
+ else:
+ self.current_member.member_manager = self.current_member.member_manager or Member.objects.get(id=self.request.user.id)
+ self.current_member.password = generate_random_string(16)
+ if self.changed:
+ self.created_num += 1
+
+ def _manage_avatar(self, avatar_file, username):
avatar = os.path.join(settings.MEDIA_REL, settings.AVATARS_DIR, avatar_file)
# avatar not changed
- if member.avatar and member.avatar.path == avatar:
- return (None, False)
+ if self.current_member.avatar and self.current_member.avatar.path == avatar:
+ return
# avatar image must already exist
if not os.path.exists(avatar):
- return (_("Avatar not found: %(avatar)s for username %(username)s. Ignored...") %
- {'avatar': avatar, 'username': username}, False)
+ self.warnings.append(_("Avatar not found: %(avatar)s for username %(username)s. Ignored...") %
+ {'avatar': avatar, 'username': username})
else:
try:
with open(avatar, 'rb') as image_file:
image = File(image_file)
- member.avatar.save(avatar_file, image)
- return (None, False)
+ self.current_member.avatar.save(avatar_file, image)
+ self.changed = True
except Exception as e:
- return (_("Error saving avatar (%(warning)s): %(avatar)s for username %(username)s. Ignored...") %
- {'warning': e, 'avatar': avatar, 'username': username}, False)
-
- def _manage_family(self, member, family_name):
- if member.family and member.family.name == family_name:
- return False
- member.family, created = Family.objects.get_or_create(name=family_name)
- return True
-
- def _update_member_field(self, member, field, value, username):
- changed = False
- warnings = []
-
- match field:
- case 'username':
- pass
- case 'family':
- changed = self._manage_family(member, value)
- case 'avatar':
- warning, modified = self._manage_avatar(member, value, username)
- changed = changed or modified
- if warning:
- warnings.append(warning)
- case _:
- if member.__dict__[field] != value:
- setattr(member, field, value)
- changed = True
-
- return (changed, warnings)
-
- def _update_member(self, member, row, activate_users):
- "update an existing member based on row content"
- changed = False
+ self.warnings.append(_("Error saving avatar (%(warning)s): %(avatar)s for username %(username)s. Ignored...") %
+ {'warning': e, 'avatar': avatar, 'username': username})
+
+ def _manage_family(self, family_name):
+ if not self.current_member.family or self.current_member.family.name != family_name:
+ self.current_member.family, _ = Family.objects.get_or_create(name=family_name)
+ self.changed = True
+
+ def _manage_managed_by(self, manager_username):
+ # print(f"in manage_manage_by, member {self.current_member.username}, manager {manager_username}. "
+ # f"Member is {'active' if self.current_member.is_active else 'inactive'}. "
+ # f"Activate during Import is {self.activate_users}")
+ if manager_username:
+ new_member_manager = Member.objects.filter(username=manager_username).first()
+ if self.activate_users and not self.warned_on_activate_users:
+ self.warned_on_activate_users = True
+ self.warnings.append(_("You requested to activate imported members. All managers will be ignored."))
+ elif not new_member_manager:
+ self.warnings.append(_("Manager %(manager)s not found for member %(member)s. Ignoring...") % {
+ 'manager': manager_username,
+ 'member': self.current_member.full_name})
+ elif self.current_member.member_manager == new_member_manager:
+ # no change
+ pass
+ elif self.current_member.is_active:
+ self.warnings.append(_("Member %(member)s is active. Ignoring manager %(manager)s") %
+ {'member': self.current_member.full_name, 'manager': manager_username})
+ else:
+ self.current_member.member_manager = new_member_manager
+ self.changed = True
+ else: # no manager provided
+ if not self.current_member.is_active: # we need one
+ if self.current_member.member_manager:
+ # just ignore with a warning
+ self.warnings.append(_("No manager provided for member %(member)s although inactive. "
+ "Keeping existing one (%(manager)s)...") % {
+ 'member': self.current_member.full_name, 'manager': self.current_member.member_manager.full_name})
+ else: # member is not active and has no manager, and none provided ==> error but continue
+ self.errors.append(_("Inactive member %(member)s has no manager. Please provide one!") % {
+ 'member': self.current_member.full_name
+ })
+ # print(f"member {self.current_member.username} manager is "
+ # f"{self.current_member.member_manager.username if self.current_member.member_manager else 'None'}")
+
+ def _update_member_field(self, field, value, username):
+ match field:
+ case 'username':
+ pass
+ case 'family':
+ self._manage_family(value)
+ case 'avatar':
+ self._manage_avatar(value, username)
+ case 'managed_by':
+ self._manage_managed_by(value)
+ case _:
+ if self.current_member.__dict__[field] != value:
+ setattr(self.current_member, field, value)
+ self.changed = True
+
+ def _update_member(self):
+ "update an existing member based on current row content"
# for all member fields but username
# if new value for this field, then override existing one
- warnings = []
- username = row[t('username')]
+ username = self.row[t('username')]
for field in MEMBER_FIELD_NAMES:
trfield = t(field)
- if trfield in row and row[trfield]:
- modified, new_warnings = self._update_member_field(member, field, row[trfield], username)
- changed = changed or modified
- warnings.extend(new_warnings)
- if activate_users and not member.is_dead:
- if member.managing_member is not None:
- member.managing_member = None
- changed = True
- if not member.is_active:
- member.is_active = changed = True
-
- return changed, warnings
-
- def _update_address(self, member, row):
- changed = False
+ if trfield in self.row and self.row[trfield]:
+ self._update_member_field(field, self.row[trfield], username)
+ if self.activate_users and not self.current_member.is_active:
+ self.current_member.member_manager = None
+ self.current_member.is_active = True
+ self.changed = True
+ if self.changed:
+ self.updated_num += 1
+
+ def _update_address(self):
address = {}
for field in ADDRESS_FIELD_NAMES:
trfield = t(field)
- if trfield in row:
- address[field] = row[trfield]
+ if trfield in self.row:
+ address[field] = self.row[trfield]
# if len(address) == 5: #we don't care if the address is incomplete
if len(address) > 0:
found = Address.objects.filter(**address).first()
if found:
- if member.address != found:
- member.address = found
- changed = True
+ if self.current_member.address != found:
+ self.current_member.address = found
+ self.changed = True
else:
address = Address.objects.create(**address)
address.save()
- member.address = address
- changed = True
- return changed
-
- def _import_csv(self, csv_file, activate_users):
- nbMembers = 0
- nbLines = 0
- all_warnings = []
+ self.current_member.address = address
+ self.changed = True
+
+ def _import_csv(self, csv_file):
csvf = io.TextIOWrapper(csv_file, encoding="utf-8", newline="")
reader = csv.DictReader(csvf)
check_fields(reader.fieldnames)
random.seed()
- for row in reader:
+ for self.row in reader:
+ # reset all row variables
+ self.warnings = []
+ self.errors = []
+ self.current_member = None
+ self.changed = False
# normalize username using slugify
- username = slugify(row[t('username')])
- row[t('username')] = username
+ self.row[t('username')] = slugify(self.row[t('username')])
# search for an existing member with this username
- member = Member.objects.filter(username=username).first()
- if member: # found, use it
- changed_member, new_warnings = self._update_member(member, row, activate_users)
+ self.current_member = Member.objects.filter(username=self.row[t('username')]).first()
+ if self.current_member: # found, update it
+ self._update_member()
else: # not found, create it
- member, new_warnings = self._create_member(row, activate_users)
- changed_member = True
-
- changed_address = self._update_address(member, row)
- all_warnings.extend(new_warnings)
-
- nbLines += 1
+ self._create_member()
- if changed_address or changed_member:
- member.save()
- nbMembers += 1
+ self._update_address()
+ self.rows_num += 1
- return (nbLines, nbMembers, all_warnings)
+ if self.changed:
+ self.current_member.save()
def post(self, request, *args, **kwargs):
self.request = request
form = CSVImportMembersForm(request.POST, request.FILES)
if form.is_valid():
csv_file = request.FILES["csv_file"]
- activate_users = form.cleaned_data["activate_users"]
+ self.activate_users = form.cleaned_data["activate_users"]
try:
- nbLines, nbMembers, warnings = self._import_csv(csv_file, activate_users)
- messages.success(request, _("CSV file uploaded: %(nbLines)i lines read, %(nbMembers)i members created or updated") %
- {'nbLines': nbLines, 'nbMembers': nbMembers})
- for warning in warnings:
+ self._import_csv(csv_file)
+ messages.success(request,
+ _("CSV file uploaded: %(rows_num)i lines read, %(created_num)i members created "
+ "and %(updated_num)i updated.") %
+ {'rows_num': self.rows_num, 'created_num': self.created_num, 'updated_num': self.updated_num})
+ for error in self.errors:
+ messages.error(request, _("Error: %(error)s") % {'warning': error})
+ for warning in self.warnings:
messages.warning(request, _("Warning: %(warning)s") % {'warning': warning})
except ValidationError as ve:
messages.error(request, ve.message)
diff --git a/members/views/views_member.py b/members/views/views_member.py
index f7e0d706..8fdbc800 100644
--- a/members/views/views_member.py
+++ b/members/views/views_member.py
@@ -38,12 +38,12 @@ def logout_member(request):
def editable(request, member):
if request.user.is_superuser:
return True
- manager = member.managing_member or member
+ manager = member.member_manager or member
return manager.id == request.user.id
-def managing_member_name(member):
- return Member.objects.get(id=member.managing_member.id).full_name if member and member.managing_member else None
+def member_manager_name(member):
+ return Member.objects.get(id=member.member_manager.id).full_name if member and member.member_manager else None
def register_remove_accents():
@@ -93,7 +93,7 @@ def get_context_data(self, **kwargs):
return super().get_context_data(**kwargs) | \
{
"can_edit": editable(self.request, member),
- "managing_member_name": member.managing_member.username if member.managing_member else None,
+ "member_manager_name": member.member_manager.username if member.member_manager else None,
"hobbies_list": [s.strip() for s in member.hobbies.split(',')] if member.hobbies else [],
}
@@ -131,9 +131,9 @@ def post(self, request, *args, **kwargs):
member = form.save()
# if new managed member is created, it must be inactivated
member.is_active = False
- # force managing_member to the logged in user
- member.managing_member = Member.objects.get(id=request.user.id)
- member.save(update_fields=['is_active', 'managing_member'])
+ # force member_manager to the logged in user
+ member.member_manager = Member.objects.get(id=request.user.id)
+ member.save(update_fields=['is_active', 'member_manager'])
messages.success(request, _('Member successfully created'))
return redirect("members:detail", member.id)
@@ -149,10 +149,10 @@ class EditMemberView(LoginRequiredMixin, generic.UpdateView):
def _can_edit(self, request, member):
if request.user.is_superuser:
return True
- if member.managing_member is None:
+ if member.member_manager is None:
return (member.id == request.user.id)
else:
- return (member.managing_member.id == request.user.id)
+ return (member.member_manager.id == request.user.id)
def get(self, request, pk):
member = get_object_or_404(Member, pk=pk)
@@ -166,7 +166,7 @@ def get(self, request, pk):
"family_form": FamilyUpdateForm(instance=member.family),
"pk": pk,
"title": self.title,
- "managing_member_name": managing_member_name(member)})
+ "member_manager_name": member_manager_name(member)})
def post(self, request, pk):
member = get_object_or_404(Member, pk=pk)
diff --git a/members/views/views_registration.py b/members/views/views_registration.py
index f7b8df0b..90ce4e8d 100644
--- a/members/views/views_registration.py
+++ b/members/views/views_registration.py
@@ -43,8 +43,8 @@ def check_before_register(self, request, encoded_email, token):
)
return redirect(reverse("members:login"))
else:
- # if member is already registered but is not active, ask him to contact his/her managing member
- manager = Member.objects.get(id=member.id).managing_member
+ # if member is already registered but is not active, ask him to contact his/her member manager
+ manager = Member.objects.get(id=member.id).member_manager
messages.error(request,
_("You are already registered but not active. Please contact %(admin)s to activate your account") %
{'admin': manager.full_name})