-
Notifications
You must be signed in to change notification settings - Fork 413
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
Fixes #93 - Add 'changes_display_dict' property to 'LogEntry' model to display diff in a more human readable format #94
Conversation
c0643d1
to
1468c6d
Compare
Thanks for your contribution. Because the changes are bit more major than usual I'll mark this for version 0.5.0. I do not have a lot of time to look through the changes now, so I'll come back to it later. |
so if you want I can break this PR up into a few separate changes, one would be to add the mapping fields attribute. This allow the diff to take a database defined name Different PR will be for adding a mapping of choices attributes to human displayable text and other potential differences, where you would prefer to take a different approach, I will open a different issue. |
e547ded
to
14ef242
Compare
PR now only adds changes display dict functionality. If you are not in favor of this approach you can close the PR and I will just use my custom fork. It is my opinion that due to the need for using model meta attribute introspection to solve this problem, template tags are an inefficient solution for this problem. From TwoScoops of Django Best Practices:
Implementing this as a templatetag would violate both bullets, it is both reading data and can be implemented in in a base class model |
OK, you convinced me. I have no time today to actually look at everything but I will take a look soon. |
.coveragerc
Outdated
@@ -0,0 +1,13 @@ | |||
[run] |
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.
I would like to have the coverage in another pull request.
.gitignore
Outdated
@@ -7,3 +7,4 @@ | |||
.project | |||
.pydevproject | |||
local_settings.py | |||
.coverage |
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.
Occurrence of coverage
README.md
Outdated
@@ -27,3 +27,16 @@ Contribute | |||
---------- | |||
|
|||
If you have great ideas for Auditlog, or if you like to improve something, feel free to fork this repository and/or create a pull request. I'm open for suggestions. If you like to discuss something with me (about Auditlog), please open an issue. | |||
|
|||
Pull Request Guidelines |
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.
Can you put this in another pull request? Thanks!
@@ -229,6 +233,62 @@ def changes_str(self, colon=': ', arrow=smart_text(' \u2192 '), separator='; '): | |||
|
|||
return separator.join(substrings) | |||
|
|||
@property | |||
def changes_display_dict(self): |
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.
Can you inline-comment this? Than it is better understandable what it exactly does.
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 do
src/auditlog/models.py
Outdated
@@ -257,6 +317,7 @@ def __init__(self, pk_indexable=True, **kwargs): | |||
kwargs['content_type_field'] = 'content_type' | |||
super(AuditlogHistoryField, self).__init__(**kwargs) | |||
|
|||
|
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.
Newline not needed here
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.
yeah I think that was my editor adding it automatically on save ill remove
) | ||
|
||
status = models.CharField(max_length=1, choices=STATUS_CHOICES) | ||
multiselect = MultiSelectField(max_length=3, choices=STATUS_CHOICES, max_choices=3) |
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.
Why are you explicitly testing with this third party library?
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.
I use this library and wanted auditlog to be compatible with 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.
Right. If this is added to the tests I would also want the built-in PostgreSQL equivalent/replacement of it (https://docs.djangoproject.com/en/1.10/ref/contrib/postgres/fields/#arrayfield) since it is likely that people store lists that way as well.
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.
yeah I am stuck on 1.8 so using it, I will add arrayfield support though, good idea!
src/auditlog_tests/test_settings.py
Outdated
@@ -19,7 +20,7 @@ | |||
DATABASES = { | |||
'default': { | |||
'ENGINE': 'django.db.backends.sqlite3', | |||
'NAME': 'auditlog_tests.db', | |||
'NAME': 'auditlog_test.db', |
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.
The 'app' is still called auditlog_tests, so why this change?
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.
not needed change, ill revert
The fixes do not seem to be pushed to this branch, so I cannot merge it. Can you do this so we can get the functionality in? |
Fixes jazzband#93 - Add 'changes_display_dict' property to 'LogEntry' model to display diff in a more human readable format 'changes_display_dict' currently handles fields with choices, long textfields and charfields, and datetime fields. Fields with choices display in the diff with their human readable text. Textfields and Charfields longer than 140 characters are truncated with an ellipsis appended. Datetime fields are rendered with the format 'Aug. 21, 2017, 02:25pm' A new kwarg was added to 'AuditlogModelRegistry' called 'mapping_fields'. The kwarg allows the user to map the fields in the model to a more human readable or intuitive name. If a field isn't mapped it will default to the name as defined in the model. Partial mapping is supported, all fields do not need to be mapped to use the feature. Tests added for 'changes_display_dict' Tests added for 'mapping_fields' property Added python 3.5 support Updated Documentation Update .travis.yml to use requirements_test.txt as well in build
… dateutil's parser and would get formatted as datetime output. Introspection is used to see if the internal field type is DateTime, Date, or Time and then try to parse it and format it appropriately. This adds functionality in that DateTime, Date and Time each now have their separate formats.
of the 'mapping_fields' dictionary keys and values. The purpose of this dictionary is so that the LogEntry model's 'changes_display_dict' method can reverse a human readable field name returned from 'changes' so that it can do extra checks on that field. Without this we would get a FieldDoesNotExist error when trying to lookup fields on the model in the 'changes_display_dict' method so that we could introspect attributes about them when determining how to display human readable changes.
…f the model its associated to
Codecov Report
@@ Coverage Diff @@
## master #94 +/- ##
=========================================
Coverage ? 69.15%
=========================================
Files ? 18
Lines ? 483
Branches ? 0
=========================================
Hits ? 334
Misses ? 149
Partials ? 0
Continue to review full report at Codecov.
|
Review comments have been addressed
Hey @jjkester addressed all the review comments and got the diff coverage to 100%. To support the request for Postgres's Also after this PR merges there will now be a base commit with coverage report to compare PR's to so we will get actual information from codecov reports. |
src/auditlog/models.py
Outdated
from auditlog.registry import auditlog | ||
model_fields = auditlog.get_model_fields(model._meta.model) | ||
reversed_field = model_fields['reverse_mapping_fields'].get(field_name) | ||
field = model._meta.get_field(reversed_field) |
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.
Again, a FieldDoesNotExist can be raised, for example when a model is changed.
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.
Ok, with the update to mapping_fields
not changing the diff.py
output, I didn't need the reverse_mapping_fields
. I have now removed that section, and in the case of FieldDoesNotExist
fall back on the field_name
auditlog has stored.
src/auditlog/models.py
Outdated
if "DateTime" in field.get_internal_type(): | ||
try: | ||
value = parser.parse(value) | ||
value = value.strftime("%b %d, %Y %I:%M %p") |
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.
I would rather see use of Django's l10n stuff for this, so that the format adapts to different settings.
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.
Alright, modified to now rely on USE_L10N
first and fallback on settings.DATETIME_FORMAT
, settings.DATE_FORMAT
, and settings.TIME_FORMAT
which have defaults and can be set if the user desires.
Added test cases which demonstrate this.
src/auditlog/models.py
Outdated
elif "Date" in field.get_internal_type(): | ||
try: | ||
value = parser.parse(value) | ||
value = value.strftime("%b %d, %Y") |
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.
Again, l10n is preferred.
src/auditlog/models.py
Outdated
elif "Time" in field.get_internal_type(): | ||
try: | ||
value = parser.parse(value) | ||
value = value.strftime("%I:%M %p") |
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.
Again, l10n is preferred.
src/auditlog/diff.py
Outdated
@@ -135,7 +135,7 @@ def model_instance_diff(old, new): | |||
new_value = get_field_value(new, field) | |||
|
|||
if old_value != new_value: | |||
diff[field.name] = (smart_text(old_value), smart_text(new_value)) | |||
diff[model_fields['mapping_fields'].get(field.name, field.name)] = (smart_text(old_value), smart_text(new_value)) |
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.
I would not use the mapping here. I think keeping the database representation as close to internal representation as possible is a good idea, especially because later on we are using database values to retrieve model fields. Changing the mapping would completely destroy that capability for the changed fields. The actual field names are less likely to change.
The mapping could be applied when the dictionary for display is built up (see my comment there for details).
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.
Ah yes I agree, I reverted this change and now only change the name in the changes_display_dict
property
src/auditlog/models.py
Outdated
|
||
values_display.append(value) | ||
|
||
changes_display_dict[field_name] = values_display |
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.
Instead of the field name we might use the mapping here. Also, we might consider going with the verbose name if present. So in order of importance: mapping - verbose name - name (the last is only guaranteed to be present).
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.
Ok made this change, the order is mapping -> verbose name -> default verbose name. Django assigns a default verbose_name if none is provided so it is guaranteed to be always there.
If this isn’t given, Django will use a munged version of the class name: CamelCase becomes camel case.
ref
…ango's settings definitions.
…e changes_display_dict function. Remove usage of reverse_fields as it is no longer required. Default to field.verbose_name in the cases where there is no mapping_field, and if the field cannot be found default to the auditlog stored field_name
…ame in the auditlog diff itself
addressed concerns, awaiting new review
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.
You might change the docs a bit, otherwise it looks good
docs/source/usage.rst
Outdated
</table> | ||
</div> | ||
|
||
If you want to display the changes in a more human readable format use the :py:class:`LogEntry`'s :py:attr:`changes_display_dict` instead. The :py:attr:`changes_display_dict` will translate ``choices`` fields into their human readable form, display timestamps in the form ``Jun. 31, 2017 12:55pm``, and truncate text greater than 140 characters to 140 characters with an ellipsis appended. |
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.
maybe be explicit about the l10n support
'changes_display_dict' currently handles fields with choices, long textfields and charfields, and datetime fields.
ArrayField
anddjango-multiselectfield
for those not on PostgresA new kwarg was added to
AuditlogModelRegistry
calledmapping_fields
. The kwarg allows the user to map the fields in the model to a more human readable or intuitive name. If a field isn't mapped it will default to the name as defined in the model. Partial mapping is supported, all fields do not need to be mapped to use the feature.Tests added for 'changes_display_dict'
Tests added for 'mapping_fields' property
Updated Documentation
Resolves #93