Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Audit: all models are being tracked by PG history #14

Merged
merged 87 commits into from
Apr 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
a861895
install pghistory
xjlin0 Feb 23, 2022
db9536d
add created/modified/is_removed default at database level by SQL
xjlin0 Feb 23, 2022
e02aa99
attendee model now have history tracked by db triggers
xjlin0 Feb 24, 2022
a0dd279
add table comment to remove pk/id column which is pgh_obj_id
xjlin0 Feb 25, 2022
6f5f855
id still required for updating model instance
xjlin0 Feb 25, 2022
4d1e1e3
revert back to models.UUIDField on model itself
xjlin0 Feb 25, 2022
7ab8af7
revert history table name back to event to match pghistory's method name
xjlin0 Feb 26, 2022
bd30538
resolve conflics
xjlin0 Feb 26, 2022
08e7818
fix partial date saving error from Django Admin
xjlin0 Feb 26, 2022
4c080bf
update django address and use bigint on it
xjlin0 Mar 1, 2022
9c1dae4
add table checking step for manage task update_content_types
xjlin0 Mar 1, 2022
37c207f
draft of user history
xjlin0 Mar 5, 2022
8a22b3a
add user_groups and user_permissions event tables
xjlin0 Mar 6, 2022
04f00e3
add a test to limit model name length for pg-trigger names
xjlin0 Mar 6, 2022
061df3f
add ccontext for celery task and access csv importer
xjlin0 Mar 6, 2022
c7a8ca4
add my own django-db-comments for labeling database tables
xjlin0 Mar 7, 2022
94215a8
upgrade version of db-comment
xjlin0 Apr 4, 2022
f57be9e
user history diff in admin successfully shows
xjlin0 Apr 6, 2022
7efa1f5
add history view for attendee, style buttons and show user id in hist…
xjlin0 Apr 6, 2022
6c617fc
add auth.Group in pg history
xjlin0 Apr 8, 2022
22a7d79
tracking group_permissions by pg history
xjlin0 Apr 8, 2022
32c83bb
track schedule Event model by PG history
xjlin0 Apr 9, 2022
2b5938f
update django pghistory version, tack event relation in History name
xjlin0 Apr 9, 2022
aa756b8
change event's event to event's history
xjlin0 Apr 9, 2022
306ea9c
rename migration file to match model name
xjlin0 Apr 9, 2022
0b731dd
rename GroupPermissionsEvent to GroupPermissionsHistory
xjlin0 Apr 9, 2022
8599fc7
rename GroupEvent to GroupHistory
xjlin0 Apr 9, 2022
d0080d2
rename UserPermissionEvent to UserPermissionHistory
xjlin0 Apr 9, 2022
f59cb7d
rename GroupPermissionsHistory to GroupPermissionHistory
xjlin0 Apr 9, 2022
b928837
rename 0007_grouphistory to 0007_group_history
xjlin0 Apr 9, 2022
c6ab570
rename UserGroupEvent to UserGroupHistory
xjlin0 Apr 9, 2022
5b8b87a
change UserEvent to UserHistory
xjlin0 Apr 9, 2022
45a982c
rename AttendeesEvent to AttendeeHistory for consistancy
xjlin0 Apr 9, 2022
cde4bcd
rename AttendeeHistory back to AttendeesHistory to match attendees ta…
xjlin0 Apr 9, 2022
b6747b4
rename migration file to make it easier to read
xjlin0 Apr 9, 2022
a8c9af2
add table comment seems overwritten by django-db-comments
xjlin0 Apr 9, 2022
225586b
rename UserGroupHistory to UserGroupsHistory for consistancy
xjlin0 Apr 9, 2022
a5d514b
UserHistory cannot be UsersHistory due to original table was created …
xjlin0 Apr 9, 2022
2a957d3
track category by pghistory now
xjlin0 Apr 9, 2022
553fbc5
now track Note by pghistory
xjlin0 Apr 9, 2022
a4db9fc
track Past with pg history now
xjlin0 Apr 10, 2022
88062a1
track Past with pg history now with Admin
xjlin0 Apr 10, 2022
32dfd0f
fix past data issues, may break again along adding pg history models
xjlin0 Apr 10, 2022
1d5fb98
now tracking schedule's Calendar wth p history
xjlin0 Apr 10, 2022
e0b0b01
schedule calendar relation model now tracked by pghistory too
xjlin0 Apr 10, 2022
e6e825f
track account's email address by pghistory
xjlin0 Apr 10, 2022
18a6d35
track email confirmation by pg history. Currently there's no records …
xjlin0 Apr 10, 2022
2c24bcc
divert to upgrade django address
xjlin0 Apr 10, 2022
7033a82
tracked address country by pghistory
xjlin0 Apr 11, 2022
cce1392
now tracked state by pghistory
xjlin0 Apr 11, 2022
37fbca9
tracked locality history by pghistory now
xjlin0 Apr 11, 2022
f2ac05d
tracked address by pghistory, noticed that django address id in histo…
xjlin0 Apr 11, 2022
ed59747
tracked menu by pghistory
xjlin0 Apr 11, 2022
21c8bc5
track menu_auth_group by django pghistory now
xjlin0 Apr 11, 2022
8cbe075
now tracking organization by django pghistory
xjlin0 Apr 11, 2022
5d75394
tracking division by django pghistory
xjlin0 Apr 11, 2022
7c93ff3
add campus to be tracked by pghistory
xjlin0 Apr 11, 2022
71aa5f3
track property with PG history now
xjlin0 Apr 16, 2022
8c81d67
track SuitesHistory by PG history now
xjlin0 Apr 16, 2022
d5ce566
track room by PG history now
xjlin0 Apr 16, 2022
6bfb68a
fix room migration
xjlin0 Apr 16, 2022
56ca3bd
track place by PG history
xjlin0 Apr 16, 2022
7d892ae
make room object id to uuid compatible, also applied permissions on m…
xjlin0 Apr 16, 2022
1279152
make room object id to uuid compatible, also applied permissions on m…
xjlin0 Apr 16, 2022
bbc914a
tracked MessageTemplate by PG history now
xjlin0 Apr 16, 2022
7f55921
changing primary id to BigIntegerField in history model
xjlin0 Apr 16, 2022
f1e739a
remove Assembly model column 'need_age' and track its history by PG h…
xjlin0 Apr 17, 2022
694a808
tracked Price model by PG history now
xjlin0 Apr 17, 2022
b5329f8
tracked character model by PG history now
xjlin0 Apr 17, 2022
86dc6d9
tracked meet model with PG history
xjlin0 Apr 17, 2022
cdc6fed
track model gathering with PG history
xjlin0 Apr 17, 2022
e1321d4
making pgh_id BigAutoField instead of AutoField
xjlin0 Apr 17, 2022
dbb9c42
now tracked Team model by PG history
xjlin0 Apr 17, 2022
b425963
tracking attendance with PG history now
xjlin0 Apr 17, 2022
7a4de9e
tracked Relations by PG history, also make pk index at history db lev…
xjlin0 Apr 17, 2022
d49fc2b
move index on pk to the migration files instead of raw SQL unless nec…
xjlin0 Apr 17, 2022
1bbf0b5
tracked Folk model by PG history, also make attenee uuid indexed
xjlin0 Apr 18, 2022
60e8c07
tracking registration by PG history and change its assembly to non-null
xjlin0 Apr 18, 2022
67952f7
add price to Attending model and tracked by PG history
xjlin0 Apr 18, 2022
5c55cfa
add price to Attending model and tracked by PG history
xjlin0 Apr 18, 2022
a43c0b3
track AttendingMeet by PG history now
xjlin0 Apr 18, 2022
73f195f
tracked FolkAttendeesHistory by PG history finally
xjlin0 Apr 18, 2022
c4a5eb8
update seed data to match content type# after PG history
xjlin0 Apr 18, 2022
62c5ee2
fix add more contact button position
xjlin0 Apr 18, 2022
6a07ece
resolve merge conflict
xjlin0 Apr 19, 2022
d29e067
update PR info
xjlin0 Apr 19, 2022
42e81ea
clean up some model def
xjlin0 Apr 19, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,8 +231,10 @@ [email protected]
</details>

