feat: Add history tracking

This commit is contained in:
2025-11-16 18:49:01 +01:00
parent c529153373
commit c4da3318c2
5 changed files with 402 additions and 15 deletions

View File

@@ -41,7 +41,8 @@ dependencies = [
"django-super-deduper", "django-super-deduper",
"django-allauth[mfa]", "django-allauth[mfa]",
"django_debug_toolbar", "django_debug_toolbar",
"django-admin-extra-buttons" "django-admin-extra-buttons",
"django-simple-history"
] ]
dynamic = ["version", "readme"] dynamic = ["version", "readme"]

View File

@@ -8,6 +8,7 @@ from django.urls import reverse
from django.utils.http import urlencode from django.utils.http import urlencode
from admin_extra_buttons.api import ExtraButtonsMixin, button, link from admin_extra_buttons.api import ExtraButtonsMixin, button, link
from simple_history.admin import SimpleHistoryAdmin
from .models import Language, Text, ReportComment, ReportAdoptionNotice, Log, Timestamp, SearchSubscription, \ from .models import Language, Text, ReportComment, ReportAdoptionNotice, Log, Timestamp, SearchSubscription, \
SpeciesSpecificURL, ImportantLocation, SocialMediaPost SpeciesSpecificURL, ImportantLocation, SocialMediaPost
@@ -50,7 +51,7 @@ class AdoptionNoticeAdmin(admin.ModelAdmin):
# Re-register UserAdmin # Re-register UserAdmin
@admin.register(User) @admin.register(User)
class UserAdmin(admin.ModelAdmin): class UserAdmin(SimpleHistoryAdmin):
search_fields = ("usernamname__icontains", "first_name__icontains", "last_name__icontains", "email__icontains") search_fields = ("usernamname__icontains", "first_name__icontains", "last_name__icontains", "email__icontains")
list_display = ("username", "email", "trust_level", "is_active", "view_adoption_notices") list_display = ("username", "email", "trust_level", "is_active", "view_adoption_notices")
list_filter = ("is_active", "trust_level",) list_filter = ("is_active", "trust_level",)
@@ -78,7 +79,7 @@ def _reported_content_link(obj):
@admin.register(ReportComment) @admin.register(ReportComment)
class ReportCommentAdmin(admin.ModelAdmin): class ReportCommentAdmin(SimpleHistoryAdmin):
list_display = ["user_comment", "reported_content_link"] list_display = ["user_comment", "reported_content_link"]
date_hierarchy = "created_at" date_hierarchy = "created_at"
@@ -89,7 +90,7 @@ class ReportCommentAdmin(admin.ModelAdmin):
@admin.register(ReportAdoptionNotice) @admin.register(ReportAdoptionNotice)
class ReportAdoptionNoticeAdmin(admin.ModelAdmin): class ReportAdoptionNoticeAdmin(SimpleHistoryAdmin):
list_display = ["user_comment", "reported_content_link"] list_display = ["user_comment", "reported_content_link"]
date_hierarchy = "created_at" date_hierarchy = "created_at"
@@ -104,7 +105,7 @@ class SpeciesSpecificURLInline(admin.StackedInline):
@admin.register(RescueOrganization) @admin.register(RescueOrganization)
class RescueOrganizationAdmin(admin.ModelAdmin): class RescueOrganizationAdmin(SimpleHistoryAdmin):
search_fields = ("name", "description", "internal_comment", "location_string", "location__city") search_fields = ("name", "description", "internal_comment", "location_string", "location__city")
list_display = ("name", "trusted", "allows_using_materials", "website") list_display = ("name", "trusted", "allows_using_materials", "website")
list_filter = ("allows_using_materials", "trusted", ("external_source_identifier", EmptyFieldListFilter)) list_filter = ("allows_using_materials", "trusted", ("external_source_identifier", EmptyFieldListFilter))
@@ -115,12 +116,12 @@ class RescueOrganizationAdmin(admin.ModelAdmin):
@admin.register(Text) @admin.register(Text)
class TextAdmin(admin.ModelAdmin): class TextAdmin(SimpleHistoryAdmin):
search_fields = ("title__icontains", "text_code__icontains",) search_fields = ("title__icontains", "text_code__icontains",)
@admin.register(Comment) @admin.register(Comment)
class CommentAdmin(admin.ModelAdmin): class CommentAdmin(SimpleHistoryAdmin):
list_filter = ("user",) list_filter = ("user",)
@@ -130,7 +131,7 @@ class BaseNotificationAdmin(admin.ModelAdmin):
@admin.register(SearchSubscription) @admin.register(SearchSubscription)
class SearchSubscriptionAdmin(admin.ModelAdmin): class SearchSubscriptionAdmin(SimpleHistoryAdmin):
list_filter = ("owner",) list_filter = ("owner",)
@@ -158,7 +159,7 @@ class IsImportantListFilter(admin.SimpleListFilter):
@admin.register(Location) @admin.register(Location)
class LocationAdmin(admin.ModelAdmin): class LocationAdmin(SimpleHistoryAdmin):
search_fields = ("name__icontains", "city__icontains") search_fields = ("name__icontains", "city__icontains")
list_filter = [IsImportantListFilter] list_filter = [IsImportantListFilter]
inlines = [ inlines = [
@@ -167,7 +168,7 @@ class LocationAdmin(admin.ModelAdmin):
@admin.register(SocialMediaPost) @admin.register(SocialMediaPost)
class SocialMediaPostAdmin(admin.ModelAdmin): class SocialMediaPostAdmin(SimpleHistoryAdmin):
list_filter = ("platform",) list_filter = ("platform",)
@@ -193,12 +194,13 @@ class LogAdmin(ExtraButtonsMixin, admin.ModelAdmin):
def invisible(self, button): def invisible(self, button):
button.visible = False button.visible = False
admin.site.register(Animal)
admin.site.register(Animal, SimpleHistoryAdmin)
admin.site.register(Species) admin.site.register(Species)
admin.site.register(Rule) admin.site.register(Rule, SimpleHistoryAdmin)
admin.site.register(Image) admin.site.register(Image)
admin.site.register(ModerationAction) admin.site.register(ModerationAction, SimpleHistoryAdmin)
admin.site.register(Language) admin.site.register(Language)
admin.site.register(Announcement) admin.site.register(Announcement, SimpleHistoryAdmin)
admin.site.register(Subscriptions) admin.site.register(Subscriptions, SimpleHistoryAdmin)
admin.site.register(Timestamp) admin.site.register(Timestamp)

View File

@@ -0,0 +1,367 @@
# Generated by Django 5.2.8 on 2025-11-16 17:37
import django.contrib.auth.validators
import django.db.models.deletion
import django.utils.timezone
import simple_history.models
import uuid
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('fellchensammlung', '0070_user_mod_notes_alter_user_reason_for_signup'),
]
operations = [
migrations.CreateModel(
name='HistoricalAdoptionNotice',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('created_at', models.DateField(default=django.utils.timezone.now, verbose_name='Erstellt am')),
('updated_at', models.DateTimeField(blank=True, editable=False)),
('last_checked', models.DateTimeField(default=django.utils.timezone.now, verbose_name='Zuletzt überprüft am')),
('searching_since', models.DateField(verbose_name='Sucht nach einem Zuhause seit')),
('name', models.CharField(max_length=200, verbose_name='Titel der Vermittlung')),
('description', models.TextField(blank=True, null=True, verbose_name='Beschreibung')),
('further_information', models.URLField(blank=True, help_text='Verlinke hier die Quelle der Vermittlung (z.B. die Website des Tierheims)', 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')),
('adoption_notice_status', models.TextField(choices=[('active_searching', 'Searching'), ('active_interested', 'Interested'), ('awaiting_action_waiting_for_review', 'Waiting for review'), ('awaiting_action_needs_additional_info', 'Needs additional info'), ('awaiting_action_unchecked', 'Unchecked'), ('closed_successful_with_notfellchen', 'Successful (with Notfellchen)'), ('closed_successful_without_notfellchen', 'Successful (without Notfellchen)'), ('closed_animal_died', 'Animal died'), ('closed_for_other_adoption_notice', 'Closed for other adoption notice'), ('closed_not_open_for_adoption_anymore', 'Not open for adoption anymore'), ('closed_link_to_more_info_not_reachable', 'Der Link zu weiteren Informationen ist nicht mehr erreichbar.'), ('closed_other', 'Other (closed)'), ('disabled_against_the_rules', 'Against the rules'), ('disabled_other', 'Other (disabled)')], max_length=64, verbose_name='Status')),
('adoption_process', models.TextField(blank=True, choices=[('contact_person_in_an', 'Kontaktiere die Person im Vermittlungstext')], max_length=64, null=True, verbose_name='Adoptionsprozess')),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
('location', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.location')),
('organization', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.rescueorganization', verbose_name='Organisation')),
('owner', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Creator')),
],
options={
'verbose_name': 'historical Vermittlung',
'verbose_name_plural': 'historical Vermittlungen',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='HistoricalAnimal',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('date_of_birth', models.DateField(verbose_name='Geburtsdatum')),
('name', models.CharField(max_length=200, verbose_name='Name')),
('description', models.TextField(blank=True, null=True, verbose_name='Beschreibung')),
('sex', models.CharField(choices=[('F', 'Weiblich'), ('M', 'Männlich'), ('M_N', 'Männlich, kastriert'), ('F_N', 'Weiblich, kastriert'), ('I', 'Intergeschlechtlich')], max_length=20, verbose_name='Geschlecht')),
('updated_at', models.DateTimeField(blank=True, editable=False)),
('created_at', models.DateTimeField(blank=True, editable=False)),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('adoption_notice', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.adoptionnotice')),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
('owner', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL)),
('species', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.species', verbose_name='Tierart')),
],
options={
'verbose_name': 'historical Tier',
'verbose_name_plural': 'historical Tiere',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='HistoricalAnnouncement',
fields=[
('text_ptr', models.ForeignKey(auto_created=True, blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, parent_link=True, related_name='+', to='fellchensammlung.text')),
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('title', models.CharField(max_length=100, verbose_name='Titel')),
('content', models.TextField(verbose_name='Inhalt')),
('text_code', models.CharField(blank=True, max_length=24, verbose_name='Text code')),
('logged_in_only', models.BooleanField(default=False)),
('created_at', models.DateTimeField(blank=True, editable=False)),
('updated_at', models.DateTimeField(blank=True, editable=False)),
('publish_start_time', models.DateTimeField(verbose_name='Veröffentlichungszeitpunkt')),
('publish_end_time', models.DateTimeField(verbose_name='Veröffentlichungsende')),
('type', models.CharField(choices=[('important', 'important'), ('warning', 'warning'), ('info', 'info')], default='info', max_length=100)),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
('language', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.language', verbose_name='Sprache')),
],
options={
'verbose_name': 'historical Banner',
'verbose_name_plural': 'historical Banner',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='HistoricalComment',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('created_at', models.DateTimeField(blank=True, editable=False)),
('updated_at', models.DateTimeField(blank=True, editable=False)),
('text', models.TextField(verbose_name='Inhalt')),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('adoption_notice', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.adoptionnotice', verbose_name='Vermittlung')),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
('reply_to', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.comment', verbose_name='Antwort auf')),
('user', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Nutzer*in')),
],
options={
'verbose_name': 'historical Kommentar',
'verbose_name_plural': 'historical Kommentare',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='HistoricalModerationAction',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('action', models.CharField(choices=[('user_banned', 'User was banned'), ('content_deleted', 'Content was deleted'), ('comment', 'Comment was added'), ('other_action_taken', 'Other action was taken'), ('no_action_taken', 'No action was taken')], max_length=30)),
('created_at', models.DateTimeField(blank=True, editable=False)),
('updated_at', models.DateTimeField(blank=True, editable=False)),
('public_comment', models.TextField(blank=True)),
('private_comment', models.TextField(blank=True)),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
('report', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.report')),
],
options={
'verbose_name': 'historical Moderationsaktion',
'verbose_name_plural': 'historical Moderationsaktionen',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='HistoricalReport',
fields=[
('id', models.UUIDField(db_index=True, default=uuid.uuid4, help_text='ID dieses reports', verbose_name='ID')),
('status', models.CharField(choices=[('action taken', 'Action was taken'), ('no action taken', 'No action was taken'), ('waiting', 'Waiting for moderator action')], max_length=30)),
('user_comment', models.TextField(blank=True, verbose_name='Kommentar/Zusätzliche Information')),
('updated_at', models.DateTimeField(blank=True, editable=False, verbose_name='Zuletzt geändert am')),
('created_at', models.DateTimeField(blank=True, editable=False, verbose_name='Erstellt am')),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'historical Meldung',
'verbose_name_plural': 'historical Meldungen',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='HistoricalRescueOrganization',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('name', models.CharField(max_length=200)),
('trusted', models.BooleanField(default=False, verbose_name='Vertrauenswürdig')),
('allows_using_materials', models.CharField(choices=[('allowed', 'Usage allowed'), ('requested', 'Usage requested'), ('denied', 'Usage denied'), ('other', "It's complicated"), ('not_asked', 'Not asked')], default='not_asked', max_length=200, verbose_name='Erlaubt Nutzung von Inhalten')),
('location_string', models.CharField(blank=True, max_length=200, null=True, verbose_name='Ort der Organisation')),
('instagram', models.URLField(blank=True, null=True, verbose_name='Instagram Profil')),
('facebook', models.URLField(blank=True, null=True, verbose_name='Facebook Profil')),
('fediverse_profile', models.URLField(blank=True, null=True, verbose_name='Fediverse Profil')),
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='E-Mail')),
('phone_number', models.CharField(blank=True, max_length=15, null=True, verbose_name='Telefonnummer')),
('website', models.URLField(blank=True, null=True, verbose_name='Website')),
('updated_at', models.DateTimeField(blank=True, editable=False)),
('created_at', models.DateTimeField(blank=True, editable=False)),
('last_checked', models.DateTimeField(blank=True, editable=False, verbose_name='Datum der letzten Prüfung')),
('internal_comment', models.TextField(blank=True, null=True, verbose_name='Interner Kommentar')),
('description', models.TextField(blank=True, null=True, verbose_name='Beschreibung')),
('external_object_identifier', models.CharField(blank=True, max_length=200, null=True, verbose_name='External Object Identifier')),
('external_source_identifier', models.CharField(blank=True, choices=[('OSM', 'Open Street Map')], max_length=200, null=True, verbose_name='External Source Identifier')),
('exclude_from_check', models.BooleanField(default=False, help_text='Organisation von der manuellen Überprüfung ausschließen, z.B. weil Tiere nicht online geführt werden', verbose_name='Von Prüfung ausschließen')),
('regular_check_status', models.CharField(choices=[('regular_check', 'Wird regelmäßig geprüft'), ('excluded_no_online_listing', 'Exkludiert: Tiere werden nicht online gelistet'), ('excluded_other_org', 'Exkludiert: Andere Organisation wird geprüft'), ('excluded_scope', 'Exkludiert: Organisation hat nie Notfellchen-relevanten Vermittlungen'), ('excluded_other', 'Exkludiert: Anderer Grund')], default='regular_check', help_text='Organisationen können, durch ändern dieser Einstellung, von der regelmäßigen Prüfung ausgeschlossen werden.', max_length=30, verbose_name='Status der regelmäßigen Prüfung')),
('ongoing_communication', models.BooleanField(default=False, help_text='Es findet gerade Kommunikation zwischen Notfellchen und der Organisation statt.', verbose_name='In aktiver Kommunikation')),
('twenty_id', models.UUIDField(blank=True, help_text='ID der der Organisation in Twenty', null=True, verbose_name='Twenty-ID')),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
('location', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.location')),
('parent_org', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.rescueorganization')),
],
options={
'verbose_name': 'historical Tierschutzorganisation',
'verbose_name_plural': 'historical Tierschutzorganisationen',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='HistoricalRule',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('title', models.CharField(max_length=200)),
('rule_text', models.TextField(verbose_name='Regeltext')),
('rule_identifier', models.CharField(help_text='Ein eindeutiger Identifikator der Regel. Ein Regelobjekt derselben Regel in einer anderen Sprache muss den gleichen Identifikator haben', max_length=24, verbose_name='Regel-ID')),
('updated_at', models.DateTimeField(blank=True, editable=False, verbose_name='Zuletzt geändert am')),
('created_at', models.DateTimeField(blank=True, editable=False, verbose_name='Erstellt am')),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
('language', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.language', verbose_name='Sprache')),
],
options={
'verbose_name': 'historical Regel',
'verbose_name_plural': 'historical Regeln',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='HistoricalSearchSubscription',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('sex', models.CharField(choices=[('F', 'Weiblich'), ('M', 'Männlich'), ('M_N', 'Männlich, kastriert'), ('F_N', 'Weiblich Kastriert'), ('I', 'Intergeschlechtlich'), ('A', 'Alle')], max_length=20, verbose_name='Geschlecht')),
('max_distance', models.IntegerField(choices=[(20, '20 km'), (50, '50 km'), (100, '100 km'), (200, '200 km'), (500, '500 km')], null=True)),
('updated_at', models.DateTimeField(blank=True, editable=False, verbose_name='Zuletzt geändert am')),
('created_at', models.DateTimeField(blank=True, editable=False, verbose_name='Erstellt am')),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
('location', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.location')),
('owner', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'historical Abonnierte Suche',
'verbose_name_plural': 'historical Abonnierte Suchen',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='HistoricalSocialMediaPost',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('created_at', models.DateField(default=django.utils.timezone.now, verbose_name='Erstellt am')),
('platform', models.CharField(choices=[('fediverse', 'Fediverse')], max_length=255, verbose_name='Social Media Platform')),
('url', models.URLField(verbose_name='URL')),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('adoption_notice', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.adoptionnotice', verbose_name='Vermittlung')),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'historical social media post',
'verbose_name_plural': 'historical social media posts',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='HistoricalSubscriptions',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('created_at', models.DateTimeField(blank=True, editable=False)),
('updated_at', models.DateTimeField(blank=True, editable=False)),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('adoption_notice', models.ForeignKey(blank=True, db_constraint=False, help_text='Vermittlung die abonniert wurde', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.adoptionnotice', verbose_name='Vermittlung')),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
('owner', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Nutzer*in')),
],
options={
'verbose_name': 'historical Abonnement',
'verbose_name_plural': 'historical Abonnements',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='HistoricalText',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('title', models.CharField(max_length=100, verbose_name='Titel')),
('content', models.TextField(verbose_name='Inhalt')),
('text_code', models.CharField(blank=True, max_length=24, verbose_name='Text code')),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
('language', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.language', verbose_name='Sprache')),
],
options={
'verbose_name': 'historical Text',
'verbose_name_plural': 'historical Texte',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
migrations.CreateModel(
name='HistoricalUser',
fields=[
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(db_index=True, error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('trust_level', models.IntegerField(choices=[(1, 'Member'), (2, 'Coordinator'), (3, 'Moderator'), (4, 'Admin')], default=1)),
('updated_at', models.DateTimeField(blank=True, editable=False)),
('reason_for_signup', models.TextField(help_text="Wir würden gerne wissen warum du dich registrierst, ob du dich z.B. Tiere eines bestimmten Tierheim einstellen willst 'nur mal gucken' willst. Beides ist toll! Wenn du für ein Tierheim/eine Pflegestelle arbeitest kontaktieren wir dich ggf. um dir erweiterte Rechte zu geben.", verbose_name='Grund für die Registrierung')),
('mod_notes', models.TextField(blank=True, null=True, verbose_name='Moderationsnotizen')),
('email_notifications', models.BooleanField(default=True, verbose_name='Benachrichtigung per E-Mail')),
('history_id', models.AutoField(primary_key=True, serialize=False)),
('history_date', models.DateTimeField(db_index=True)),
('history_change_reason', models.CharField(max_length=100, null=True)),
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
('organization_affiliation', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.rescueorganization', verbose_name='Organisation')),
('preferred_language', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='fellchensammlung.language', verbose_name='Bevorzugte Sprache')),
],
options={
'verbose_name': 'historical Nutzer*in',
'verbose_name_plural': 'historical Nutzer*innen',
'ordering': ('-history_date', '-history_id'),
'get_latest_by': ('history_date', 'history_id'),
},
bases=(simple_history.models.HistoricalChanges, models.Model),
),
]

View File

@@ -7,6 +7,7 @@ from django.contrib.auth.models import Group
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
import base64 import base64
from simple_history.models import HistoricalRecords
from .tools import misc, geo from .tools import misc, geo
from notfellchen.settings import MEDIA_URL, base_url from notfellchen.settings import MEDIA_URL, base_url
@@ -187,6 +188,7 @@ class RescueOrganization(models.Model):
specializations = models.ManyToManyField(Species, blank=True) specializations = models.ManyToManyField(Species, blank=True)
twenty_id = models.UUIDField(verbose_name=_("Twenty-ID"), null=True, blank=True, twenty_id = models.UUIDField(verbose_name=_("Twenty-ID"), null=True, blank=True,
help_text=_("ID der der Organisation in Twenty")) help_text=_("ID der der Organisation in Twenty"))
history = HistoricalRecords()
class Meta: class Meta:
unique_together = ('external_object_identifier', 'external_source_identifier',) unique_together = ('external_object_identifier', 'external_source_identifier',)
@@ -250,6 +252,7 @@ class RescueOrganization(models.Model):
def set_checked(self): def set_checked(self):
self.last_checked = timezone.now() self.last_checked = timezone.now()
self._change_reason = 'Organization checked'
self.save() self.save()
@property @property
@@ -311,6 +314,7 @@ class User(AbstractUser):
reason_for_signup = models.TextField(verbose_name=reason_for_signup_label, help_text=reason_for_signup_help_text) reason_for_signup = models.TextField(verbose_name=reason_for_signup_label, help_text=reason_for_signup_help_text)
mod_notes = models.TextField(verbose_name=_("Moderationsnotizen"), null=True, blank=True) mod_notes = models.TextField(verbose_name=_("Moderationsnotizen"), null=True, blank=True)
email_notifications = models.BooleanField(verbose_name=_("Benachrichtigung per E-Mail"), default=True) email_notifications = models.BooleanField(verbose_name=_("Benachrichtigung per E-Mail"), default=True)
history = HistoricalRecords()
REQUIRED_FIELDS = ["reason_for_signup", "email"] REQUIRED_FIELDS = ["reason_for_signup", "email"]
class Meta: class Meta:
@@ -406,6 +410,7 @@ class AdoptionNotice(models.Model):
adoption_process = models.TextField(null=True, blank=True, adoption_process = models.TextField(null=True, blank=True,
max_length=64, verbose_name=_('Adoptionsprozess'), max_length=64, verbose_name=_('Adoptionsprozess'),
choices=AdoptionProcess) choices=AdoptionProcess)
history = HistoricalRecords()
@property @property
def animals(self): def animals(self):
@@ -641,6 +646,7 @@ class Animal(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE) owner = models.ForeignKey(User, on_delete=models.CASCADE)
updated_at = models.DateTimeField(auto_now=True) updated_at = models.DateTimeField(auto_now=True)
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
history = HistoricalRecords()
def __str__(self): def __str__(self):
return f"{self.name}" return f"{self.name}"
@@ -704,6 +710,7 @@ class SearchSubscription(models.Model):
max_distance = models.IntegerField(choices=DistanceChoices.choices, null=True) max_distance = models.IntegerField(choices=DistanceChoices.choices, null=True)
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Zuletzt geändert am")) updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Zuletzt geändert am"))
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Erstellt am")) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Erstellt am"))
history = HistoricalRecords()
def __str__(self): def __str__(self):
if self.location and self.max_distance: if self.location and self.max_distance:
@@ -734,6 +741,7 @@ class Rule(models.Model):
"Identifikator haben")) "Identifikator haben"))
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Zuletzt geändert am")) updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Zuletzt geändert am"))
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Erstellt am")) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Erstellt am"))
history = HistoricalRecords()
def __str__(self): def __str__(self):
return self.title return self.title
@@ -759,6 +767,7 @@ class Report(models.Model):
user_comment = models.TextField(blank=True, verbose_name=_("Kommentar/Zusätzliche Information")) user_comment = models.TextField(blank=True, verbose_name=_("Kommentar/Zusätzliche Information"))
updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Zuletzt geändert am")) updated_at = models.DateTimeField(auto_now=True, verbose_name=_("Zuletzt geändert am"))
created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Erstellt am")) created_at = models.DateTimeField(auto_now_add=True, verbose_name=_("Erstellt am"))
history = HistoricalRecords()
def __str__(self): def __str__(self):
return f"[{self.status}]: {self.user_comment:.20}" return f"[{self.status}]: {self.user_comment:.20}"
@@ -845,6 +854,7 @@ class ModerationAction(models.Model):
# Only visible to moderator # Only visible to moderator
private_comment = models.TextField(blank=True) private_comment = models.TextField(blank=True)
report = models.ForeignKey(Report, on_delete=models.CASCADE) report = models.ForeignKey(Report, on_delete=models.CASCADE)
history = HistoricalRecords()
def __str__(self): def __str__(self):
return f"[{self.action}]: {self.public_comment}" return f"[{self.action}]: {self.public_comment}"
@@ -866,6 +876,7 @@ class Text(models.Model):
content = models.TextField(verbose_name="Inhalt") content = models.TextField(verbose_name="Inhalt")
language = models.ForeignKey(Language, verbose_name="Sprache", on_delete=models.PROTECT) language = models.ForeignKey(Language, verbose_name="Sprache", on_delete=models.PROTECT)
text_code = models.CharField(max_length=24, verbose_name="Text code", blank=True) text_code = models.CharField(max_length=24, verbose_name="Text code", blank=True)
history = HistoricalRecords()
class Meta: class Meta:
verbose_name = "Text" verbose_name = "Text"
@@ -909,6 +920,7 @@ class Announcement(Text):
INFO: "info", INFO: "info",
} }
type = models.CharField(choices=TYPES, max_length=100, default=INFO) type = models.CharField(choices=TYPES, max_length=100, default=INFO)
history = HistoricalRecords()
@property @property
def is_active(self): def is_active(self):
@@ -955,6 +967,7 @@ class Comment(models.Model):
adoption_notice = models.ForeignKey(AdoptionNotice, on_delete=models.CASCADE, verbose_name=_('Vermittlung')) adoption_notice = models.ForeignKey(AdoptionNotice, on_delete=models.CASCADE, verbose_name=_('Vermittlung'))
text = models.TextField(verbose_name="Inhalt") text = models.TextField(verbose_name="Inhalt")
reply_to = models.ForeignKey("self", verbose_name="Antwort auf", blank=True, null=True, on_delete=models.CASCADE) reply_to = models.ForeignKey("self", verbose_name="Antwort auf", blank=True, null=True, on_delete=models.CASCADE)
history = HistoricalRecords()
def __str__(self): def __str__(self):
return f"{self.user} at {self.created_at.strftime('%H:%M %d.%m.%y')}: {self.text:.10}" return f"{self.user} at {self.created_at.strftime('%H:%M %d.%m.%y')}: {self.text:.10}"
@@ -1026,6 +1039,7 @@ class Subscriptions(models.Model):
help_text=_("Vermittlung die abonniert wurde")) help_text=_("Vermittlung die abonniert wurde"))
created_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True) updated_at = models.DateTimeField(auto_now=True)
history = HistoricalRecords()
def __str__(self): def __str__(self):
return f"{self.owner} - {self.adoption_notice}" return f"{self.owner} - {self.adoption_notice}"
@@ -1087,6 +1101,7 @@ class SocialMediaPost(models.Model):
choices=PlatformChoices.choices) choices=PlatformChoices.choices)
adoption_notice = models.ForeignKey(AdoptionNotice, on_delete=models.CASCADE, verbose_name=_('Vermittlung')) adoption_notice = models.ForeignKey(AdoptionNotice, on_delete=models.CASCADE, verbose_name=_('Vermittlung'))
url = models.URLField(verbose_name=_("URL")) url = models.URLField(verbose_name=_("URL"))
history = HistoricalRecords()
@staticmethod @staticmethod
def get_an_to_post(): def get_an_to_post():

View File

@@ -237,6 +237,7 @@ INSTALLED_APPS = [
'widget_tweaks', 'widget_tweaks',
"debug_toolbar", "debug_toolbar",
'admin_extra_buttons', 'admin_extra_buttons',
'simple_history',
] ]
MIDDLEWARE = [ MIDDLEWARE = [
@@ -254,6 +255,7 @@ MIDDLEWARE = [
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
# allauth middleware, needs to be after message middleware # allauth middleware, needs to be after message middleware
"allauth.account.middleware.AccountMiddleware", "allauth.account.middleware.AccountMiddleware",
'simple_history.middleware.HistoryRequestMiddleware',
] ]
ROOT_URLCONF = 'notfellchen.urls' ROOT_URLCONF = 'notfellchen.urls'