Permalink
Browse files

Merge 145914a into 14172cf

  • Loading branch information...
2 parents 14172cf + 145914a commit ec4023027c02a0ea4ee6c182163069cb098e27b5 @olivierdalang olivierdalang committed on GitHub Jul 27, 2017
@@ -225,37 +225,49 @@ class InlineAdminFormSet:
A wrapper around an inline formset for use in the admin system.
"""
def __init__(self, inline, formset, fieldsets, prepopulated_fields=None,
- readonly_fields=None, model_admin=None):
+ readonly_fields=None, model_admin=None, has_view_permission=True, has_add_permission=True,
+ has_change_permission=True, has_delete_permission=True):
self.opts = inline
self.formset = formset
self.fieldsets = fieldsets
self.model_admin = model_admin
if readonly_fields is None:
readonly_fields = ()
self.readonly_fields = readonly_fields
+
+ if has_change_permission:
+ self.viewonly_fields = readonly_fields
+ else:
+ self.viewonly_fields = readonly_fields + flatten_fieldsets(fieldsets)
if prepopulated_fields is None:
prepopulated_fields = {}
self.prepopulated_fields = prepopulated_fields
self.classes = ' '.join(inline.classes) if inline.classes else ''
+ self.has_view_permission = has_view_permission
+ self.has_add_permission = has_add_permission
+ self.has_change_permission = has_change_permission
+ self.has_delete_permission = has_delete_permission
def __iter__(self):
for form, original in zip(self.formset.initial_forms, self.formset.get_queryset()):
view_on_site_url = self.opts.get_view_on_site_url(original)
yield InlineAdminForm(
self.formset, form, self.fieldsets, self.prepopulated_fields,
- original, self.readonly_fields, model_admin=self.opts,
+ original, self.viewonly_fields, model_admin=self.opts,
view_on_site_url=view_on_site_url,
)
for form in self.formset.extra_forms:
yield InlineAdminForm(
self.formset, form, self.fieldsets, self.prepopulated_fields,
None, self.readonly_fields, model_admin=self.opts,
)
- yield InlineAdminForm(
- self.formset, self.formset.empty_form,
- self.fieldsets, self.prepopulated_fields, None,
- self.readonly_fields, model_admin=self.opts,
- )
+
+ if self.has_add_permission:
+ yield InlineAdminForm(
+ self.formset, self.formset.empty_form,
+ self.fieldsets, self.prepopulated_fields, None,
+ self.readonly_fields, model_admin=self.opts,
+ )
def fields(self):
fk = getattr(self.formset, "fk", None)
@@ -157,6 +157,7 @@ def formfield_for_dbfield(self, db_field, request, **kwargs):
wrapper_kwargs = {}
if related_modeladmin:
wrapper_kwargs.update(
+ can_view_related=related_modeladmin.has_view_permission(request),
can_add_related=related_modeladmin.has_add_permission(request),
can_change_related=related_modeladmin.has_change_permission(request),
can_delete_related=related_modeladmin.has_delete_permission(request),
@@ -434,13 +435,19 @@ def to_field_allowed(self, request, to_field):
return False
- def has_add_permission(self, request):
+ def has_view_permission(self, request, obj=None):
"""
- Return True if the given request has permission to add an object.
- Can be overridden by the user in subclasses.
+ Returns True if the given request has permission to view the given
+ Django model instance, the default implementation doesn't examine the
+ `obj` parameter.
+
+ Can be overridden by the user in subclasses. In such case it should
+ return True if the given request has permission to view the `obj`
+ model instance. If `obj` is None, this should return True if the given
+ request has permission to view *any* object of the given type.
"""
opts = self.opts
- codename = get_permission_codename('add', opts)
+ codename = get_permission_codename('view', opts)
return request.user.has_perm("%s.%s" % (opts.app_label, codename))
def has_change_permission(self, request, obj=None):
@@ -458,6 +465,15 @@ def has_change_permission(self, request, obj=None):
codename = get_permission_codename('change', opts)
return request.user.has_perm("%s.%s" % (opts.app_label, codename))
+ def has_add_permission(self, request):
+ """
+ Return True if the given request has permission to add an object.
+ Can be overridden by the user in subclasses.
+ """
+ opts = self.opts
+ codename = get_permission_codename('add', opts)
+ return request.user.has_perm("%s.%s" % (opts.app_label, codename))
+
def has_delete_permission(self, request, obj=None):
"""
Return True if the given request has permission to change the given
@@ -537,7 +553,8 @@ def get_inline_instances(self, request, obj=None):
for inline_class in self.inlines:
inline = inline_class(self.model, self.admin_site)
if request:
- if not (inline.has_add_permission(request) or
+ if not (inline.has_view_permission(request, obj) or
+ inline.has_add_permission(request) or
inline.has_change_permission(request, obj) or
inline.has_delete_permission(request, obj)):
continue
@@ -597,6 +614,7 @@ def get_model_perms(self, request):
of those actions.
"""
return {
+ 'view': self.has_view_permission(request),
'add': self.has_add_permission(request),
'change': self.has_change_permission(request),
'delete': self.has_delete_permission(request),
@@ -605,7 +623,7 @@ def get_model_perms(self, request):
def _get_form_for_get_fields(self, request, obj):
return self.get_form(request, obj, fields=None)
- def get_form(self, request, obj=None, **kwargs):
+ def get_form(self, request, obj=None, change=False, **kwargs):
"""
Return a Form class for use in the admin add view. This is used by
add_view and change_view.
@@ -618,6 +636,11 @@ def get_form(self, request, obj=None, **kwargs):
exclude = [] if excluded is None else list(excluded)
readonly_fields = self.get_readonly_fields(request, obj)
exclude.extend(readonly_fields)
+
+ # If it is a change form and the user has no permission, we exlcude all the fields
+ if change and hasattr(request, 'user') and not self.has_change_permission(request, obj):
+ exclude.extend(fields)
+
if excluded is None and hasattr(self.form, '_meta') and self.form._meta.exclude:
# Take the custom ModelForm's Meta.exclude into account only if the
# ModelAdmin doesn't define its own.
@@ -776,6 +799,10 @@ def get_actions(self, request):
if self.actions is None or IS_POPUP_VAR in request.GET:
return OrderedDict()
+ # Also user has to have change permisson
+ if not self.has_change_permission(request, None):
+ return OrderedDict()
+
actions = []
# Gather actions from the admin site first
@@ -1009,12 +1036,21 @@ def render_change_form(self, request, context, add=False, change=False, form_url
preserved_filters = self.get_preserved_filters(request)
form_url = add_preserved_filters({'preserved_filters': preserved_filters, 'opts': opts}, form_url)
view_on_site_url = self.get_view_on_site_url(obj)
+
+ has_editable_inline_admin_formsets = False
+ for inline in context['inline_admin_formsets']:
+ if inline.has_add_permission or inline.has_change_permission or inline.has_delete_permission:
+ has_editable_inline_admin_formsets = True
+ break
+
context.update({
'add': add,
'change': change,
+ 'has_view_permission': self.has_view_permission(request, obj),
'has_add_permission': self.has_add_permission(request),
'has_change_permission': self.has_change_permission(request, obj),
'has_delete_permission': self.has_delete_permission(request, obj),
+ 'has_editable_inline_admin_formsets': has_editable_inline_admin_formsets,
'has_file_field': True, # FIXME - this should check if form or formsets have a FileField,
'has_absolute_url': view_on_site_url is not None,
'absolute_url': view_on_site_url,
@@ -1359,14 +1395,20 @@ def render_delete_form(self, request, context):
)
def get_inline_formsets(self, request, formsets, inline_instances, obj=None):
+
inline_admin_formsets = []
for inline, formset in zip(inline_instances, formsets):
fieldsets = list(inline.get_fieldsets(request, obj))
readonly = list(inline.get_readonly_fields(request, obj))
+ has_view_permission = inline.has_view_permission(request)
+ has_add_permission = inline.has_add_permission(request)
+ has_change_permission = inline.has_change_permission(request)
+ has_delete_permission = inline.has_delete_permission(request)
prepopulated = dict(inline.get_prepopulated_fields(request, obj))
inline_admin_formset = helpers.InlineAdminFormSet(
- inline, formset, fieldsets, prepopulated, readonly,
- model_admin=self,
+ inline, formset, fieldsets, prepopulated, readonly, model_admin=self,
+ has_view_permission=has_view_permission, has_add_permission=has_add_permission,
+ has_change_permission=has_change_permission, has_delete_permission=has_delete_permission,
)
inline_admin_formsets.append(inline_admin_formset)
return inline_admin_formsets
@@ -1425,13 +1467,13 @@ def _changeform_view(self, request, object_id, form_url, extra_context):
else:
obj = self.get_object(request, unquote(object_id), to_field)
- if not self.has_change_permission(request, obj):
+ if not self.has_view_permission(request, obj) and not self.has_change_permission(request, obj):
raise PermissionDenied
if obj is None:
return self._get_obj_does_not_exist_redirect(request, opts, object_id)
- ModelForm = self.get_form(request, obj)
+ ModelForm = self.get_form(request, obj, change=not add)
if request.method == 'POST':
form = ModelForm(request.POST, request.FILES, instance=obj)
if form.is_valid():
@@ -1465,12 +1507,15 @@ def _changeform_view(self, request, object_id, form_url, extra_context):
else:
form = ModelForm(instance=obj)
formsets, inline_instances = self._create_formsets(request, obj, change=True)
-
+ if not add and not self.has_change_permission(request):
+ uneditable_fields = flatten_fieldsets(self.get_fieldsets(request, obj))
+ else:
+ uneditable_fields = self.get_readonly_fields(request, obj)
adminForm = helpers.AdminForm(
form,
list(self.get_fieldsets(request, obj)),
self.get_prepopulated_fields(request, obj),
- self.get_readonly_fields(request, obj),
+ uneditable_fields,
model_admin=self)
media = self.media + adminForm.media
@@ -1519,7 +1564,7 @@ def changelist_view(self, request, extra_context=None):
from django.contrib.admin.views.main import ERROR_FLAG
opts = self.model._meta
app_label = opts.app_label
- if not self.has_change_permission(request, None):
+ if not self.has_view_permission(request, None) and not self.has_change_permission(request, None):
raise PermissionDenied
list_display = self.get_list_display(request)
@@ -1565,6 +1610,8 @@ def changelist_view(self, request, extra_context=None):
# Actions with no confirmation
if (actions and request.method == 'POST' and
'index' in request.POST and '_save' not in request.POST):
+ if not self.has_change_permission(request, None):
+ raise PermissionDenied
if selected:
response = self.response_action(request, queryset=cl.get_queryset(request))
if response:
@@ -1581,6 +1628,8 @@ def changelist_view(self, request, extra_context=None):
if (actions and request.method == 'POST' and
helpers.ACTION_CHECKBOX_NAME in request.POST and
'index' not in request.POST and '_save' not in request.POST):
+ if not self.has_change_permission(request, None):
+ raise PermissionDenied
if selected:
response = self.response_action(request, queryset=cl.get_queryset(request))
if response:
@@ -1601,6 +1650,8 @@ def changelist_view(self, request, extra_context=None):
# Handle POSTed bulk-edit data.
if request.method == 'POST' and cl.list_editable and '_save' in request.POST:
+ if not self.has_change_permission(request, None):
+ raise PermissionDenied
FormSet = self.get_changelist_formset(request)
formset = cl.formset = FormSet(request.POST, request.FILES, queryset=self.get_queryset(request))
if formset.is_valid():
@@ -1628,7 +1679,7 @@ def changelist_view(self, request, extra_context=None):
return HttpResponseRedirect(request.get_full_path())
# Handle GET -- construct a formset for display.
- elif cl.list_editable:
+ elif cl.list_editable and self.has_change_permission(request):
FormSet = self.get_changelist_formset(request)
formset = cl.formset = FormSet(queryset=cl.result_list)
@@ -1756,7 +1807,7 @@ def history_view(self, request, object_id, extra_context=None):
if obj is None:
return self._get_obj_does_not_exist_redirect(request, model._meta, object_id)
- if not self.has_change_permission(request, obj):
+ if not self.has_view_permission(request, obj) and not self.has_change_permission(request, obj):
raise PermissionDenied
# Then get the history for this object.
@@ -1904,7 +1955,17 @@ def get_formset(self, request, obj=None, **kwargs):
defaults.update(kwargs)
base_model_form = defaults['form']
+ can_change = self.has_change_permission(request, obj) if request else True
+ can_add = self.has_add_permission(request) if request else True
+
class DeleteProtectedModelForm(base_model_form):
+ def __init__(self, *args, **kwargs):
+ super(DeleteProtectedModelForm, self).__init__(*args, **kwargs)
+ if not can_change and not self.instance._state.adding:
+ self.fields = {}
+ if not can_add and self.instance._state.adding:
+ self.fields = {}
+
def hand_clean_DELETE(self):
"""
We don't validate the 'DELETE' field itself because on
@@ -1914,7 +1975,7 @@ def hand_clean_DELETE(self):
if self.cleaned_data.get(DELETION_FIELD_NAME, False):
using = router.db_for_write(self._meta.model)
collector = NestedObjects(using=using)
- if self.instance.pk is None:
+ if self.instance._state.adding:
return
collector.collect([self.instance])
if collector.protected:
@@ -1952,7 +2013,7 @@ def _get_form_for_get_fields(self, request, obj=None):
def get_queryset(self, request):
queryset = super().get_queryset(request)
- if not self.has_change_permission(request):
+ if not self.has_change_permission(request) and not self.has_view_permission(request):
queryset = queryset.none()
return queryset
@@ -1977,6 +2038,15 @@ def has_change_permission(self, request, obj=None):
codename = get_permission_codename('change', opts)
return request.user.has_perm("%s.%s" % (opts.app_label, codename))
+ def has_view_permission(self, request, obj=None):
+ if self.opts.auto_created:
+ # We're checking the rights to an auto-created intermediate model,
+ # which doesn't have its own individual permissions. The user needs
+ # to have the view permission for the related model in order to
+ # be able to do anything with the intermediate model.
+ return self.has_change_permission(request, obj)
+ return super(InlineModelAdmin, self).has_view_permission(request, obj)
+
def has_delete_permission(self, request, obj=None):
if self.opts.auto_created:
# We're checking the rights to an auto-created intermediate model,
@@ -427,7 +427,8 @@ def _build_app_dict(self, request, label=None):
'object_name': model._meta.object_name,
'perms': perms,
}
- if perms.get('change'):
+ if perms.get('change') or perms.get('view'):
+ model_dict['view_only'] = not perms.get('change')
with suppress(NoReverseMatch):
model_dict['admin_url'] = reverse('admin:%s_%s_changelist' % info, current_app=self.name)
if perms.get('add'):
@@ -662,6 +662,11 @@ div.breadcrumbs a:focus, div.breadcrumbs a:hover {
/* ACTION ICONS */
+.viewlink, .inlineviewlink {
+ padding-left: 16px;
+ background: url(../img/icon-viewlink.svg) 0 1px no-repeat;
+}
+
.addlink {
padding-left: 16px;
background: url(../img/icon-addlink.svg) 0 1px no-repeat;
Oops, something went wrong.

0 comments on commit ec40230

Please sign in to comment.