## [How to start dev env on Linux](https://cookiecutter-django.readthedocs.io/en/latest/developing-locally-docker.html)

<details>
<summary>Click to expand all</summary>

* double check if the dev port 8008 is open on fire wall
* add server's public ip to ALLOWED_HOSTS in settings
* install docker and docker-compose, such as `sudo apt install docker docker-compose`
Expand All @@ -252,6 +254,7 @@ [email protected]
* create 2 superusers by `docker-compose -f local.yml run django python manage.py createsuperuser`
* import the seed data by `docker-compose -f local.yml run django python manage.py loaddata fixtures/db_seed`
* go to Django admin to add the first organization and all groups to the first user (superuser) at http://<<your domain name>>:8008/admin/users/user/

</details>

## [How to start dev env on Windows](https://cookiecutter-django.readthedocs.io/en/latest/developing-locally-docker.html)
Expand Down Expand Up @@ -305,7 +308,7 @@ [email protected]
* upadte content types after migration by `docker-compose -f local.yml run django python manage.py update_content_types`
* create 2 superusers by `docker-compose -f local.yml run --rm django python manage.py createsuperuser`
* import the seed data by `docker-compose -f local.yml run django python manage.py loaddata fixtures/db_seed`
(data were created by `docker-compose -f local.yml run django python manage.py dumpdata --exclude users.user --exclude admin.logentry --exclude sessions.session --exclude contenttypes.contenttype --exclude sites.site --exclude account.emailaddress --exclude account.emailconfirmation --exclude socialaccount.socialtoken --exclude auth.permission --indent 2 > fixtures/db_seed2.json`)
(data were created by `docker-compose -f local.yml run django python manage.py dumpdata --exclude users.user --exclude admin.logentry --exclude sessions.session --exclude contenttypes.contenttype --exclude sites.site --exclude account.emailaddress --exclude account.emailconfirmation --exclude socialaccount.socialtoken --exclude auth.permission --exclude pghistory.context --exclude pghistory.aggregateevent --indent 2 > fixtures/db_seed2.json`)
* go to Django admin to add the first organization and all groups to the first user (superuser) at http://192.168.99.100:8008/admin123/users/user/
* use browser to open http://192.168.99.100:8008/ and http://192.168.99.100:8025/
* Enter postgres db console by `docker-compose -f local.yml exec postgres psql --username=YBIJMKerEaNYKqzfvMxOlBAesdyiahxk attendees_development`
Expand Down Expand Up @@ -388,9 +391,9 @@ All libraries are included to facilitate offline development, it will take port
- [ ] Division specific menu links, such as including selected meets in the search params
- [ ] Junior
- [ ] Data
- [ ] Audit log/history/vision of data
- [ ] find library and install: maybe django-pghistory with AggregateEvent
- [ ] each model level version
- [x] [2PR#14](https://github.com/xjlin0/attendees32/pull/14) Audit log/history/vision of data
- [x] find library and install: maybe django-pghistory with AggregateEvent
- [x] each model level version
- [ ] document aggregation level version
- [x] [New repo] upgrade to Django 3.2LTS for support of DEFAULT_AUTO_FIELD
-[x] accept partial date on all attending/past, etc django-date-extensions or django_partial_date. Also use Javascript solution to make yearless date back to 1800, so birthday of "1999 August" will be 08-01-1999 and "May 24th" will be 05-24-1800
Expand Down
164 changes: 151 additions & 13 deletions attendees/occasions/admin.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@

from django.contrib import messages
from django.contrib import admin
from django.contrib.postgres import fields

from django_json_widget.widgets import JSONEditorWidget
from attendees.occasions.models import Attendance, MessageTemplate, Assembly, Price, Character, Meet, Gathering, Team
from attendees.persons.models import PgHistoryPage
from attendees.whereabouts.models import Organization, Division


# Register your models here.
Expand All @@ -19,7 +21,7 @@
# extra = 0


class MessageTemplateAdmin(admin.ModelAdmin):
class MessageTemplateAdmin(PgHistoryPage, admin.ModelAdmin):
formfield_overrides = {
fields.JSONField: {"widget": JSONEditorWidget},
}
Expand All @@ -29,8 +31,25 @@ class MessageTemplateAdmin(admin.ModelAdmin):
def truncate_template(self, obj):
return list(obj.templates.values())[0][:100] + "..."


class AssemblyAdmin(admin.ModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "organization":
kwargs["queryset"] = Organization.objects.all() if request.user.is_superuser else Organization.objects.filter(pk=request.user.organization_pk())
return super().formfield_for_foreignkey(db_field, request, **kwargs)

def get_queryset(self, request):
qs = super().get_queryset(request)
if request.user.is_superuser:
return qs
else:
if request.resolver_match.func.__name__ == "changelist_view":
messages.warning(
request,
"Not all, but only those records accessible to you will be listed here.",
)
return qs.filter(organization=request.user.organization)


class AssemblyAdmin(PgHistoryPage, admin.ModelAdmin):
formfield_overrides = {
fields.JSONField: {"widget": JSONEditorWidget},
}
Expand All @@ -40,12 +59,46 @@ class AssemblyAdmin(admin.ModelAdmin):
list_display = ("id", "division", "display_name", "slug", "get_addresses")
readonly_fields = ["id", "created", "modified"]


class PriceAdmin(admin.ModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "division":
kwargs["queryset"] = Division.objects.all() if request.user.is_superuser else Division.objects.filter(organization=request.user.organization)
return super().formfield_for_foreignkey(db_field, request, **kwargs)

def get_queryset(self, request):
qs = super().get_queryset(request)
if request.user.is_superuser:
return qs
else:
if request.resolver_match.func.__name__ == "changelist_view":
messages.warning(
request,
"Not all, but only those records accessible to you will be listed here.",
)
return qs.filter(division__organization=request.user.organization)


class PriceAdmin(PgHistoryPage, admin.ModelAdmin):
list_display = ("display_name", "price_type", "start", "price_value", "modified")


class AttendanceAdmin(admin.ModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "assembly":
kwargs["queryset"] = Assembly.objects.all() if request.user.is_superuser else Assembly.objects.filter(division__organization=request.user.organization)
return super().formfield_for_foreignkey(db_field, request, **kwargs)

def get_queryset(self, request):
qs = super().get_queryset(request)
if request.user.is_superuser:
return qs
else:
if request.resolver_match.func.__name__ == "changelist_view":
messages.warning(
request,
"Not all, but only those records accessible to you will be listed here.",
)
return qs.filter(assembly__division__organization=request.user.organization)


class AttendanceAdmin(PgHistoryPage, admin.ModelAdmin):
list_display_links = ("get_attendee",)
list_filter = (
("gathering", admin.RelatedOnlyFieldListFilter),
Expand Down Expand Up @@ -77,6 +130,23 @@ class AttendanceAdmin(admin.ModelAdmin):
),
)

def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "gathering":
kwargs["queryset"] = Gathering.objects.all() if request.user.is_superuser else Gathering.objects.filter(meet__assembly__division__organization=request.user.organization)
return super().formfield_for_foreignkey(db_field, request, **kwargs)

def get_queryset(self, request):
qs = super().get_queryset(request)
if request.user.is_superuser:
return qs
else:
if request.resolver_match.func.__name__ == "changelist_view":
messages.warning(
request,
"Not all, but only those records accessible to you will be listed here.",
)
return qs.filter(gathering__meet__assembly__division__organization=request.user.organization)

def get_attendee(self, obj):
return obj.attending.attendee

Expand Down Expand Up @@ -109,26 +179,60 @@ class AttendanceInline(admin.StackedInline):
# return qs[:10] # does not work


class CharacterAdmin(admin.ModelAdmin):
class CharacterAdmin(PgHistoryPage, admin.ModelAdmin):
prepopulated_fields = {"slug": ("display_name",)}
list_filter = ("assembly", "type")
readonly_fields = ["id", "created", "modified"]
list_display_links = ("display_name",)
list_display = ("id", "assembly", "display_name", "slug", "display_order", "type")

def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "assembly":
kwargs["queryset"] = Assembly.objects.all() if request.user.is_superuser else Assembly.objects.filter(division__organization=request.user.organization)
return super().formfield_for_foreignkey(db_field, request, **kwargs)

def get_queryset(self, request):
qs = super().get_queryset(request)
if request.user.is_superuser:
return qs
else:
if request.resolver_match.func.__name__ == "changelist_view":
messages.warning(
request,
"Not all, but only those records accessible to you will be listed here.",
)
return qs.filter(assembly__division__organization=request.user.organization)

class Media:
css = {"all": ("css/admin.css",)}
js = ["js/admin/list_filter_collapse.js"]


class TeamAdmin(admin.ModelAdmin):
class TeamAdmin(PgHistoryPage, admin.ModelAdmin):
prepopulated_fields = {"slug": ("display_name",)}
readonly_fields = ["id", "created", "modified"]
list_display_links = ("display_name",)
list_display = ("id", "display_name", "slug", "meet", "display_order", "modified")


class MeetAdmin(admin.ModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "meet":
kwargs["queryset"] = Meet.objects.all() if request.user.is_superuser else Meet.objects.filter(assembly__division__organization=request.user.organization)
return super().formfield_for_foreignkey(db_field, request, **kwargs)

def get_queryset(self, request):
qs = super().get_queryset(request)
if request.user.is_superuser:
return qs
else:
if request.resolver_match.func.__name__ == "changelist_view":
messages.warning(
request,
"Not all, but only those records accessible to you will be listed here.",
)
return qs.filter(meet__assembly__division__organization=request.user.organization)


class MeetAdmin(PgHistoryPage, admin.ModelAdmin):
formfield_overrides = {
fields.JSONField: {"widget": JSONEditorWidget},
}
Expand Down Expand Up @@ -161,12 +265,29 @@ class MeetAdmin(admin.ModelAdmin):
),
)

def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "assembly":
kwargs["queryset"] = Assembly.objects.all() if request.user.is_superuser else Assembly.objects.filter(division__organization=request.user.organization)
return super().formfield_for_foreignkey(db_field, request, **kwargs)

def get_queryset(self, request):
qs = super().get_queryset(request)
if request.user.is_superuser:
return qs
else:
if request.resolver_match.func.__name__ == "changelist_view":
messages.warning(
request,
"Not all, but only those records accessible to you will be listed here.",
)
return qs.filter(assembly__division__organization=request.user.organization)

class Media:
css = {"all": ("css/admin.css",)}
js = ["js/admin/list_filter_collapse.js"]


class GatheringAdmin(admin.ModelAdmin):
class GatheringAdmin(PgHistoryPage, admin.ModelAdmin):
formfield_overrides = {
fields.JSONField: {"widget": JSONEditorWidget},
}
Expand All @@ -190,6 +311,23 @@ class GatheringAdmin(admin.ModelAdmin):
),
)

