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
Integrate DJ2.1 "view" permissions for both model and historical model #529
Conversation
Codecov Report
@@ Coverage Diff @@
## master #529 +/- ##
==========================================
- Coverage 97.8% 97.55% -0.25%
==========================================
Files 17 17
Lines 866 942 +76
Branches 122 131 +9
==========================================
+ Hits 847 919 +72
- Misses 8 12 +4
Partials 11 11
Continue to review full report at Codecov.
|
Since this wasn't fully merged in yet, I jus wanted to add in 2 cents and say that rather than just hiding the Edit: aren't instead of are. |
author erikvw <ew2789@gmail.com> 1550266705 -0600 committer erikvw <ew2789@gmail.com> 1560873977 -0500 add Close button conditionally show text move titles and show close to methods just use has_change_permission use user permissions instead of just overriding method add additional tests
SIMPLE_HISTORY_REVERT_ENABLED
refactor history_view reduce complexity ... add dj2.1 note further refactor history_view, allow superuser to override all perms and settings, update tests fix py2 incompatibles, fix revert logic with view/change revert_disabled must return false if superuser format minor format improve coverage, update docs improve coverage, update docs add test update test update CHANGES add doscstring update docs separate tests, refactor format revert runtests format change title to inline if statement minor edit to docs format, pep8
format merge w/ master
@rossmechanic and @dwasyl ... to want to follow the Django pattern of adding the fields to the "readonly_fields" is a good instinct. The difference, however, is that the options are no longer rendered (e.g. for a radio field, etc). Although a matter of opinion/preference, my experience has been that an auditor prefers not only to see the selected response but also to see what the available options were. So I would prefer to leave it at is. What do you think? |
@erikvw @rossmechanic For what it's worth, I understand that and actually find it to be a problem with history records (since many of mine link to other models via ForeignKey or ManyToMany relationships which are never quite captured. The choices that you'll see in a historical record are coloured by the currently available relations or choices. Since it's not truly an accurate historical representation my thought is that displaying it as a widget makes it more confusing since an end-user could think those were the options at the time the record was made (which may or may not be true). Flattening them to the |
@rossmechanic @dwasyl You make a good case. Would you like me to explore this change? |
Would be nice to merge and treat @dwasyl 's suggestion as a separate PR. |
return self.fetch_history_list_display_callables(queryset) | ||
|
||
@property | ||
def history_manager(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.
We have a util that already does this. but fine with this for now so we can get this in
<a href="../">{% trans "History" %}</a> › | ||
{% blocktrans with original_opts.verbose_name as verbose_name %}Revert {{verbose_name}}{% endblocktrans %} | ||
{% if has_revert_permission %} | ||
{% blocktrans with original_opts.verbose_name as verbose_name %}Revert {{ verbose_name }}{% endblocktrans %} |
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.
Indentation looks off 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.
Hey @erikvw ! Finally got around to reviewing this. A few questions around how we structured perms. Also want to avoid some of the refactoring work that was done, so we can get this feature in and possibly look at refactoring work in a different PR. Thanks!
historical_permission = request.user.has_perm( | ||
"%s.%s" % (historical_opts.app_label, historical_codename) | ||
) | ||
return permission and historical_permission |
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.
so in this case, for view
, the user has to succeed on the base model's has_view_permission
function as well as have the app_label.view_historicalmodelname
permission?
else: | ||
try: | ||
view_permission = self.has_view_permission(request, obj) | ||
except AttributeError: |
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.
Is this just checking that we're on version >= 2.1? Would rather have an explicit check of the django version
else: | ||
try: | ||
has_permission = self.has_view_or_change_permission(request, obj) | ||
except AttributeError: |
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.
Same as above. Would prefer to have an explicit check for django version. Makes it a lot easier to remove as we deprecate certain versions down the line
historical_opts = self.history_manager.model._meta | ||
historical_codename = get_permission_codename(action, historical_opts) | ||
try: | ||
func = getattr(self, "has_%s_permission" % action) |
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 sure that I necessarily want to check for this permission. For example, in the case of reverting:
Say you have Model
and HistoricalModel
,
if I need to have has_change_permission
as well as change_historicalmodel
permission, then what's the point of this change_historicalmodel
permission? I think the only reason that permission would make sense would be if we wanted users who could revert objects, but not change them from scratch. In that case, you would want a user to be able to revert using only the change_historicalmodel
permission. I think you shouldn't need both, you should only need the historical permission
object_history_template = "simple_history/object_history.html" | ||
object_history_form_template = "simple_history/object_history_form.html" | ||
|
||
def get_urls(self): | ||
"""Returns the additional urls used by the Reversion admin.""" | ||
"""Returns the additional urls used by the Reversion admin. |
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 these changes?
return obj | ||
|
||
def get_object_history(self, object_id): | ||
"""Returns a queryset of historical instances. |
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.
Let's reformat these docstrings so if they can fit on one line then they're on one line
@@ -42,53 +121,39 @@ def get_urls(self): | |||
return history_urls + urls | |||
|
|||
def history_view(self, request, object_id, extra_context=None): | |||
"""The 'history' admin view for this model.""" | |||
"""Overridden `history_view`. |
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 appreciate this refactor, but maybe let's do this in a separate PR? Hard to review in the context of the larger PR
@property | ||
def change_history(self): | ||
try: | ||
change_history = settings.SIMPLE_HISTORY_EDIT |
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 we change these try except AttributeErrors to getattr(settings, "SIMPLE_HISTORY_EDIT")
@@ -207,6 +344,13 @@ def render_history_view(self, request, template, context, **kwargs): | |||
return render(request, template, context, **kwargs) | |||
|
|||
def save_model(self, request, obj, form, change): | |||
"""Set special model attribute to user for reference after save""" | |||
"""Set special model attribute to user for reference after save. |
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.
same as above on docstrings
Hey @erikvw any update on this? |
@erikvw lmk if you want me to take this over |
been very busy recently, ...I'll try to look at this either today or
tomorrow. but failing that I may ask you to take over ...
…On Fri, Aug 2, 2019 at 9:34 AM Ross Mechanic ***@***.***> wrote:
@erikvw <https://github.com/erikvw> lmk if you want me to take this over
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#529>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAD2TZH3JYXENSB5PSFASVDQCRAYRANCNFSM4GX2XGXQ>
.
|
Sounds good @erikvw ! Just lmk |
This needs to be rebased. |
In some scenarios, it is not ideal to allow users to revert a model instance to a previous version.
Django 2.1 introduced view permissions which, if integrated into DSH, would give an administrator the ability to allow a user to add/change a model but not revert to a previous instance through the model's history.
Description
The
SimpleHistoryAdmin
reviews the historical model instance permissions instead of just the model instance by overriding thehas_xxxx_permission
methods in ModelAdmin.enable this feature through settings (SIMPLE_HISTORY_PERMISSIONS_ENABLED);
honor
superuser
privileges;user/group permissions granted for the historical model cannot exceed those of the model. That is, granting
view
andchange
permissions for the historical model but onlyview
permission for the model means the user hasview
permission only for the historical model;text on the changelist and form becomes context sensitive (change permission vs view permission);
setting
SIMPLE_HISTORY_REVERT_DISABLED=True
limits all users (other than superuser) to view access. (this works for all Django versions)the [Revert] button is changed to a [Close] button if the user has:
-- if Django >= 2.1, feature is disabled, and has
view
on model;-- if Django >= 2.1, feature is enable and has
view
model permission andview
historical model permission;-- if
settings. SIMPLE_HISTORY_REVERT_DISABLED = True
(regardless of Django version).How Has This Been Tested?
See tests in
test_admin_with_permissions.py
A few tests in
test_admin.py
required very minor modifications.Screenshots (if appropriate):
For a user with "view" only permission on a historical object, the changelist looks like this:
and the form looks like this:
Types of changes
Checklist:
make format
command to format my codeAUTHORS.rst
CHANGES.rst