-
-
Notifications
You must be signed in to change notification settings - Fork 483
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
Allow alternative user model for tracking history_user #371
Changes from 3 commits
10c794f
406e75e
25f9297
16f0241
3ddae54
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,20 +24,29 @@ | |
registered_models = {} | ||
|
||
|
||
def default_get_user(request, **kwargs): | ||
try: | ||
return request.user | ||
except AttributeError: | ||
return None | ||
|
||
|
||
class HistoricalRecords(object): | ||
thread = threading.local() | ||
|
||
def __init__(self, verbose_name=None, bases=(models.Model,), | ||
user_related_name='+', table_name=None, inherit=False, | ||
excluded_fields=None, | ||
history_id_field=None, | ||
history_change_reason_field=None): | ||
excluded_fields=None, history_id_field=None, | ||
history_change_reason_field=None, | ||
user_model=None, get_user=default_get_user): | ||
self.user_set_verbose_name = verbose_name | ||
self.user_related_name = user_related_name | ||
self.table_name = table_name | ||
self.inherit = inherit | ||
self.history_id_field = history_id_field | ||
self.history_change_reason_field = history_change_reason_field | ||
self.user_model = user_model | ||
self.get_user = get_user | ||
if excluded_fields is None: | ||
excluded_fields = [] | ||
self.excluded_fields = excluded_fields | ||
|
@@ -74,15 +83,11 @@ def save_without_historical_record(self, *args, **kwargs): | |
|
||
def finalize(self, sender, **kwargs): | ||
inherited = False | ||
try: | ||
hint_class = self.cls | ||
except AttributeError: # called via `register` | ||
pass | ||
else: | ||
if hint_class is not sender: # set in concrete | ||
inherited = (self.inherit and issubclass(sender, hint_class)) | ||
if not inherited: | ||
return # set in abstract | ||
if self.cls is not sender: # set in concrete | ||
inherited = (self.inherit and issubclass(sender, self.cls)) | ||
if not inherited: | ||
return # set in abstract | ||
|
||
if hasattr(sender._meta, 'simple_history_manager_attribute'): | ||
raise exceptions.MultipleRegistrationsError( | ||
'{}.{} registered multiple times for history tracking.'.format( | ||
|
@@ -207,7 +212,9 @@ def copy_fields(self, model): | |
def get_extra_fields(self, model, fields): | ||
"""Return dict of extra fields added to the historical record model""" | ||
|
||
user_model = getattr(settings, 'AUTH_USER_MODEL', 'auth.User') | ||
user_model = self.user_model or getattr( | ||
settings, 'AUTH_USER_MODEL', 'auth.User' | ||
) | ||
|
||
def revert_url(self): | ||
"""URL for this change in the default admin site.""" | ||
|
@@ -349,12 +356,14 @@ def get_history_user(self, instance): | |
try: | ||
return instance._history_user | ||
except AttributeError: | ||
request = None | ||
try: | ||
if self.thread.request.user.is_authenticated: | ||
return self.thread.request.user | ||
return None | ||
request = self.thread.request | ||
except AttributeError: | ||
return None | ||
pass | ||
|
||
return self.get_user(history=self, instance=instance, request=request) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why include the HistoricalRecords instance in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Once I hear back about this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought it may be useful to give the historical object to the method since it is a generic API that the user is providing. It could be removed. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yea I don't see a reason for it to be included. Let's get rid of it for now and if someone ends up needing it, we can reevaluate. Just remove that and then I'm happy to merge. |
||
|
||
|
||
def transform_field(field): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -415,6 +415,71 @@ class InheritTracking4(TrackedAbstractBaseA): | |
pass | ||
|
||
|
||
class BucketMember(models.Model): | ||
name = models.CharField(max_length=30) | ||
user = models.OneToOneField( | ||
User, | ||
related_name="bucket_member", | ||
on_delete=models.CASCADE | ||
) | ||
|
||
|
||
class BucketData(models.Model): | ||
changed_by = models.ForeignKey( | ||
BucketMember, | ||
on_delete=models.SET_NULL, | ||
null=True, blank=True, | ||
) | ||
history = HistoricalRecords(user_model=BucketMember) | ||
|
||
@property | ||
def _history_user(self): | ||
return self.changed_by | ||
|
||
|
||
def get_bucket_member1(instance, **kwargs): | ||
try: | ||
return instance.changed_by | ||
except AttributeError: | ||
return None | ||
|
||
|
||
class BucketDataRegister1(models.Model): | ||
changed_by = models.ForeignKey( | ||
BucketMember, | ||
on_delete=models.SET_NULL, | ||
null=True, blank=True, | ||
) | ||
|
||
|
||
register( | ||
BucketDataRegister1, | ||
user_model=BucketMember, | ||
get_user=get_bucket_member1 | ||
) | ||
|
||
|
||
def get_bucket_member2(request, **kwargs): | ||
try: | ||
return request.user.bucket_member | ||
except AttributeError: | ||
return None | ||
|
||
|
||
class BucketDataRegister2(models.Model): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see the precedent for the |
||
data = models.CharField(max_length=30) | ||
|
||
def get_absolute_url(self): | ||
return reverse('bucket_data-detail', kwargs={'pk': self.pk}) | ||
|
||
|
||
register( | ||
BucketDataRegister2, | ||
user_model=BucketMember, | ||
get_user=get_bucket_member2 | ||
) | ||
|
||
|
||
class UUIDModel(models.Model): | ||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) | ||
history = HistoricalRecords( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,8 @@ | |
from simple_history.tests.tests.utils import middleware_override_settings | ||
from ..models import ( | ||
Book, | ||
BucketData, | ||
BucketMember, | ||
Choice, | ||
ConcreteExternal, | ||
Employee, | ||
|
@@ -301,6 +303,21 @@ def test_deleteting_user(self): | |
historical_poll = poll.history.all()[0] | ||
self.assertEqual(historical_poll.history_user, None) | ||
|
||
def test_deleteting_member(self): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. typo |
||
"""Test deletes of a BucketMember doesn't cascade delete the history""" | ||
self.login() | ||
member = BucketMember.objects.create(name="member1", user=self.user) | ||
bucket_data = BucketData(changed_by=member) | ||
bucket_data.save() | ||
|
||
historical_poll = bucket_data.history.all()[0] | ||
self.assertEqual(historical_poll.history_user, member) | ||
|
||
member.delete() | ||
|
||
historical_poll = bucket_data.history.all()[0] | ||
self.assertEqual(historical_poll.history_user, None) | ||
|
||
def test_missing_one_to_one(self): | ||
"""A relation to a missing one-to-one model should still show | ||
history""" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will this still work when you call register?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I updated the register method to store this and the unit tests have been updated to verify it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about
populate_history
? I noticed this removed adding the model toregistered_models
which is used thereThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The register function calls calls
records.finalize(model)
on line 34. Thefinalize
method callscreate_history_model
which also updatesregistered_models
on line 148.