Index: django/db/models/options.py
===================================================================
--- django/db/models/options.py (revision 11372)
+++ django/db/models/options.py (working copy)
@@ -412,9 +412,12 @@
cache[obj] = parent
else:
cache[obj] = model
+ from django.contrib.contenttypes import generic
for klass in get_models():
for f in klass._meta.local_many_to_many:
- if f.rel and not isinstance(f.rel.to, str) and self == f.rel.to._meta:
+ if f.rel and isinstance(f, generic.GenericRelation) and self == klass._meta:
+ cache[RelatedObject(klass, f.rel.to, f)] = None
+ elif f.rel and not isinstance(f.rel.to, str) and self == f.rel.to._meta:
cache[RelatedObject(f.rel.to, klass, f)] = None
if app_cache_ready():
self._related_many_to_many_cache = cache
Index: django/contrib/admin/util.py
===================================================================
--- django/contrib/admin/util.py (revision 11372)
+++ django/contrib/admin/util.py (working copy)
@@ -71,7 +71,7 @@
except NoReverseMatch:
return '%s%s/%s/%s/' % ('../'*levels_to_root, app_label, module_name, pk)
-def get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current_depth, admin_site, levels_to_root=4):
+def get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current_depth, admin_site, levels_to_root=4, objs_seen=[]):
"""
Helper function that recursively populates deleted_objects.
@@ -86,18 +86,23 @@
if current_depth > 16:
return # Avoid recursing too deep.
opts_seen = []
+ if current_depth == 1:
+ objs_seen = [] # avoid to have the older objs_seen
for related in opts.get_all_related_objects():
has_admin = related.model in admin_site._registry
- if related.opts in opts_seen:
+ rel_opts_name = related.get_accessor_name()
+ if rel_opts_name in opts_seen:
continue
- opts_seen.append(related.opts)
- rel_opts_name = related.get_accessor_name()
+ opts_seen.append(rel_opts_name)
if isinstance(related.field.rel, models.OneToOneRel):
try:
sub_obj = getattr(obj, rel_opts_name)
except ObjectDoesNotExist:
pass
else:
+ if sub_obj in objs_seen:
+ continue # avoid to have the same object
+ objs_seen.append(sub_obj)
if has_admin:
p = '%s.%s' % (related.opts.app_label, related.opts.get_delete_permission())
if not user.has_perm(p):
@@ -109,6 +114,13 @@
# admin or is edited inline.
nh(deleted_objects, current_depth,
[u'%s: %s' % (capfirst(related.opts.verbose_name), force_unicode(sub_obj)), []])
+ elif has_admin and hasattr(admin_site._registry[related.model], 'get_delete_confirmation_message'):
+ # Display a link to the admin page but use an admin function in order to change the message, obj and link
+ result_msg = admin_site._registry[related.model].get_delete_confirmation_message(
+ u'%s' % capfirst(related.opts.verbose_name), related.opts.app_label, sub_obj, admin_site, levels_to_root)
+ if not result_msg:
+ continue
+ nh(deleted_objects, current_depth, [mark_safe(result_msg), []])
else:
# Display a link to the admin page.
nh(deleted_objects, current_depth, [mark_safe(u'%s: %s' %
@@ -119,16 +131,26 @@
admin_site,
levels_to_root),
escape(sub_obj))), []])
- get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2, admin_site)
+ get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2, admin_site, objs_seen=objs_seen)
else:
has_related_objs = False
for sub_obj in getattr(obj, rel_opts_name).all():
+ if sub_obj in objs_seen:
+ continue # avoid to have the same object
+ objs_seen.append(sub_obj)
has_related_objs = True
if not has_admin:
# Don't display link to edit, because it either has no
# admin or is edited inline.
nh(deleted_objects, current_depth,
[u'%s: %s' % (capfirst(related.opts.verbose_name), force_unicode(sub_obj)), []])
+ elif has_admin and hasattr(admin_site._registry[related.model], 'get_delete_confirmation_message'):
+ # Display a link to the admin page but use an admin function in order to change the message, obj and link
+ result_msg = admin_site._registry[related.model].get_delete_confirmation_message(
+ u'%s' % capfirst(related.opts.verbose_name), related.opts.app_label, sub_obj, admin_site, levels_to_root)
+ if not result_msg:
+ continue
+ nh(deleted_objects, current_depth, [mark_safe(result_msg), []])
else:
# Display a link to the admin page.
nh(deleted_objects, current_depth, [mark_safe(u'%s: %s' %
@@ -139,34 +161,48 @@
admin_site,
levels_to_root),
escape(sub_obj))), []])
- get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2, admin_site)
+ get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, related.opts, current_depth+2, admin_site, objs_seen=objs_seen)
# If there were related objects, and the user doesn't have
# permission to delete them, add the missing perm to perms_needed.
if has_admin and has_related_objs:
p = '%s.%s' % (related.opts.app_label, related.opts.get_delete_permission())
if not user.has_perm(p):
perms_needed.add(related.opts.verbose_name)
+ from django.contrib.contenttypes import generic
for related in opts.get_all_related_many_to_many_objects():
has_admin = related.model in admin_site._registry
- if related.opts in opts_seen:
+ rel_opts_name = related.get_accessor_name()
+ if rel_opts_name in opts_seen:
continue
- opts_seen.append(related.opts)
- rel_opts_name = related.get_accessor_name()
+ opts_seen.append(rel_opts_name)
has_related_objs = False
# related.get_accessor_name() could return None for symmetrical relationships
if rel_opts_name:
- rel_objs = getattr(obj, rel_opts_name, None)
+ rel_objs = getattr(obj, rel_opts_name, None) or \
+ (isinstance(related.field, generic.GenericRelation) and getattr(obj, related.field.verbose_name, None))
if rel_objs:
has_related_objs = True
if has_related_objs:
for sub_obj in rel_objs.all():
+ if sub_obj in objs_seen:
+ continue # avoid to have the same object
+ objs_seen.append(sub_obj)
if not has_admin:
# Don't display link to edit, because it either has no
# admin or is edited inline.
nh(deleted_objects, current_depth, [_('One or more %(fieldname)s in %(name)s: %(obj)s') % \
{'fieldname': force_unicode(related.field.verbose_name), 'name': force_unicode(related.opts.verbose_name), 'obj': escape(sub_obj)}, []])
+ elif has_admin and hasattr(admin_site._registry[related.model], 'get_delete_confirmation_message'):
+ # Display a link to the admin page but use an admin function in order to change the message, obj and link
+ result_msg = admin_site._registry[related.model].get_delete_confirmation_message(
+ (_('One or more %(fieldname)s in %(name)s:') % \
+ {'fieldname': escape(force_unicode(related.field.verbose_name)), 'name': escape(force_unicode(related.opts.verbose_name))}),
+ related.opts.app_label, sub_obj, admin_site, levels_to_root)
+ if not result_msg:
+ continue
+ nh(deleted_objects, current_depth, [mark_safe(result_msg), []])
else:
# Display a link to the admin page.
nh(deleted_objects, current_depth, [