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 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 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_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 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_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 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 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 response(self, request, username, project_slug, member_username): membership = get_object_or_404( Membership.objects.select_related('project', 'member'), project__slug=project_slug, project__author__username=username, member__username=member_username)\ member = membership.member project = membership.project if project.author == member: messages.warning( request, _("Project owner's membership cannot be " "removed.")) return redirect(project.get_members_url()) collector = NestedObjects() membership._collect_sub_objects(collector) form = MembershipDeleteForm(request.POST or None) perms_to_delete = UserObjectPermission.objects.filter( user=member, content_type=ContentType.objects.get_for_model(project), object_pk=project.id) if request.method == 'POST': # Confirm removal if form.is_valid() and project.author != member: msg = _("Membership removed") messages.success(request, msg) membership.delete() perms_to_delete.delete() return redirect(project.get_members_url()) else: msg = _("Couldn't remove membership") messages.error(request, msg) if project.author == member: msg = _("Project's author cannot be removed") messages.error(request, msg) context = { 'project': project, 'membership': membership, 'form': form, 'to_delete': collector.nested(), 'member_perms': perms_to_delete, } return context
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 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 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 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 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 response(self, request, username, project_slug, name): team = get_object_or_404( Team.objects.select_related('group', 'project'), project__author__username=username, project__slug=project_slug, group__name=name) group, project = team.group, team.project collector = NestedObjects() team._collect_sub_objects(collector) form = TeamDeleteForm(request.POST or None) perms_to_delete = GroupObjectPermission.objects.filter( group = group, content_type = ContentType.objects.get_for_model(project), object_pk = project.id) if request.method == 'POST': # Confirm removal if form.is_valid(): msg = _("Team removed") messages.success(request, msg) team.delete() perms_to_delete.delete() return redirect(project.get_teams_url()) else: msg = _("Couldn't remove team") messages.error(request, msg) context = { 'project': project, 'team': team, 'form': form, 'to_delete': collector.nested(), 'team_perms': perms_to_delete, } return context
def setUp(self): self.n = NestedObjects() self.objs = [Count.objects.create(num=i) for i in range(5)]
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()]
def setUp(self): self.n = NestedObjects(using=DEFAULT_DB_ALIAS) self.objs = [Count.objects.create(num=i) for i in range(5)]
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 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 related_objs(q): collector = NestedObjects(using='default') collector.collect(q) return collector.data