def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "meet":
kwargs["queryset"] = Meet.objects.all() if request.user.is_superuser else Meet.objects.filter(assembly__division__organization=request.user.organization)
return super().formfield_for_foreignkey(db_field, request, **kwargs)

def get_queryset(self, request):
qs = super().get_queryset(request)
if request.user.is_superuser:
return qs
else:
if request.resolver_match.func.__name__ == "changelist_view":
messages.warning(
request,
"Not all, but only those records accessible to you will be listed here.",
)
return qs.filter(meet__assembly__division__organization=request.user.organization)

class Media:
css = {"all": ("css/admin.css",)}
js = ["js/admin/list_filter_collapse.js"]
Expand Down
36 changes: 36 additions & 0 deletions attendees/occasions/apps.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,41 @@
import pghistory
from django.apps import AppConfig
from django.apps import apps as django_apps


class OccasionsConfig(AppConfig):
name = "attendees.occasions"

def ready(self):
schedule_calendar_model = django_apps.get_model("schedule.Calendar", require_ready=False)
schedule_calendarrelation_model = django_apps.get_model("schedule.CalendarRelation", require_ready=False)
schedule_event_model = django_apps.get_model("schedule.Event", require_ready=False)
schedule_eventrelation_model = django_apps.get_model("schedule.EventRelation", require_ready=False)

