From 8980cc263b3ca570fa21c3d13bc4d80fe92f5441 Mon Sep 17 00:00:00 2001 From: moanos Date: Sat, 8 Jun 2024 09:31:23 +0200 Subject: [PATCH] refactor: Rip out member and use custom user model --- src/fellchensammlung/admin.py | 17 +- src/fellchensammlung/mail.py | 6 +- .../management/commands/populate_db.py | 14 +- .../migrations/0001_initial.py | 246 ++++++++---------- src/fellchensammlung/models.py | 85 +++--- src/fellchensammlung/urls.py | 2 +- src/fellchensammlung/views.py | 4 +- 7 files changed, 162 insertions(+), 212 deletions(-) diff --git a/src/fellchensammlung/admin.py b/src/fellchensammlung/admin.py index 12ee258..833a196 100644 --- a/src/fellchensammlung/admin.py +++ b/src/fellchensammlung/admin.py @@ -5,20 +5,7 @@ from django.utils.html import format_html from .models import User, Language, Text, ReportComment, ReportAdoptionNotice from .models import Animal, Species, RescueOrganization, AdoptionNotice, Location, Rule, Image, ModerationAction, \ - Member, Comment, Report, Announcement, AdoptionNoticeStatus - - -# Define an inline admin descriptor for Employee model -# which acts a bit like a singleton -class MemberInline(admin.StackedInline): - model = Member - can_delete = False - verbose_name_plural = "member" - - -# Define a new User admin -class UserAdmin(BaseUserAdmin): - inlines = [MemberInline] + Comment, Report, Announcement, AdoptionNoticeStatus, User class StatusInline(admin.StackedInline): @@ -33,7 +20,7 @@ class AdoptionNoticeAdmin(admin.ModelAdmin): # Re-register UserAdmin -admin.site.register(User, UserAdmin) +admin.site.register(User) def _reported_content_link(obj): diff --git a/src/fellchensammlung/mail.py b/src/fellchensammlung/mail.py index 32c7f3d..51fc00d 100644 --- a/src/fellchensammlung/mail.py +++ b/src/fellchensammlung/mail.py @@ -5,15 +5,15 @@ from django.utils.translation import gettext from django.conf import settings from django.core import mail from django.db.models import Q -from fellchensammlung.models import Member +from fellchensammlung.models import User from notfellchen.settings import host def mail_admins_new_report(report): subject = _("Neue Meldung") - for moderator in Member.objects.filter(Q(trust_level=Member.MODERATOR) | Q(trust_level=Member.ADMIN)): + for moderator in User.objects.filter(Q(trust_level=User.MODERATOR) | Q(trust_level=User.ADMIN)): greeting = _("Moin,") + "\r\n" - new_report_text = _("es wurde eine Vermittlung gemeldet.") + "\r\n" + new_report_text = _("es wurde ein Regelverstoß gemeldet.") + "\r\n" if len(report.reported_broken_rules.all()) > 0: reported_rules_text = (f"Ein Verstoß gegen die folgenden Regeln wurde gemeldet:\r\n" f"- {'\r\n - '.join([str(r) for r in report.reported_broken_rules.all()])}\r\n") diff --git a/src/fellchensammlung/management/commands/populate_db.py b/src/fellchensammlung/management/commands/populate_db.py index 1ddedad..a17afad 100644 --- a/src/fellchensammlung/management/commands/populate_db.py +++ b/src/fellchensammlung/management/commands/populate_db.py @@ -6,7 +6,7 @@ from django.core.files import File from fellchensammlung import baker_recipes from model_bakery import baker -from fellchensammlung.models import AdoptionNotice, Species, Animal, Image, ModerationAction, User, Member, Rule, \ +from fellchensammlung.models import AdoptionNotice, Species, Animal, Image, ModerationAction, User, Rule, \ Report, Comment, ReportAdoptionNotice @@ -100,15 +100,11 @@ class Command(BaseCommand): public_comment="A moderator has deleted the reported content") User.objects.create_user('test', password='foobar') - u_admin1 = User.objects.create_superuser(username="admin", password="admin", email="admin1@example.org") - admin1 = Member.objects.get(user=u_admin1) - admin1.trust_level = Member.ADMIN - admin1.save() + admin1 = User.objects.create_superuser(username="admin", password="admin", email="admin1@example.org", + trust_level=User.ADMIN) - u_mod1 = User.objects.create_user(username="mod1", password="mod", email="mod1@example.org") - mod1 = Member.objects.get(user=u_mod1) - mod1.trust_level = Member.MODERATOR - mod1.save() + mod1 = User.objects.create_user(username="mod1", password="mod", email="mod1@example.org", + trust_level=User.MODERATOR) comment1 = baker.make(Comment, user=admin1, text="This is a comment", adoption_notice=adoption1) comment2 = baker.make(Comment, diff --git a/src/fellchensammlung/migrations/0001_initial.py b/src/fellchensammlung/migrations/0001_initial.py index e166f24..38353c7 100644 --- a/src/fellchensammlung/migrations/0001_initial.py +++ b/src/fellchensammlung/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 5.0.6 on 2024-06-06 16:11 +# Generated by Django 5.0.6 on 2024-06-08 07:08 import datetime import django.contrib.auth.models @@ -19,61 +19,6 @@ class Migration(migrations.Migration): ] operations = [ - migrations.CreateModel( - name="AdoptionNotice", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "created_at", - models.DateField( - default=datetime.datetime.now, verbose_name="Erstellt am" - ), - ), - ( - "searching_since", - models.DateField(verbose_name="Sucht nach einem Zuhause seit"), - ), - ("name", models.CharField(max_length=200)), - ( - "description", - models.TextField( - blank=True, null=True, verbose_name="Beschreibung" - ), - ), - ( - "further_information", - models.URLField( - blank=True, null=True, verbose_name="Link zu mehr Informationen" - ), - ), - ( - "group_only", - models.BooleanField( - default=False, verbose_name="Ausschließlich Gruppenadoption" - ), - ), - ( - "location_string", - models.CharField(max_length=200, verbose_name="Ortsangabe"), - ), - ], - options={ - "permissions": [ - ( - "create_active_adoption_notice", - "Can create an active adoption notice", - ) - ], - }, - ), migrations.CreateModel( name="Text", fields=[ @@ -307,6 +252,19 @@ class Migration(migrations.Migration): default=django.utils.timezone.now, verbose_name="date joined" ), ), + ( + "trust_level", + models.CharField( + choices=[ + ("admin", "Administrator*in"), + ("Moderator", "Moderator*in"), + ("Koordinator*in", "Koordinator*in"), + ("Mitglied", "Mitglied"), + ], + default="Mitglied", + max_length=100, + ), + ), ( "groups", models.ManyToManyField( @@ -329,16 +287,101 @@ class Migration(migrations.Migration): verbose_name="user permissions", ), ), + ( + "preferred_language", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.PROTECT, + to="fellchensammlung.language", + verbose_name="Bevorzugte Sprache", + ), + ), ], options={ - "verbose_name": "user", - "verbose_name_plural": "users", - "abstract": False, + "verbose_name": "Nutzer*in", + "verbose_name_plural": "Nutzer*innen", }, managers=[ ("objects", django.contrib.auth.models.UserManager()), ], ), + migrations.CreateModel( + name="AdoptionNotice", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "created_at", + models.DateField( + default=datetime.datetime.now, verbose_name="Erstellt am" + ), + ), + ( + "searching_since", + models.DateField(verbose_name="Sucht nach einem Zuhause seit"), + ), + ("name", models.CharField(max_length=200)), + ( + "description", + models.TextField( + blank=True, null=True, verbose_name="Beschreibung" + ), + ), + ( + "further_information", + models.URLField( + blank=True, null=True, verbose_name="Link zu mehr Informationen" + ), + ), + ( + "group_only", + models.BooleanField( + default=False, verbose_name="Ausschließlich Gruppenadoption" + ), + ), + ( + "location_string", + models.CharField(max_length=200, verbose_name="Ortsangabe"), + ), + ( + "created_by", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + verbose_name="Creator", + ), + ), + ( + "photos", + models.ManyToManyField(blank=True, to="fellchensammlung.image"), + ), + ( + "location", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="fellchensammlung.location", + ), + ), + ], + options={ + "permissions": [ + ( + "create_active_adoption_notice", + "Can create an active adoption notice", + ) + ], + }, + ), migrations.CreateModel( name="AdoptionNoticeStatus", fields=[ @@ -390,6 +433,7 @@ class Migration(migrations.Migration): ("other", "other"), ("against_the_rules", "against_the_rules"), ("missing_information", "missing_information"), + ("technical_error", "technical_error"), ], max_length=200, ), @@ -442,79 +486,6 @@ class Migration(migrations.Migration): ], bases=("fellchensammlung.text",), ), - migrations.AddField( - model_name="adoptionnotice", - name="photos", - field=models.ManyToManyField(blank=True, to="fellchensammlung.image"), - ), - migrations.AddField( - model_name="text", - name="language", - field=models.ForeignKey( - on_delete=django.db.models.deletion.PROTECT, - to="fellchensammlung.language", - verbose_name="Sprache", - ), - ), - migrations.AddField( - model_name="adoptionnotice", - name="location", - field=models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.SET_NULL, - to="fellchensammlung.location", - ), - ), - migrations.CreateModel( - name="Member", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "trust_level", - models.CharField( - choices=[ - ("admin", "Administrator*in"), - ("Moderator", "Moderator*in"), - ("Koordinator*in", "Koordinator*in"), - ("Mitglied", "Mitglied"), - ], - default="Mitglied", - max_length=100, - ), - ), - ( - "preferred_language", - models.ForeignKey( - blank=True, - null=True, - on_delete=django.db.models.deletion.PROTECT, - to="fellchensammlung.language", - verbose_name="Bevorzugte Sprache", - ), - ), - ( - "user", - models.OneToOneField( - on_delete=django.db.models.deletion.CASCADE, - to=settings.AUTH_USER_MODEL, - verbose_name="Nutzer*in", - ), - ), - ], - options={ - "verbose_name": "Nutzer*in", - "verbose_name_plural": "Nutzer*innen", - }, - ), migrations.CreateModel( name="Comment", fields=[ @@ -551,12 +522,21 @@ class Migration(migrations.Migration): "user", models.ForeignKey( on_delete=django.db.models.deletion.CASCADE, - to="fellchensammlung.member", + to=settings.AUTH_USER_MODEL, verbose_name="Nutzer*in", ), ), ], ), + migrations.AddField( + model_name="text", + name="language", + field=models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to="fellchensammlung.language", + verbose_name="Sprache", + ), + ), migrations.CreateModel( name="ModerationAction", fields=[ diff --git a/src/fellchensammlung/models.py b/src/fellchensammlung/models.py index 7fe80c9..bbcc25d 100644 --- a/src/fellchensammlung/models.py +++ b/src/fellchensammlung/models.py @@ -35,6 +35,40 @@ class Language(models.Model): verbose_name_plural = _('Sprachen') +class User(AbstractUser): + """ + Model that holds a user's profile, including the django user model + + The trust levels act as permission system and can be displayed as a badge for the user + """ + + # Admins can perform all actions and have the highest trust associated with them + # Moderators can make moderation decisions regarding the deletion of content + # Coordinators can create adoption notices without them being checked + # Members can create adoption notices that must be activated + ADMIN = "admin" + MODERATOR = "Moderator" + COORDINATOR = "Koordinator*in" + MEMBER = "Mitglied" + TRUES_LEVEL = { + ADMIN: "Administrator*in", + MODERATOR: "Moderator*in", + COORDINATOR: "Koordinator*in", + MEMBER: "Mitglied", + } + + preferred_language = models.ForeignKey(Language, on_delete=models.PROTECT, null=True, blank=True, + verbose_name=_('Bevorzugte Sprache')) + trust_level = models.CharField(choices=TRUES_LEVEL, max_length=100, default=MEMBER) + + class Meta: + verbose_name = _('Nutzer*in') + verbose_name_plural = _('Nutzer*innen') + + def get_absolute_url(self): + return reverse("user-detail", args=[str(self.pk)]) + + class Image(models.Model): image = models.ImageField(upload_to='images') alt_text = models.TextField(max_length=2000) @@ -124,6 +158,7 @@ class AdoptionNotice(models.Model): photos = models.ManyToManyField(Image, blank=True) location_string = models.CharField(max_length=200, verbose_name=_("Ortsangabe")) location = models.ForeignKey(Location, blank=True, null=True, on_delete=models.SET_NULL, ) + created_by = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_('Creator')) @property def animals(self): @@ -411,54 +446,6 @@ Membership """ -class User(AbstractUser): - pass - - -class Member(models.Model): - """ - Model that holds a user's profile, including the django user model - - It is created upon creation of a new django user (see add_member) - The trust levels act as permission system and can be displayed as a badge for the user - """ - - # Admins can perform all actions and have the highest trust associated with them - # Moderators can make moderation decisions regarding the deletion of content - # Coordinators can create adoption notices without them being checked - # Members can create adoption notices that must be activated - ADMIN = "admin" - MODERATOR = "Moderator" - COORDINATOR = "Koordinator*in" - MEMBER = "Mitglied" - TRUES_LEVEL = { - ADMIN: "Administrator*in", - MODERATOR: "Moderator*in", - COORDINATOR: "Koordinator*in", - MEMBER: "Mitglied", - } - - user = models.OneToOneField(User, on_delete=models.CASCADE, verbose_name=_('Nutzer*in')) - preferred_language = models.ForeignKey(Language, on_delete=models.PROTECT, null=True, blank=True, - verbose_name=_('Bevorzugte Sprache')) - trust_level = models.CharField(choices=TRUES_LEVEL, max_length=100, default=MEMBER) - - class Meta: - verbose_name = _('Nutzer*in') - verbose_name_plural = _('Nutzer*innen') - - @receiver(post_save, sender=User) - def add_member(sender, instance, created, raw, using, **kwargs): - if len(Member.objects.filter(user=instance)) != 1: - Member.objects.create(user=instance) - - def __str__(self): - return str(self.user) - - def get_absolute_url(self): - return reverse("member-detail", args=[str(self.user.id)]) - - class Text(models.Model): """ Base class to store markdown content @@ -528,7 +515,7 @@ class Comment(models.Model): """ Class to store comments in markdown content """ - user = models.ForeignKey(Member, on_delete=models.CASCADE, verbose_name=_('Nutzer*in')) + user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=_('Nutzer*in')) created_at = models.DateTimeField(auto_now_add=True) adoption_notice = models.ForeignKey(AdoptionNotice, on_delete=models.CASCADE, verbose_name=_('AdoptionNotice')) text = models.TextField(verbose_name="Inhalt") diff --git a/src/fellchensammlung/urls.py b/src/fellchensammlung/urls.py index 949a6c9..906a274 100644 --- a/src/fellchensammlung/urls.py +++ b/src/fellchensammlung/urls.py @@ -46,7 +46,7 @@ urlpatterns = [ ## USERS ## ########### # ex: user/1 - path("user//", views.member_detail, name="user-detail"), + path("user//", views.user_detail, name="user-detail"), path('accounts/register/', RegistrationView.as_view( diff --git a/src/fellchensammlung/views.py b/src/fellchensammlung/views.py index f3ebfb3..f5dd89d 100644 --- a/src/fellchensammlung/views.py +++ b/src/fellchensammlung/views.py @@ -12,7 +12,7 @@ from notfellchen import settings from fellchensammlung import logger from fellchensammlung.models import AdoptionNotice, Text, Animal, Rule, Image, Report, ModerationAction, \ - Member, Location + User, Location from .forms import AdoptionNoticeForm, ImageForm, ReportAdoptionNoticeForm, CommentForm, ReportCommentForm, AnimalForm, \ AdoptionNoticeSearchForm from .models import Language, Announcement @@ -289,7 +289,7 @@ def report_detail_success(request, report_id): return report_detail(request, report_id, form_complete=True) -def member_detail(request, user_id): +def user_detail(request, user_id): member = Member.objects.get(id=user_id) context = {"member": member} return render(request, 'fellchensammlung/details/detail-member.html', context=context)