From cda9a2dcb99efd2a024f76cb39b7beae06d99a74 Mon Sep 17 00:00:00 2001 From: Tudor Amariei Date: Mon, 21 Oct 2024 15:49:03 +0300 Subject: [PATCH] Update user model to include more information --- backend/accounts/admin.py | 127 ++++++++++++++++++ ...oxy_user_created_user_modified_and_more.py | 51 +++++++ backend/accounts/models.py | 15 ++- backend/locale/en/LC_MESSAGES/django.po | 100 ++++++++------ backend/locale/ro/LC_MESSAGES/django.po | 100 ++++++++------ 5 files changed, 310 insertions(+), 83 deletions(-) create mode 100644 backend/accounts/admin.py create mode 100644 backend/accounts/migrations/0011_groupproxy_user_created_user_modified_and_more.py diff --git a/backend/accounts/admin.py b/backend/accounts/admin.py new file mode 100644 index 00000000..24add759 --- /dev/null +++ b/backend/accounts/admin.py @@ -0,0 +1,127 @@ +from django.contrib import admin +from django.contrib.admin import ModelAdmin +from django.contrib.admin.sites import NotRegistered +from django.contrib.auth.admin import GroupAdmin as BaseGroupAdmin +from django.contrib.auth.models import Group as BaseGroup +from django.urls import reverse_lazy +from django.utils.safestring import mark_safe +from django.utils.translation import gettext_lazy as _ + +from .models import User, GroupProxy + +# Remove the default admins for User and Group +try: + admin.site.unregister(User) +except NotRegistered: + pass + +try: + admin.site.unregister(BaseGroup) +except NotRegistered: + pass + + +@admin.register(User) +class UserAdmin(ModelAdmin): + list_display = ( + "email", + "get_organization", + "get_groups", + "is_active", + "is_ngohub_user", + "created", + ) + list_display_links = ( + "email", + "is_active", + "is_ngohub_user", + "created", + ) + list_filter = ("is_ngohub_user", "is_active", "is_superuser", "is_staff", "groups") + + search_fields = ("email", "first_name", "last_name") + + readonly_fields = ( + "email", + "password", + "organization", + "is_ngohub_user", + "date_joined", + "last_login", + "first_name", + "last_name", + "groups", + "user_permissions", + ) + + fieldsets = ( + ( + _("Identification"), + { + "fields": ( + "email", + "is_ngohub_user", + "organization", + "first_name", + "last_name", + ) + }, + ), + ( + _("Flags"), + { + "fields": ( + "is_active", + "is_staff", + "is_superuser", + ) + }, + ), + ( + _("Permissions"), + { + "fields": ( + "groups", + "user_permissions", + ) + }, + ), + ( + _("Important dates"), + { + "fields": ( + "date_joined", + "last_login", + ) + }, + ), + ) + + def get_organization(self, obj: User): + if not obj: + return "-" + + if not obj.organization: + return "-" + + organization_url = reverse_lazy("admin:hub_organization_change", args=[obj.organization.pk]) + return mark_safe(f'{obj.organization.name}') + + get_organization.short_description = _("Organization") + + def get_groups(self, obj: User): + user_groups = obj.groups.all() + + group_url = reverse_lazy("admin:accounts_user_changelist") + groups_display = [ + f'{group.name}' for group in user_groups + ] + + # noinspection DjangoSafeString + return mark_safe(", ".join(groups_display)) + + get_groups.short_description = _("Groups") + + +@admin.register(GroupProxy) +class GroupAdmin(BaseGroupAdmin, ModelAdmin): ... diff --git a/backend/accounts/migrations/0011_groupproxy_user_created_user_modified_and_more.py b/backend/accounts/migrations/0011_groupproxy_user_created_user_modified_and_more.py new file mode 100644 index 00000000..e87e9098 --- /dev/null +++ b/backend/accounts/migrations/0011_groupproxy_user_created_user_modified_and_more.py @@ -0,0 +1,51 @@ +# Generated by Django 4.2.16 on 2024-10-21 12:48 + +import django.contrib.auth.models +from django.db import migrations, models +import django.utils.timezone +import model_utils.fields + + +class Migration(migrations.Migration): + + dependencies = [ + ("auth", "0012_alter_user_first_name_max_length"), + ("accounts", "0010_user_organization"), + ] + + operations = [ + migrations.CreateModel( + name="GroupProxy", + fields=[], + options={ + "verbose_name": "Grup", + "verbose_name_plural": "Grupuri", + "proxy": True, + "indexes": [], + "constraints": [], + }, + bases=("auth.group",), + managers=[ + ("objects", django.contrib.auth.models.GroupManager()), + ], + ), + migrations.AddField( + model_name="user", + name="created", + field=model_utils.fields.AutoCreatedField( + default=django.utils.timezone.now, editable=False, verbose_name="created" + ), + ), + migrations.AddField( + model_name="user", + name="modified", + field=model_utils.fields.AutoLastModifiedField( + default=django.utils.timezone.now, editable=False, verbose_name="modified" + ), + ), + migrations.AlterField( + model_name="user", + name="is_ngohub_user", + field=models.BooleanField(default=False, verbose_name="este utilizator NGO Hub"), + ), + ] diff --git a/backend/accounts/models.py b/backend/accounts/models.py index 0abbf0e9..57ad7943 100644 --- a/backend/accounts/models.py +++ b/backend/accounts/models.py @@ -1,9 +1,10 @@ from django.conf import settings -from django.contrib.auth.models import AbstractUser +from django.contrib.auth.models import AbstractUser, Group from django.db import models from django.db.models.functions import Lower from django.utils.decorators import method_decorator from django.utils.translation import gettext as _ +from model_utils.models import TimeStampedModel from civil_society_vote.common.cache import cache_decorator @@ -14,7 +15,7 @@ NGO_GROUP = "ONG" -class User(AbstractUser): +class User(AbstractUser, TimeStampedModel): # We ignore the "username" field because we will use the email for the authentication username = models.CharField( verbose_name=_("username"), @@ -25,7 +26,7 @@ class User(AbstractUser): null=True, ) email = models.EmailField(verbose_name=_("email address"), blank=False, null=False, unique=True) - is_ngohub_user = models.BooleanField(default=False) + is_ngohub_user = models.BooleanField(verbose_name=_("is ngo hub user"), default=False) organization = models.ForeignKey( "hub.Organization", @@ -67,3 +68,11 @@ def in_commission_groups(self): @method_decorator(cache_decorator(timeout=settings.TIMEOUT_CACHE_NORMAL, cache_key_prefix="staff_groups")) def in_staff_groups(self): return self.groups.filter(name__in=[STAFF_GROUP, SUPPORT_GROUP]).exists() + + +class GroupProxy(Group): + class Meta: + proxy = True + + verbose_name = _("Group") + verbose_name_plural = _("Groups") diff --git a/backend/locale/en/LC_MESSAGES/django.po b/backend/locale/en/LC_MESSAGES/django.po index 7447297d..37e9397b 100644 --- a/backend/locale/en/LC_MESSAGES/django.po +++ b/backend/locale/en/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-21 11:34+0300\n" +"POT-Creation-Date: 2024-10-21 15:45+0300\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,6 +17,30 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" +#: accounts/admin.py:59 +msgid "Identification" +msgstr "" + +#: accounts/admin.py:71 +msgid "Flags" +msgstr "" + +#: accounts/admin.py:81 hub/admin.py:53 +msgid "Permissions" +msgstr "" + +#: accounts/admin.py:90 hub/admin.py:64 +msgid "Important dates" +msgstr "" + +#: accounts/admin.py:110 hub/models.py:340 +msgid "Organization" +msgstr "" + +#: accounts/admin.py:123 accounts/models.py:78 +msgid "Groups" +msgstr "" + #: accounts/forms.py:21 msgid "Emails don't match." msgstr "" @@ -25,26 +49,34 @@ msgstr "" msgid "This email can't be set." msgstr "" -#: accounts/models.py:20 +#: accounts/models.py:21 msgid "username" msgstr "" -#: accounts/models.py:23 +#: accounts/models.py:24 msgid "We do not use this field" msgstr "" -#: accounts/models.py:27 hub/templates/registration/reset_password.html:39 +#: accounts/models.py:28 hub/templates/registration/reset_password.html:39 msgid "email address" msgstr "" -#: accounts/models.py:42 -msgid "User" +#: accounts/models.py:29 +msgid "is ngo hub user" msgstr "" #: accounts/models.py:43 +msgid "User" +msgstr "" + +#: accounts/models.py:44 msgid "Users" msgstr "" +#: accounts/models.py:77 +msgid "Group" +msgstr "" + #: accounts/templates/accounts/avatar/change.html:47 msgid "You haven't uploaded an avatar yet. Please upload one now." msgstr "" @@ -346,14 +378,6 @@ msgstr "" msgid "Personal info" msgstr "" -#: hub/admin.py:53 -msgid "Permissions" -msgstr "" - -#: hub/admin.py:64 -msgid "Important dates" -msgstr "" - #: hub/admin.py:83 msgid "groups" msgstr "" @@ -816,10 +840,6 @@ msgstr "" msgid "Organizations" msgstr "" -#: hub/models.py:340 -msgid "Organization" -msgstr "" - #: hub/models.py:528 msgid "Pending" msgstr "" @@ -838,7 +858,7 @@ msgstr "" msgid "The domain in which the candidate is running." msgstr "" -#: hub/models.py:558 hub/templates/hub/candidate/detail.html:103 +#: hub/models.py:558 hub/templates/hub/candidate/detail.html:115 msgid "Representative name" msgstr "" @@ -862,7 +882,7 @@ msgstr "" msgid "Candidate photo" msgstr "" -#: hub/models.py:580 hub/templates/hub/candidate/detail.html:107 +#: hub/models.py:580 hub/templates/hub/candidate/detail.html:119 msgid "Representative statement" msgstr "Candidate statement" @@ -873,7 +893,7 @@ msgid "" "public office and is not a dignitary of the Romanian state." msgstr "" -#: hub/models.py:591 hub/templates/hub/candidate/detail.html:112 +#: hub/models.py:591 hub/templates/hub/candidate/detail.html:124 msgid "Mandate" msgstr "" @@ -883,7 +903,7 @@ msgid "" "highlighting of the domain for which it is running" msgstr "" -#: hub/models.py:601 hub/templates/hub/candidate/detail.html:117 +#: hub/models.py:601 hub/templates/hub/candidate/detail.html:129 msgid "Letter of intent" msgstr "" @@ -893,7 +913,7 @@ msgid "" "CES)" msgstr "" -#: hub/models.py:608 hub/templates/hub/candidate/detail.html:122 +#: hub/models.py:608 hub/templates/hub/candidate/detail.html:134 msgid "CV" msgstr "" @@ -901,7 +921,7 @@ msgstr "" msgid "Europass format CV" msgstr "" -#: hub/models.py:615 hub/templates/hub/candidate/detail.html:127 +#: hub/models.py:615 hub/templates/hub/candidate/detail.html:139 msgid "Declaration of interests" msgstr "" @@ -909,7 +929,7 @@ msgstr "" msgid "Official format Declaration of interests" msgstr "" -#: hub/models.py:622 hub/templates/hub/candidate/detail.html:132 +#: hub/models.py:622 hub/templates/hub/candidate/detail.html:144 msgid "Fiscal record" msgstr "" @@ -917,7 +937,7 @@ msgstr "" msgid "Fiscal record, valid at the time of submitting the candidacy" msgstr "" -#: hub/models.py:629 hub/templates/hub/candidate/detail.html:137 +#: hub/models.py:629 hub/templates/hub/candidate/detail.html:149 msgid "Criminal record" msgstr "" @@ -942,27 +962,27 @@ msgstr "" msgid "Cannot update candidate after votes have been cast." msgstr "" -#: hub/models.py:732 +#: hub/models.py:733 msgid "Candidate votes" msgstr "" -#: hub/models.py:733 +#: hub/models.py:734 msgid "Candidate vote" msgstr "" -#: hub/models.py:756 +#: hub/models.py:757 msgid "Canditate supporters" msgstr "" -#: hub/models.py:757 +#: hub/models.py:758 msgid "Candidate supporter" msgstr "" -#: hub/models.py:771 +#: hub/models.py:772 msgid "Candidate confirmations" msgstr "" -#: hub/models.py:772 +#: hub/models.py:773 msgid "Candidate confirmation" msgstr "" @@ -1017,32 +1037,32 @@ msgstr "" msgid "Section: " msgstr "" -#: hub/templates/hub/candidate/detail.html:71 +#: hub/templates/hub/candidate/detail.html:77 #: hub/templates/hub/committee/candidates.html:44 msgid "Supporters:" msgstr "" -#: hub/templates/hub/candidate/detail.html:77 +#: hub/templates/hub/candidate/detail.html:83 msgid "Confirm candidate status" msgstr "" -#: hub/templates/hub/candidate/detail.html:83 +#: hub/templates/hub/candidate/detail.html:90 msgid "✔ VOTED" msgstr "" -#: hub/templates/hub/candidate/detail.html:85 +#: hub/templates/hub/candidate/detail.html:94 msgid "You have used all the votes allotted for this domain." msgstr "" -#: hub/templates/hub/candidate/detail.html:87 +#: hub/templates/hub/candidate/detail.html:98 msgid "Vote" msgstr "" -#: hub/templates/hub/candidate/detail.html:99 +#: hub/templates/hub/candidate/detail.html:111 msgid "Candidate information" msgstr "" -#: hub/templates/hub/candidate/detail.html:104 +#: hub/templates/hub/candidate/detail.html:116 msgid "Representative role" msgstr "" @@ -1566,7 +1586,7 @@ msgstr "" msgid "You must write a rejection message." msgstr "" -#: hub/views.py:748 +#: hub/views.py:811 #, python-format msgid "Please wait %(minutes_threshold)s minutes before updating again." msgstr "" diff --git a/backend/locale/ro/LC_MESSAGES/django.po b/backend/locale/ro/LC_MESSAGES/django.po index d9a04d62..253533ad 100644 --- a/backend/locale/ro/LC_MESSAGES/django.po +++ b/backend/locale/ro/LC_MESSAGES/django.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-10-21 11:34+0300\n" +"POT-Creation-Date: 2024-10-21 15:45+0300\n" "PO-Revision-Date: 2020-04-23 17:54+0300\n" "Last-Translator: \n" "Language-Team: \n" @@ -19,6 +19,30 @@ msgstr "" "2:1));\n" "X-Generator: Poedit 2.3\n" +#: accounts/admin.py:59 +msgid "Identification" +msgstr "Identificare" + +#: accounts/admin.py:71 +msgid "Flags" +msgstr "Acțiuni" + +#: accounts/admin.py:81 hub/admin.py:53 +msgid "Permissions" +msgstr "Permisiuni" + +#: accounts/admin.py:90 hub/admin.py:64 +msgid "Important dates" +msgstr "Date importante" + +#: accounts/admin.py:110 hub/models.py:340 +msgid "Organization" +msgstr "Organizație" + +#: accounts/admin.py:123 accounts/models.py:78 +msgid "Groups" +msgstr "Grupuri" + #: accounts/forms.py:21 msgid "Emails don't match." msgstr "Emailurile nu se potrivesc." @@ -27,26 +51,34 @@ msgstr "Emailurile nu se potrivesc." msgid "This email can't be set." msgstr "Acest email nu poate fi ales." -#: accounts/models.py:20 +#: accounts/models.py:21 msgid "username" msgstr "nume utilizator" -#: accounts/models.py:23 +#: accounts/models.py:24 msgid "We do not use this field" msgstr "Nu utilizăm acest câmp" -#: accounts/models.py:27 hub/templates/registration/reset_password.html:39 +#: accounts/models.py:28 hub/templates/registration/reset_password.html:39 msgid "email address" msgstr "adresă email" -#: accounts/models.py:42 +#: accounts/models.py:29 +msgid "is ngo hub user" +msgstr "este utilizator NGO Hub" + +#: accounts/models.py:43 msgid "User" msgstr "Utilizator" -#: accounts/models.py:43 +#: accounts/models.py:44 msgid "Users" msgstr "Utilizatori" +#: accounts/models.py:77 +msgid "Group" +msgstr "Grup" + #: accounts/templates/accounts/avatar/change.html:47 msgid "You haven't uploaded an avatar yet. Please upload one now." msgstr "Nu ai ales niciun avatar. Te rugăm să îți alegi unul." @@ -357,14 +389,6 @@ msgstr "autentificare/" msgid "Personal info" msgstr "Informații personale" -#: hub/admin.py:53 -msgid "Permissions" -msgstr "Permisiuni" - -#: hub/admin.py:64 -msgid "Important dates" -msgstr "Date importante" - #: hub/admin.py:83 msgid "groups" msgstr "grupuri" @@ -844,10 +868,6 @@ msgstr "Ultima actualizare NGO Hub" msgid "Organizations" msgstr "Organizații" -#: hub/models.py:340 -msgid "Organization" -msgstr "Organizație" - #: hub/models.py:528 msgid "Pending" msgstr "În așteptare" @@ -868,7 +888,7 @@ msgstr "" msgid "The domain in which the candidate is running." msgstr "Domeniul pentru care candidează candidatul." -#: hub/models.py:558 hub/templates/hub/candidate/detail.html:103 +#: hub/models.py:558 hub/templates/hub/candidate/detail.html:115 msgid "Representative name" msgstr "Nume reprezentant" @@ -896,7 +916,7 @@ msgstr "Funcția în organizației a persoanei desemnate" msgid "Candidate photo" msgstr "Fotografia candidatului" -#: hub/models.py:580 hub/templates/hub/candidate/detail.html:107 +#: hub/models.py:580 hub/templates/hub/candidate/detail.html:119 msgid "Representative statement" msgstr "Declarație candidat" @@ -910,7 +930,7 @@ msgstr "" "declară că nu este membru al conducerii unui partid politic, nu a fost ales " "într-o funcție publică și nu este demnitar al statului român." -#: hub/models.py:591 hub/templates/hub/candidate/detail.html:112 +#: hub/models.py:591 hub/templates/hub/candidate/detail.html:124 msgid "Mandate" msgstr "Mandat" @@ -922,7 +942,7 @@ msgstr "" "Mandat din partea organizației (semnat în original + electronică) cu " "sublinierea domeniului pentru care candidează" -#: hub/models.py:601 hub/templates/hub/candidate/detail.html:117 +#: hub/models.py:601 hub/templates/hub/candidate/detail.html:129 msgid "Letter of intent" msgstr "Scrisoare de intenție" @@ -934,7 +954,7 @@ msgstr "" "Scrisoare de intenție (cu menționarea domeniului pe care dorește să îl " "reprezinte în CES)" -#: hub/models.py:608 hub/templates/hub/candidate/detail.html:122 +#: hub/models.py:608 hub/templates/hub/candidate/detail.html:134 msgid "CV" msgstr "CV" @@ -942,7 +962,7 @@ msgstr "CV" msgid "Europass format CV" msgstr "CV în format Europass" -#: hub/models.py:615 hub/templates/hub/candidate/detail.html:127 +#: hub/models.py:615 hub/templates/hub/candidate/detail.html:139 msgid "Declaration of interests" msgstr "Declarație de interese" @@ -950,7 +970,7 @@ msgstr "Declarație de interese" msgid "Official format Declaration of interests" msgstr "Declarație de interese în format oficial" -#: hub/models.py:622 hub/templates/hub/candidate/detail.html:132 +#: hub/models.py:622 hub/templates/hub/candidate/detail.html:144 msgid "Fiscal record" msgstr "Cazier fiscal" @@ -958,7 +978,7 @@ msgstr "Cazier fiscal" msgid "Fiscal record, valid at the time of submitting the candidacy" msgstr "Cazier fiscal, valabil la data depunerii candidaturii" -#: hub/models.py:629 hub/templates/hub/candidate/detail.html:137 +#: hub/models.py:629 hub/templates/hub/candidate/detail.html:149 msgid "Criminal record" msgstr "Cazier judiciar" @@ -983,27 +1003,27 @@ msgstr "Candidatură" msgid "Cannot update candidate after votes have been cast." msgstr "Nu se poate modifica candidatura după ce s-au înregistrat voturi." -#: hub/models.py:732 +#: hub/models.py:733 msgid "Candidate votes" msgstr "Voturi candidatură" -#: hub/models.py:733 +#: hub/models.py:734 msgid "Candidate vote" msgstr "Vot candidatură" -#: hub/models.py:756 +#: hub/models.py:757 msgid "Canditate supporters" msgstr "Susținători candidatură" -#: hub/models.py:757 +#: hub/models.py:758 msgid "Candidate supporter" msgstr "Susținător candidatură" -#: hub/models.py:771 +#: hub/models.py:772 msgid "Candidate confirmations" msgstr "Confirmări candidatură" -#: hub/models.py:772 +#: hub/models.py:773 msgid "Candidate confirmation" msgstr "Confirmare candidatură" @@ -1060,32 +1080,32 @@ msgstr "Încarcă fișier" msgid "Section: " msgstr "Secțiune: " -#: hub/templates/hub/candidate/detail.html:71 +#: hub/templates/hub/candidate/detail.html:77 #: hub/templates/hub/committee/candidates.html:44 msgid "Supporters:" msgstr "Susținători:" -#: hub/templates/hub/candidate/detail.html:77 +#: hub/templates/hub/candidate/detail.html:83 msgid "Confirm candidate status" msgstr "Confirmă starea candidaturii" -#: hub/templates/hub/candidate/detail.html:83 +#: hub/templates/hub/candidate/detail.html:90 msgid "✔ VOTED" msgstr "✔ VOTAT" -#: hub/templates/hub/candidate/detail.html:85 +#: hub/templates/hub/candidate/detail.html:94 msgid "You have used all the votes allotted for this domain." msgstr "Ai folosit toate voturile alocate pentru acest domeniu." -#: hub/templates/hub/candidate/detail.html:87 +#: hub/templates/hub/candidate/detail.html:98 msgid "Vote" msgstr "Votează" -#: hub/templates/hub/candidate/detail.html:99 +#: hub/templates/hub/candidate/detail.html:111 msgid "Candidate information" msgstr "Informații candidatură" -#: hub/templates/hub/candidate/detail.html:104 +#: hub/templates/hub/candidate/detail.html:116 msgid "Representative role" msgstr "Funcție reprezentant" @@ -1633,7 +1653,7 @@ msgstr "" msgid "You must write a rejection message." msgstr "Trebuie să completați un motiv de respingere." -#: hub/views.py:748 +#: hub/views.py:811 #, python-format msgid "Please wait %(minutes_threshold)s minutes before updating again." msgstr ""