def delete_survey(request, sid): survey = get_object_or_404(Engagement_Survey, id=sid) form = Delete_Eng_Survey_Form(instance=survey) from django.contrib.admin.util import NestedObjects from django.db import DEFAULT_DB_ALIAS collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([survey]) rels = collector.nested() if request.method == 'POST': if 'id' in request.POST and str(survey.id) == request.POST['id']: form = Delete_Eng_Survey_Form(request.POST, instance=survey) if form.is_valid(): survey.delete() messages.add_message(request, messages.SUCCESS, 'Survey and relationships removed.', extra_tags='alert-success') return HttpResponseRedirect(reverse('survey')) add_breadcrumb(title="Delete Survey", top_level=False, request=request) return render(request, 'defectDojo-engagement-survey/delete_survey.html', { 'survey': survey, 'form': form, 'rels': rels, })
def ui_model_delete_device(request, umdid): uim_device = UIModelDevice.objects.get(id=umdid) if uim_device.uimodel.is_frozen: return render(request, 'mcms/uimodels/frozen.html', dict(uimodel=uim_device.uimodel)) collector = NestedObjects(using='default') collector.collect([uim_device, ]) to_delete = collector.nested() if request.method == 'POST': # The user has already confirmed the delete. uim_device.delete() return HttpResponseRedirect( reverse('mcms:uimodels:details', kwargs={ 'mid': uim_device.uimodel.id})) opts = uim_device._meta object_name = force_unicode(opts.verbose_name) title = ('Are you sure?') context = { 'title': title, 'object_name': object_name, 'object': uim_device, 'deleted_objects': to_delete, 'opts': opts, 'app_label': opts.app_label, } return TemplateResponse( request, 'mcms/uimodels/delete_confirmation.html', context, current_app='Test')
def viewmap_delete_page(request, vsid): page = get_object_or_404(Page, pk=vsid) uim = page.uimodel if uim.is_frozen: return render(request, 'mcms/uimodels/frozen.html', dict(uimodel=uim)) collector = NestedObjects(using='default') collector.collect([page]) to_delete = collector.nested() if request.method == 'POST': page.delete() return HttpResponseRedirect(reverse('mcms:uimodels:edit_viewmap', kwargs={'vmid': page.viewmap.id})) opts = page._meta object_name = force_unicode(opts.verbose_name) title = ('Are you sure?') context = { 'title': title, 'object_name': object_name, 'object': page, 'deleted_objects': to_delete, 'opts': opts, 'app_label': opts.app_label, } return TemplateResponse( request, 'mcms/uimodels/delete_confirmation.html', context, current_app='Test')
def hand_clean_DELETE(self): """ We don't validate the 'DELETE' field itself because on templates it's not rendered using the field information, but just using a generic "deletion_field" of the InlineModelAdmin. """ if self.cleaned_data.get(DELETION_FIELD_NAME, False): collector = NestedObjects() collector.collect([self.instance]) if collector.protected: objs = [] for p in collector.protected: objs.append( # Translators: Model verbose name and instance representation, suitable to be an item in a list _('%(class_name)s %(instance)s') % { 'class_name': p._meta.verbose_name, 'instance': p} ) params = {'class_name': self._meta.model._meta.verbose_name, 'instance': self.instance, 'related_objects': get_text_list(objs, _('and'))} msg = _("Deleting %(class_name)s %(instance)s would require " "deleting the following protected related objects: " "%(related_objects)s") raise ValidationError(msg, code='deleting_protected', params=params)
def get_context_data(self, **kwargs): context = super(EmplacementDelete, self).get_context_data(**kwargs) collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([context['object']]) deleted_elements = collector.nested() context['deleted_elements'] = deleted_in_str(deleted_elements) return context
def deletion_tree(obj): ''' Return a list of all nested objects that will be deleted ''' collector = NestedObjects(using=obj._state.db) collector.collect([obj]) def format_callback(obj): return u'%s: %s' % (force_text(obj._meta.verbose_name), force_text(obj)) return collector.nested(format_callback)
def delete_engagement(request, eid): engagement = get_object_or_404(Engagement, pk=eid) product = engagement.product form = DeleteEngagementForm(instance=engagement) from django.contrib.admin.util import NestedObjects from django.db import DEFAULT_DB_ALIAS collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([engagement]) rels = collector.nested() if request.method == 'POST': if 'id' in request.POST and str(engagement.id) == request.POST['id']: form = DeleteEngagementForm(request.POST, instance=engagement) if form.is_valid(): del engagement.tags engagement.delete() messages.add_message(request, messages.SUCCESS, 'Engagement and relationships removed.', extra_tags='alert-success') return HttpResponseRedirect( reverse('view_product', args=(product.id, ))) add_breadcrumb(parent=engagement, title="Delete", top_level=False, request=request) return render(request, 'dojo/delete_engagement.html', { 'engagement': engagement, 'form': form, 'rels': rels, })
def delete_survey(request, sid): survey = get_object_or_404(Engagement_Survey, id=sid) form = Delete_Eng_Survey_Form(instance=survey) from django.contrib.admin.util import NestedObjects from django.db import DEFAULT_DB_ALIAS collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([survey]) rels = collector.nested() if request.method == 'POST': if 'id' in request.POST and str(survey.id) == request.POST['id']: form = Delete_Eng_Survey_Form(request.POST, instance=survey) if form.is_valid(): survey.delete() messages.add_message(request, messages.SUCCESS, 'Survey and relationships removed.', extra_tags='alert-success') return HttpResponseRedirect(reverse('survey')) add_breadcrumb(title="Delete Survey", top_level=False, request=request) return render(request, 'defectDojo-engagement-survey/delete_survey.html', {'survey': survey, 'form': form, 'rels': rels, })
def delete_engagement(request, eid): engagement = get_object_or_404(Engagement, pk=eid) product = engagement.product form = DeleteEngagementForm(instance=engagement) from django.contrib.admin.util import NestedObjects from django.db import DEFAULT_DB_ALIAS collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([engagement]) rels = collector.nested() if request.method == 'POST': if 'id' in request.POST and str(engagement.id) == request.POST['id']: form = DeleteEngagementForm(request.POST, instance=engagement) if form.is_valid(): engagement.delete() messages.add_message(request, messages.SUCCESS, 'Engagement and relationships removed.', extra_tags='alert-success') return HttpResponseRedirect(reverse('view_product', args=(product.id,))) add_breadcrumb(parent=engagement, title="Delete", top_level=False, request=request) return render(request, 'dojo/delete_engagement.html', {'engagement': engagement, 'form': form, 'rels': rels, })
def get_related_objects( obj, to_update=DEFAULT_TO_UPDATE ): collector = NestedObjects( using=DEFAULT_DB_ALIAS ) collector.collect( obj ) perms_needed = set() def format_callback( o ): opts = o._meta key = '%s_%s' % ( opts.app_label, opts.object_name.lower() ) to_delete = key not in to_update try: admin_url = reverse( 'admin:%s_%s_change' % ( opts.app_label, opts.object_name.lower() ), None, ( o._get_pk_val() ,) ) except: return mark_safe( u'%s%s: %s%s' % ( '<strike>' if to_delete else '', capfirst( opts.verbose_name ), force_unicode( o ), '</strike>' if to_delete else '' ) ) try: name = escape( str( o ) ) except Exception, e: print e name = 'None' return mark_safe( u'%s%s: <a href="%s">%s</a>%s' % ( '<strike>' if to_delete else '', escape( capfirst( opts.verbose_name ) ), admin_url, name, '</strike>' if to_delete else '' ) )
def get_deleted_objects(obj, user): """ Find all objects related to ``obj`` that should also be deleted. Returns a nested list of strings suitable for display in the template with the ``unordered_list`` filter. Copied and updated from django.contrib.admin.util for front end display. """ using = router.db_for_write(obj.__class__) collector = NestedObjects(using=using) collector.collect([obj]) perms_needed = set() def format_callback(obj): opts = obj._meta if hasattr(obj, "get_absolute_url"): url = obj.get_absolute_url() p = "%s.%s" % (opts.app_label, opts.get_delete_permission()) if not user.has_perm(p): perms_needed.add(opts.verbose_name) # Display a link to the admin page. return mark_safe(u'%s: <a href="%s">%s</a>' % (escape(capfirst(opts.verbose_name)), url, escape(obj))) else: # no link return u"%s: %s" % (capfirst(opts.verbose_name), force_unicode(obj)) to_delete = collector.nested(format_callback) protected = [format_callback(obj) for obj in collector.protected] return to_delete, perms_needed, protected
def delete_user(request, uid): user = get_object_or_404(Dojo_User, id=uid) form = DeleteUserForm(instance=user) from django.contrib.admin.util import NestedObjects from django.db import DEFAULT_DB_ALIAS collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([user]) rels = collector.nested() if user.id == request.user.id: messages.add_message(request, messages.ERROR, 'You may not delete yourself.', extra_tags='alert-danger') return HttpResponseRedirect(reverse('edit_user', args=(user.id, ))) if request.method == 'POST': if 'id' in request.POST and str(user.id) == request.POST['id']: form = DeleteUserForm(request.POST, instance=user) if form.is_valid(): user.delete() messages.add_message(request, messages.SUCCESS, 'User and relationships removed.', extra_tags='alert-success') return HttpResponseRedirect(reverse('users')) add_breadcrumb(title="Delete User", top_level=False, request=request) return render(request, 'dojo/delete_user.html', { 'to_delete': user, 'form': form, 'rels': rels, })
def hand_clean_DELETE(self): """ We don't validate the 'DELETE' field itself because on templates it's not rendered using the field information, but just using a generic "deletion_field" of the InlineModelAdmin. """ if self.cleaned_data.get(DELETION_FIELD_NAME, False): collector = NestedObjects() collector.collect([self.instance]) if collector.protected: objs = [] for p in collector.protected: objs.append( # Translators: Model verbose name and instance # representation, suitable to be an item in a # list _('%(class_name)s %(instance)s') % { 'class_name': p._meta.verbose_name, 'instance': p }) params = { 'class_name': self._meta.model._meta.verbose_name, 'instance': self.instance, 'related_objects': get_text_list(objs, _('and')) } msg = _( "Deleting %(class_name)s %(instance)s would require " "deleting the following protected related objects: " "%(related_objects)s") raise ValidationError(msg, code='deleting_protected', params=params)
def get_context_data(self, *args, **kwargs): context = super(DeleteView, self).get_context_data(*args, **kwargs) using = router.db_for_write(self.model) collector = NestedObjects(using=using) collector.collect([self.object]) context["related_objects"] = collector.nested() return context
def delete_user(request, uid): user = get_object_or_404(Dojo_User, id=uid) form = DeleteUserForm(instance=user) from django.contrib.admin.util import NestedObjects from django.db import DEFAULT_DB_ALIAS collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([user]) rels = collector.nested() if user.id == request.user.id: messages.add_message(request, messages.ERROR, 'You may not delete yourself.', extra_tags='alert-danger') return HttpResponseRedirect(reverse('edit_user', args=(user.id,))) if request.method == 'POST': if 'id' in request.POST and str(user.id) == request.POST['id']: form = DeleteUserForm(request.POST, instance=user) if form.is_valid(): user.delete() messages.add_message(request, messages.SUCCESS, 'User and relationships removed.', extra_tags='alert-success') return HttpResponseRedirect(reverse('users')) add_breadcrumb(title="Delete User", top_level=False, request=request) return render(request, 'dojo/delete_user.html', {'to_delete': user, 'form': form, 'rels': rels, })
def deepcopy(self, newname, value=None, field=None, duplicate_order=None): """ Duplicate all related objects of obj setting field to value. If one of the duplicate objects has an FK to another duplicate object update that as well. Return the duplicate copy of obj. duplicate_order is a list of models which specify how the duplicate objects are saved. For complex objects this can matter. Check to save if objects are being saved correctly and if not just pass in related objects in the order that they should be saved. """ obj = self collector = NestedObjects(using='default') collector.collect([obj]) collector.sort() related_models = collector.data.keys() data_snapshot = {} for key in collector.data.keys(): data_snapshot.update({ key: dict( zip([item.pk for item in collector.data[key]], [item for item in collector.data[key]])) }) root_obj = None # Sometimes it's good enough just to save in reverse deletion order. if duplicate_order is None: duplicate_order = reversed(related_models) for model in duplicate_order: # Find all FKs on model that point to a related_model. fks = [] for f in model._meta.fields: if isinstance(f, ForeignKey) and f.rel.to in related_models: fks.append(f) # Replace each `sub_obj` with a duplicate. if model not in collector.data: continue sub_objects = collector.data[model] for obj in sub_objects: for fk in fks: fk_value = getattr(obj, "%s_id" % fk.name) # If this FK has been duplicated then point to the duplicate. fk_rel_to = data_snapshot[fk.rel.to] if fk_value in fk_rel_to: dupe_obj = fk_rel_to[fk_value] setattr(obj, fk.name, dupe_obj) # Duplicate the object and save it. obj.id = None if field is not None: setattr(obj, field, value) obj.save() if root_obj is None: root_obj = obj setattr(root_obj, 'name', newname) return root_obj
def get_related_objects(self, flatten=False): """ Returns all objects related to the current. """ collector = NestedObjects(using='default') collector.collect([self]) if flatten: return list(utils.flatten(collector.nested())) return collector.nested()
def get_context_data(self, **kwargs): context = super(OrganizationDelete, self).get_context_data(**kwargs) collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([context['object']]) deleted_elements = collector.nested() context['deleted_elements'] = deleted_in_str(deleted_elements) context['title'] = _("Organization") context['model'] = "organization" return context
def test_on_delete_do_nothing(self): """ Check that the nested collector doesn't query for DO_NOTHING objects. """ n = NestedObjects(using=DEFAULT_DB_ALIAS) objs = [Event.objects.create()] EventGuide.objects.create(event=objs[0]) with self.assertNumQueries(2): # One for Location, one for Guest, and no query for EventGuide n.collect(objs)
def list_deleted_objects(obj): model = obj.__class__ def format_callback(item): return u'%s: %s' % (item._meta.verbose_name.capitalize(), force_unicode(item)) collector = NestedObjects(using=router.db_for_write(model)) collector.collect(model.objects.filter(id=obj.id)) to_delete = collector.nested(format_callback) return {'to_delete': to_delete}
def deepcopy(self, newname, value=None, field=None, duplicate_order=None): """ Duplicate all related objects of obj setting field to value. If one of the duplicate objects has an FK to another duplicate object update that as well. Return the duplicate copy of obj. duplicate_order is a list of models which specify how the duplicate objects are saved. For complex objects this can matter. Check to save if objects are being saved correctly and if not just pass in related objects in the order that they should be saved. """ obj = self collector = NestedObjects(using='default') collector.collect([obj]) collector.sort() related_models = collector.data.keys() data_snapshot = {} for key in collector.data.keys(): data_snapshot.update({ key: dict(zip([item.pk for item in collector.data[key]], [item for item in collector.data[key]])) }) root_obj = None # Sometimes it's good enough just to save in reverse deletion order. if duplicate_order is None: duplicate_order = reversed(related_models) for model in duplicate_order: # Find all FKs on model that point to a related_model. fks = [] for f in model._meta.fields: if isinstance(f, ForeignKey) and f.rel.to in related_models: fks.append(f) # Replace each `sub_obj` with a duplicate. if model not in collector.data: continue sub_objects = collector.data[model] for obj in sub_objects: for fk in fks: fk_value = getattr(obj, "%s_id" % fk.name) # If this FK has been duplicated then point to the duplicate. fk_rel_to = data_snapshot[fk.rel.to] if fk_value in fk_rel_to: dupe_obj = fk_rel_to[fk_value] setattr(obj, fk.name, dupe_obj) # Duplicate the object and save it. obj.id = None if field is not None: setattr(obj, field, value) obj.save() if root_obj is None: root_obj = obj setattr(root_obj, 'name', newname) return root_obj
def make_mobile_numbers_unique(users): for user in users: collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([user]) items = collector.nested() for item in items: if isinstance(item, User): continue else: unique_instances = set([str(instance.__class__.__name__) for instance in item]) if len(unique_instances) == 1: pass
def make_mobile_numbers_unique(users): for user in users: collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([user]) items = collector.nested() for item in items: if isinstance(item, User): continue else: unique_instances = set( [str(instance.__class__.__name__) for instance in item]) if len(unique_instances) == 1: pass
class NestedObjectsTests(TestCase): """ Tests for ``NestedObject`` utility collection. """ def setUp(self): self.n = NestedObjects(using=DEFAULT_DB_ALIAS) self.objs = [Count.objects.create(num=i) for i in range(5)] def _check(self, target): self.assertEqual(self.n.nested(lambda obj: obj.num), target) def _connect(self, i, j): self.objs[i].parent = self.objs[j] self.objs[i].save() def _collect(self, *indices): self.n.collect([self.objs[i] for i in indices]) def test_unrelated_roots(self): self._connect(2, 1) self._collect(0) self._collect(1) self._check([0, 1, [2]]) def test_siblings(self): self._connect(1, 0) self._connect(2, 0) self._collect(0) self._check([0, [1, 2]]) def test_non_added_parent(self): self._connect(0, 1) self._collect(0) self._check([0]) def test_cyclic(self): self._connect(0, 2) self._connect(1, 0) self._connect(2, 1) self._collect(0) self._check([0, [1, [2]]]) def test_queries(self): self._connect(1, 0) self._connect(2, 0) # 1 query to fetch all children of 0 (1 and 2) # 1 query to fetch all children of 1 and 2 (none) # Should not require additional queries to populate the nested graph. self.assertNumQueries(2, self._collect, 0)
def related_objects(obj): """ Return a generator to the objects that would be deleted if we delete "obj" (excluding obj) """ collector = NestedObjects(using=router.db_for_write(obj)) collector.collect([obj]) def flatten(elem): if isinstance(elem, list): return itertools.chain.from_iterable(map(flatten, elem)) elif obj != elem: return (elem,) return () return flatten(collector.nested())
def related_objects(obj): """ Return a generator to the objects that would be deleted if we delete "obj" (excluding obj) """ collector = NestedObjects(using=router.db_for_write(obj)) collector.collect([obj]) def flatten(elem): if isinstance(elem, list): return itertools.chain.from_iterable(map(flatten, elem)) elif obj != elem: return (elem, ) return () return flatten(collector.nested())
def _get_deleted_objects(objs): """ Slightly simplified version of the function used in the standard admin """ if len(objs) == 0: return [] collector = NestedObjects(using=router.db_for_write(objs[0])) collector.collect(objs) def format_callback(obj): return '%s: %s' % (obj.__class__.__name__, unicode(obj)) return collector.nested(format_callback)
def get_deleted_objects(objs, opts, user, admin_site, using): collector = NestedObjects(using=using) collector.collect(objs) perms_needed = set() def clean_empty(objects): cleaned = [] for obj in objects: if isinstance(obj, list): cleaned.append(clean_empty(obj)) elif obj != '': cleaned.append(obj) return cleaned if cleaned else '"Hidden objects"' def format_callback(obj): has_admin = admin_site._registry.get(obj.__class__, False) opts = obj._meta if has_admin: if getattr(has_admin, 'deletable_objects_excluded', False): return '' admin_url = reverse('%s:%s_%s_change' % (admin_site.name, opts.app_label, opts.object_name.lower()), None, (quote(obj._get_pk_val()),)) p = '%s.%s' % (opts.app_label, opts.get_delete_permission()) if not user.has_perm(p): perms_needed.add(opts.verbose_name) # Display a link to the admin page. # NOTE: Define as unicode for avoid errors when obj # representation contains non-ascii chars return format_html(u'{0}: <a href="{1}">{2}</a>', capfirst(opts.verbose_name), admin_url, obj) else: # Don't display link to edit, because it either has no # admin or is edited inline. return u'%s: %s' % (capfirst(opts.verbose_name), force_text(obj)) to_delete = collector.nested(format_callback) to_delete = clean_empty(to_delete) protected = [format_callback(obj) for obj in collector.protected] return to_delete, perms_needed, protected
def signal_reciever(self, message, **kwargs): """Collect related models to instance and for widgets call widget_update in message expect: singal_name: string instance: instance created: bool """ instance = self.get_instance( message.content['sender'], message.content['instance']) # page related are different because regions can inherit from parent # that means it's not directly connected to this page if instance.__class__.__name__ == "Page": regions = instance.content._fetch_regions() for region, instances in regions.items(): for widget in instances: msg = { "widget": widget, "sender": widget.__class__, "path": "/widgets/update"} self.sender.send("http.request", msg) return # get related models and refresh it collector = NestedObjects( using=instance._state.db) # database name collector.collect([instance]) for w_cls, models in collector.data.items(): if hasattr(w_cls, 'parent') and hasattr(w_cls, 'fe_identifier'): for w in models: msg = { "widget": w, "sender": w_cls, "path": "/widgets/update"} self.sender.send("http.request", msg)
def collect_backlinks(model_instance): from django.contrib.admin.util import NestedObjects collector = NestedObjects(using='scenario_db') # or specific database collector.collect([model_instance]) dependants = collector.nested() # fun fact: spelling differs between America and Brittain print("Found related models:", dependants) links = {} if len(dependants[1:]): for direct_reference in dependants[1:][0]: # only iterates over the top level if not isinstance(direct_reference, list) and not isinstance(direct_reference, RelationalPoint): # Points are obvious, don't include them name = direct_reference.__class__.__name__ try: # not everything has a name attr links[str(direct_reference)] = '/setup/%s/%i/' % (name, direct_reference.pk) except: links['%s:%i' % (name, direct_reference.pk)] = \ '/setup/%s/%i/' % (name, direct_reference.pk) print(links) return links
def related_not_authorized(self, user, objs): """ Find all objects related to ``objs`` that should also be deleted. ``objs`` must be a homogenous iterable of objects (e.g. a QuerySet). """ collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect(objs) blocked = [] for model, obj in collector.instances_with_model(): admin_instance = admin.site._registry.get(obj.__class__, None) if admin_instance and hasattr(admin_instance, 'is_authorized'): if not admin_instance.is_authorized(user, obj): blocked += [obj] return blocked
def get_deleted_objects(objs): from django.contrib.admin.util import NestedObjects from django.utils.text import capfirst from django.utils.encoding import force_unicode def format_callback(obj): opts = obj._meta # Don't display link to edit, because it either has no # admin or is edited inline. return u'%s: %s' % (capfirst(opts.verbose_name), force_unicode(obj)) collector = NestedObjects(using='default') # or specific database # collector = NestedObjects(using=using) collector.collect(objs) return collector.nested(format_callback)
def will_be_deleted_with(obj): """ Pass in any Django model object that you intend to delete. This will iterate over all the model classes that would be affected by the deletion, yielding a two-tuple: the model class, and a set of all the objects of that type that would be deleted. """ collector = NestedObjects(using="default") collector.collect([obj]) # the collector returns a list of all objects in the database that # would be deleted if `obj` were deleted. for cls, list_of_items_to_be_deleted in collector.data.items(): # remove obj itself from the list if cls == obj.__class__: list_of_items_to_be_deleted = set(item for item in list_of_items_to_be_deleted if item.pk != obj.pk) if len(list_of_items_to_be_deleted) == 0: continue yield cls, list_of_items_to_be_deleted
def get_deleted_objects(objs, user): """ Find all objects related to ``objs`` that should also be deleted. ``objs`` must be a homogeneous iterable of objects (e.g. a QuerySet). Returns a nested list of strings suitable for display in the template with the ``unordered_list`` filter. Copied and updated from django.contrib.admin.utils for front end display. """ using = router.db_for_write(objs[0].__class__) collector = NestedObjects(using=using) collector.collect(objs) perms_needed = set() def format_callback(obj): opts = obj._meta no_edit_link = '%s: %s' % (capfirst(opts.verbose_name), force_text(obj)) p = '%s.%s' % (opts.app_label, get_permission_codename('delete', opts)) if not user.has_perm(p): perms_needed.add(opts.verbose_name) if hasattr(obj, 'get_absolute_url'): url = obj.get_absolute_url() # Display a link to the admin page. return format_html('{}: <a href="{}">{}</a>', capfirst(opts.verbose_name), url, obj) else: # Don't display link to edit, because it either has no # admin or is edited inline. return no_edit_link to_delete = collector.nested(format_callback) protected = [format_callback(obj) for obj in collector.protected] return to_delete, collector.model_count, perms_needed, protected
def get_deleted_objects(objs, opts, user, admin_site, using): """ Find all objects related to ``objs`` that should also be deleted. ``objs`` must be a homogenous iterable of objects (e.g. a QuerySet). Returns a nested list of strings suitable for display in the template with the ``unordered_list`` filter. """ collector = NestedObjects(using=using) collector.collect(objs) perms_needed = set() def format_callback(obj): has_admin = obj.__class__ in admin_site._registry opts = obj._meta if has_admin: admin_url = reverse('%s_%s_edit' % ( opts.app_label, opts.object_name.lower()), None, (quote(obj._get_pk_val()),)) p = '%s_%s_delete' % (opts.app_label,opts.object_name.lower()) if not user.has_perm(p): perms_needed.add(opts.verbose_name) # Display a link to the admin page. return format_html(u'{0}: <a href="{1}">{2}</a>', capfirst(opts.verbose_name), admin_url, obj) else: # Don't display link to edit, because it either has no # admin or is edited inline. return '%s: %s' % (capfirst(opts.verbose_name), force_text(obj)) to_delete = collector.nested(format_callback) protected = [format_callback(obj) for obj in collector.protected] return to_delete, perms_needed, protected
def delete_bastard(obj): """ Delete the bastard obj, user or subscriber. Don't delete if cascade will delete a protected object, in this case alert. """ collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([obj]) def candelete(item): if isinstance(item, Iterable): return all([candelete(i) for i in item]) elif type(item) in PROTECTED_CLASSES: print("%s not deleted because has %s" % (obj, item)) return False else: return True if candelete(collector.nested()): print("Deleting %s and %s" % (obj, collector.nested())) obj.delete()
def reportwhatwillbedeleted(request, report): if not request.user.is_authenticated() or not hasPermission( request.user, 'moderate_reports'): raise PermissionDenied() context = ajaxContext(request) # Get the report report = get_object_or_404(models.Report, pk=report, i_status=models.Report.get_i( 'status', 'Pending')) # Get the reported thing queryset = report.reported_thing_collection.queryset thing = get_object_or_404(queryset, pk=report.reported_thing_id) from django.contrib.admin.util import NestedObjects collector = NestedObjects(using='default') # or specific database collector.collect([thing]) context['report'] = report context['to_delete'] = collector.nested() return render(request, 'ajax/reportwhatwillbedeleted.html', context)
def get_deleted_objects(obj, user): """ Find all objects related to ``obj`` that should also be deleted. Returns a nested list of strings suitable for display in the template with the ``unordered_list`` filter. Copied and updated from django.contrib.admin.util for front end display. """ using = router.db_for_write(obj.__class__) collector = NestedObjects(using=using) collector.collect([obj]) perms_needed = set() def format_callback(obj): opts = obj._meta if hasattr(obj, 'get_absolute_url'): url = obj.get_absolute_url() p = '%s.%s' % (opts.app_label, opts.get_delete_permission()) if not user.has_perm(p): perms_needed.add(opts.verbose_name) # Display a link to the admin page. return mark_safe(u'%s: <a href="%s">%s</a>' % (escape(capfirst(opts.verbose_name)), url, escape(obj))) else: # no link return u'%s: %s' % (capfirst(opts.verbose_name), force_unicode(obj)) to_delete = collector.nested(format_callback) protected = [format_callback(obj) for obj in collector.protected] return to_delete, perms_needed, protected
def _collect_deleted_objects(self, obj): result = [] try: # This is for Django up to 1.2 from django.db.models.query_utils import CollectedObjects seen_objs = CollectedObjects() obj._collect_sub_objects(seen_objs) for cls, subobjs in seen_objs.iteritems(): for subobj in subobjs.values(): result.append(subobj) except ImportError: # Django 1.3 solution, those imports needs to be here, because # otherwise they will fail on Django < 1.3. from django.contrib.admin.util import NestedObjects from django.db import router using = router.db_for_write(obj) collector = NestedObjects(using=using) collector.collect([obj]) result = self._flatten(collector.nested()) return result
def get_deleted_objects(objs, opts, user, admin_site, using): """ Find all objects related to ``objs`` that should also be deleted. ``objs`` must be a homogenous iterable of objects (e.g. a QuerySet). Returns a nested list of strings suitable for display in the template with the ``unordered_list`` filter. """ collector = NestedObjects(using=using) collector.collect(objs) perms_needed = set() def format_callback(obj): opts = obj._meta return '%s: %s' % (capfirst(opts.verbose_name), force_text(obj)) to_delete = collector.nested(format_callback) protected = [format_callback(obj) for obj in collector.protected] return to_delete, perms_needed, protected
def delete_related_objects(modeladmin, request, queryset): """ Action that deletes related objects for the selected items. This action first displays a confirmation page whichs shows all the deleteable objects, or, if the user has no permission one of the related childs (foreignkeys), a "permission denied" message. Next, it deletes all related objects and redirects back to the change list. """ opts = modeladmin.model._meta app_label = opts.app_label # Check that the user has delete permission for the actual model if not modeladmin.has_delete_permission(request): raise PermissionDenied using = router.db_for_write(modeladmin.model) first_level_related_objects = [] collector = NestedObjects(using=using) collector.collect(queryset) for base_object_or_related_list in collector.nested(): if type(base_object_or_related_list) is not list: # If it's not a list, it's a base object. Skip it. continue for obj in base_object_or_related_list: if type(obj) is list: # A list here contains related objects for the previous # element. We can skip it since delete() on the first # level of related objects will cascade. continue else: first_level_related_objects.append(obj) # Populate deletable_objects, a data structure of (string representations # of) all related objects that will also be deleted. deletable_objects, model_count, perms_needed, protected = get_deleted_objects( first_level_related_objects, opts, request.user, modeladmin.admin_site, using ) # The user has already confirmed the deletion. # Do the deletion and return a None to display the change list view again. if request.POST.get('post'): if perms_needed: raise PermissionDenied n = 0 with transaction.atomic(using): for obj in first_level_related_objects: obj_display = force_text(obj) modeladmin.log_deletion(request, obj, obj_display) obj.delete() n += 1 modeladmin.message_user( request, _("Successfully deleted %(count)d related objects.") % { "count": n, "items": model_ngettext(modeladmin.opts, n)}, messages.SUCCESS ) # Return None to display the change list page again. return None if len(queryset) == 1: objects_name = force_text(opts.verbose_name) else: objects_name = force_text(opts.verbose_name_plural) if perms_needed or protected: title = _("Cannot delete %(name)s") % {"name": objects_name} else: title = _("Are you sure?") context = dict( modeladmin.admin_site.each_context(request), title=title, objects_name=objects_name, deletable_objects=[deletable_objects], model_count=dict(model_count).items(), queryset=queryset, perms_lacking=perms_needed, protected=protected, opts=opts, action_checkbox_name=helpers.ACTION_CHECKBOX_NAME, ) request.current_app = modeladmin.admin_site.name # Display the confirmation page return TemplateResponse( request, "delete_related_for_selected_confirmation.html", context, current_app=modeladmin.admin_site.name)
def get_context_data(self, **kwargs): collector = NestedObjects(using=DEFAULT_DB_ALIAS) collector.collect([self.object]) kwargs.setdefault('nested_objects', collector.nested()) return super(TreeDeleteView, self).get_context_data(**kwargs)
def get_nested_objects(obj): from django.contrib.admin.util import NestedObjects collector = NestedObjects(using='default') collector.collect([obj]) return collector.nested()
def delete_related_objects(modeladmin, request, queryset): """ Action that deletes related objects for the selected items. This action first displays a confirmation page whichs shows all the deleteable objects, or, if the user has no permission one of the related childs (foreignkeys), a "permission denied" message. Next, it deletes all related objects and redirects back to the change list. """ opts = modeladmin.model._meta app_label = opts.app_label # Check that the user has delete permission for the actual model if not modeladmin.has_delete_permission(request): raise PermissionDenied using = router.db_for_write(modeladmin.model) first_level_related_objects = [] first_level_related_objects_by_type = defaultdict(list) collector = NestedObjects(using=using) collector.collect(queryset) for base_object_or_related_list in collector.nested(): if type(base_object_or_related_list) is not list: # If it's not a list, it's a base object. Skip it. continue for obj in base_object_or_related_list: if type(obj) is list: # A list here contains related objects for the previous # element. We can skip it since delete() on the first # level of related objects will cascade. continue else: first_level_related_objects.append(obj) first_level_related_objects_by_type[type(obj)].append(obj) deletable_objects = [] model_count = defaultdict(int) perms_needed = set() protected = [] # `get_deleted_objects()` fails when passed a heterogeneous list like # `first_level_related_objects`, so embark on this rigmarole to spoon-feed # it only homogeneous lists for homogeneous_list in first_level_related_objects_by_type.values(): # Populate deletable_objects, a data structure of (string # representations of) all related objects that will also be deleted. deletable_objects_, model_count_, perms_needed_, protected_ = get_deleted_objects( homogeneous_list, opts, request.user, modeladmin.admin_site, using) # Combine the results with those from previous homogeneous lists deletable_objects.extend(deletable_objects_) for k, v in model_count_.iteritems(): model_count[k] += v perms_needed.update(perms_needed_) protected.extend(protected_) # The user has already confirmed the deletion. # Do the deletion and return a None to display the change list view again. if request.POST.get('post'): if perms_needed: raise PermissionDenied n = 0 with transaction.atomic(using): for obj in first_level_related_objects: obj_display = force_text(obj) modeladmin.log_deletion(request, obj, obj_display) obj.delete() n += 1 modeladmin.message_user( request, _("Successfully deleted %(count)d related objects.") % { "count": n, "items": model_ngettext(modeladmin.opts, n) }, messages.SUCCESS) # Return None to display the change list page again. return None if len(queryset) == 1: objects_name = force_text(opts.verbose_name) else: objects_name = force_text(opts.verbose_name_plural) if perms_needed or protected: title = _("Cannot delete %(name)s") % {"name": objects_name} else: title = _("Are you sure?") context = dict( modeladmin.admin_site.each_context(request), title=title, objects_name=objects_name, deletable_objects=[deletable_objects], model_count=dict(model_count).items(), queryset=queryset, perms_lacking=perms_needed, protected=protected, opts=opts, action_checkbox_name=helpers.ACTION_CHECKBOX_NAME, ) request.current_app = modeladmin.admin_site.name # Display the confirmation page return TemplateResponse(request, "delete_related_for_selected_confirmation.html", context, current_app=modeladmin.admin_site.name)
def get_related_objects(obj): collector = NestedObjects(using='default') collector.collect(obj) return [(model._meta.verbose_name_plural, instance) for model, instance in collector.instances_with_model()]
class NestedObjectsTests(TestCase): """ Tests for ``NestedObject`` utility collection. """ def setUp(self): self.n = NestedObjects(using=DEFAULT_DB_ALIAS) self.objs = [Count.objects.create(num=i) for i in range(5)] def _check(self, target): self.assertEqual(self.n.nested(lambda obj: obj.num), target) def _connect(self, i, j): self.objs[i].parent = self.objs[j] self.objs[i].save() def _collect(self, *indices): self.n.collect([self.objs[i] for i in indices]) def test_unrelated_roots(self): self._connect(2, 1) self._collect(0) self._collect(1) self._check([0, 1, [2]]) def test_siblings(self): self._connect(1, 0) self._connect(2, 0) self._collect(0) self._check([0, [1, 2]]) def test_non_added_parent(self): self._connect(0, 1) self._collect(0) self._check([0]) def test_cyclic(self): self._connect(0, 2) self._connect(1, 0) self._connect(2, 1) self._collect(0) self._check([0, [1, [2]]]) def test_queries(self): self._connect(1, 0) self._connect(2, 0) # 1 query to fetch all children of 0 (1 and 2) # 1 query to fetch all children of 1 and 2 (none) # Should not require additional queries to populate the nested graph. self.assertNumQueries(2, self._collect, 0) def test_on_delete_do_nothing(self): """ Check that the nested collector doesn't query for DO_NOTHING objects. """ n = NestedObjects(using=DEFAULT_DB_ALIAS) objs = [Event.objects.create()] EventGuide.objects.create(event=objs[0]) with self.assertNumQueries(2): # One for Location, one for Guest, and no query for EventGuide n.collect(objs)
def related_objs(q): collector = NestedObjects(using='default') collector.collect(q) return collector.data