Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
I tried to fix #11561. "raw_id_fields_readonly" is similar to "raw_id_fields", but do not require "change permission". If a user does not have "change permission", "raw_id_fields" works as "raw_id_fields_readonly".
class ModelAdmin(BaseModelAdmin):
raw_id_fields_readonly = ()
def get_urls(self):
from django.conf.urls import patterns, url
def wrap(view):
def wrapper(*args, **kwargs):
return self.admin_site.admin_view(view)(*args, **kwargs)
return update_wrapper(wrapper, view)
info = self.model._meta.app_label, self.model._meta.module_name
urlpatterns = patterns('',
name='%s_%s_changelist' % info),
name='%s_%s_readonlylist' % info),
name='%s_%s_add' % info),
name='%s_%s_history' % info),
name='%s_%s_delete' % info),
name='%s_%s_change' % info),
return urlpatterns
def readonly_list_view(self, request, extra_context=None):
The 'readonly list' admin view for this model.
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):
# raise PermissionDenied
list_display = self.get_list_display(request)
list_display_links = self.get_list_display_links(request, list_display)
list_filter = self.get_list_filter(request)
# Check actions to see if any are available on this changelist
actions = self.get_actions(request)
if actions:
# Add the action checkboxes if there are any actions available.
list_display = ['action_checkbox'] + list(list_display)
ChangeList = self.get_changelist(request)
cl = ChangeList(request, self.model, list_display,
list_display_links, list_filter, self.date_hierarchy,
self.search_fields, self.list_select_related,
self.list_per_page, self.list_max_show_all, self.list_editable,
except IncorrectLookupParameters:
# Wacky lookup parameters were given, so redirect to the main
# changelist page, without parameters, and pass an 'invalid=1'
# parameter via the query string. If wacky parameters were given
# and the 'invalid=1' parameter was already in the query string,
# something is screwed up with the database, so display an error
# page.
if ERROR_FLAG in request.GET.keys():
return SimpleTemplateResponse('admin/invalid_setup.html', {
'title': _('Database error'),
return HttpResponseRedirect(request.path + '?' + ERROR_FLAG + '=1')
# If we're allowing changelist editing, we need to construct a formset
# for the changelist given all the fields to be edited. Then we'll
# use the formset to validate/process POSTed data.
formset = cl.formset = None
# Handle GET -- construct a formset for display.
if cl.list_editable:
FormSet = self.get_changelist_formset(request)
formset = cl.formset = FormSet(queryset=cl.result_list)
# Build the list of media to be used by the formset.
if formset:
media = +
media =
# Build the action form and populate it with available actions.
if actions:
action_form = self.action_form(auto_id=None)
action_form.fields['action'].choices = self.get_action_choices(request)
action_form = None
selection_note_all = ungettext('%(total_count)s selected',
'All %(total_count)s selected', cl.result_count)
context = {
'module_name': force_text(opts.verbose_name_plural),
'selection_note': _('0 of %(cnt)s selected') % {'cnt': len(cl.result_list)},
'selection_note_all': selection_note_all % {'total_count': cl.result_count},
'title': cl.title,
'is_popup': cl.is_popup,
'cl': cl,
'media': media,
'has_add_permission': False,#self.has_add_permission(request),
'app_label': app_label,
'action_form': action_form,
'actions_on_top': self.actions_on_top,
'actions_on_bottom': self.actions_on_bottom,
'actions_selection_counter': self.actions_selection_counter,
context.update(extra_context or {})
return TemplateResponse(request, self.change_list_template or [
'admin/%s/%s/change_list.html' % (app_label, opts.object_name.lower()),
'admin/%s/change_list.html' % app_label,
], context,
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
Get a form Field for a ForeignKey.
user = request.user
model =
permission_ok = user.has_perms(model._meta.app_label + '.' + model.__name__.lower())
db = kwargs.get('using')
if in self.raw_id_fields and permission_ok:
kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel,
self.admin_site, using=db, readonly=False)
elif in self.raw_id_fields_readonly or in self.raw_id_fields:
kwargs['widget'] = widgets.ForeignKeyRawIdWidget(db_field.rel,
self.admin_site, using=db, readonly=True)
elif in self.radio_fields:
kwargs['widget'] = widgets.AdminRadioSelect(attrs={
'class': get_ul_class(self.radio_fields[]),
kwargs['empty_label'] = db_field.blank and _('None') or None
return db_field.formfield(**kwargs)
def formfield_for_manytomany(self, db_field, request=None, **kwargs):
Get a form Field for a ManyToManyField.
# If it uses an intermediary model that isn't auto created, don't show
# a field in admin.
if not db_field.rel.through._meta.auto_created:
return None
db = kwargs.get('using')
user = request.user
model =
permission_ok = user.has_perms(model._meta.app_label + '.' + model.__name__.lower())
if in self.raw_id_fields and permission_ok:
kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel,
self.admin_site, using=db, readonly=False)
kwargs['help_text'] = ''
elif in self.raw_id_fields_readonly or in self.raw_id_fields:
kwargs['widget'] = widgets.ManyToManyRawIdWidget(db_field.rel,
self.admin_site, using=db, readonly=True)
kwargs['help_text'] = ''
elif in (list(self.filter_vertical) + list(self.filter_horizontal)):
kwargs['widget'] = widgets.FilteredSelectMultiple(db_field.verbose_name, ( in self.filter_vertical))
return db_field.formfield(**kwargs)
class ForeignKeyRawIdWidget(forms.TextInput):
def render(self, name, value, attrs=None):
rel_to =
if attrs is None:
attrs = {}
extra = []
readonly = 'true' if self.readonly else 'false'
if rel_to in self.admin_site._registry:
# The related object is registered with the same AdminSite
related_url = reverse('admin:%s_%s_changelist' %
params = self.url_parameters()
if params:
url = '?' + '&'.join(['%s=%s' % (k, v) for k, v in params.items()])
url = ''
if "class" not in attrs:
attrs['class'] = 'vForeignKeyRawIdAdminField' # The JavaScript code looks for this hook.
# TODO: "lookup_id_" is hard-coded here. This should instead use
# the correct API to determine the ID dynamically.
extra.append('<a href="%s%s" class="related-lookup" id="lookup_id_%s" onclick="return showRelatedObjectLookupPopup(this, %s);"> '
% (related_url, url, name ,readonly))
extra.append('<img src="%s" width="16" height="16" alt="%s" /></a>'
% (static('admin/img/selector-search.gif'), _('Lookup')))
output = [super(ForeignKeyRawIdWidget, self).render(name, value, attrs)] + extra
if value:
return mark_safe(''.join(output))
function showRelatedObjectLookupPopup(triggeringLink, readonly) {
var name =^lookup_/, '');
name = id_to_windowname(name);
var href;
href = triggeringLink.href
if (\?/) >= 0) {
href = href.replace(/\?/, 'readonly/?');
href = href + '&pop=1';
} else {
href = href + 'readonly/'
href = href + '?pop=1';
var win =, name, 'height=500,width=800,resizable=yes,scrollbars=yes');
return false;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.