pghistory.track(
pghistory.Snapshot('calendar.snapshot'),
related_name='history',
model_name='CalendarHistory',
app_label='occasions'
)(schedule_calendar_model)

pghistory.track(
pghistory.Snapshot('calendarrelation.snapshot'),
related_name='history',
model_name='CalendarRelationHistory',
app_label='occasions',
)(schedule_calendarrelation_model)

pghistory.track(
pghistory.Snapshot('event.snapshot'),
related_name='history',
model_name='EventHistory',
app_label='occasions'
)(schedule_event_model)

pghistory.track(
pghistory.Snapshot('eventrelation.snapshot'),
related_name='history',
model_name='EventRelationHistory',
app_label='occasions',
)(schedule_eventrelation_model)
25 changes: 24 additions & 1 deletion attendees/occasions/migrations/0001_message_template.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Generated by Django 3.0.14 on 2021-08-28 17:55

# from django.contrib.postgres.fields.jsonb import JSONField
from attendees.persons.models.utility import Utility
from django.db import migrations, models
import django.utils.timezone
import model_utils.fields
Expand Down Expand Up @@ -31,8 +31,31 @@ class Migration(migrations.Migration):
'db_table': 'occasions_message_templates',
},
),
migrations.RunSQL(Utility.default_sql('occasions_message_templates')),
migrations.AddConstraint(
model_name='messagetemplate',
constraint=models.UniqueConstraint(condition=models.Q(is_removed=False), fields=('organization', 'type'), name='organization_type'),
),
migrations.CreateModel(
name='MessageTemplatesHistory',
fields=[
('pgh_id', models.BigAutoField(primary_key=True, serialize=False)),
('pgh_created_at', models.DateTimeField(auto_now_add=True)),
('pgh_label', models.TextField(help_text='The event label.')),
('pgh_obj', models.ForeignKey(db_constraint=False, on_delete=models.deletion.DO_NOTHING, related_name='history', to='occasions.messagetemplate')),
('id', models.BigIntegerField(db_index=True)),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, editable=False, verbose_name='created')),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, editable=False, verbose_name='modified')),
('is_removed', models.BooleanField(default=False)),
('organization', models.ForeignKey(db_constraint=False, on_delete=models.deletion.DO_NOTHING, related_name='+', related_query_name='+', to='whereabouts.organization')),
('templates', models.JSONField(blank=True, default=dict, help_text='Example: {"body": "Dear {name}: Hello!"}. Whatever in curly braces will be interpolated by variables, Please keep {} here even no data', null=True)),
('defaults', models.JSONField(blank=True, default=dict, help_text='Example: {"name": "John", "Date": "08/31/2020"}. Please keep {} here even no data', null=True)),
('type', models.SlugField(db_index=False, help_text='format: Organization_slug-prefix-message-type-name')),
('pgh_context', models.ForeignKey(db_constraint=False, null=True, on_delete=models.deletion.DO_NOTHING, related_name='+', to='pghistory.context')),
],
options={
'db_table': 'occasions_message_templateshistory',
},
),
migrations.RunSQL(Utility.pgh_default_sql('occasions_message_templateshistory', original_model_table='occasions_message_templates')),
]
Loading