Fixed account delete/disable

This commit is contained in:
Marc Aymerich 2015-09-18 11:29:52 +00:00
parent 2491367d42
commit 912ee9744e
10 changed files with 239 additions and 37 deletions

View File

@ -10,7 +10,7 @@ from django.utils import timezone
from django.utils.encoding import force_text from django.utils.encoding import force_text
from django.utils.html import format_html from django.utils.html import format_html
from django.utils.text import capfirst from django.utils.text import capfirst
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ungettext, ugettext_lazy as _
from orchestra.core import services from orchestra.core import services
@ -146,16 +146,24 @@ def delete_related_services(modeladmin, request, queryset):
# The user has already confirmed the deletion. # The user has already confirmed the deletion.
# Do the deletion and return a None to display the change list view again. # Do the deletion and return a None to display the change list view again.
if request.POST.get('post'): if request.POST.get('post'):
n = queryset.count() accounts = len(queryset)
if n: msg = _("Related services deleted and account disabled.")
for account in queryset:
account.is_active = False
account.save(update_fields=('is_active',))
modeladmin.log_change(request, account, msg)
if accounts:
relateds = len(to_delete)
for obj in to_delete: for obj in to_delete:
obj_display = force_text(obj) obj_display = force_text(obj)
modeladmin.log_deletion(request, obj, obj_display) modeladmin.log_deletion(request, obj, obj_display)
# TODO This probably will fail in certain conditions, just capture exception
obj.delete() obj.delete()
modeladmin.message_user(request, _("Successfully deleted %(count)d %(items)s.") % { context = {
"count": n, "items": model_ngettext(modeladmin.opts, n) 'accounts': accounts,
}, messages.SUCCESS) 'relateds': relateds,
}
msg = _("Successfully disabled %(accounts)d account and deleted %(relateds)d related services.") % context
modeladmin.message_user(request, msg, messages.SUCCESS)
# Return None to display the change list page again. # Return None to display the change list page again.
return None return None
@ -165,7 +173,7 @@ def delete_related_services(modeladmin, request, queryset):
objects_name = force_text(opts.verbose_name_plural) objects_name = force_text(opts.verbose_name_plural)
context = dict( context = dict(
modeladmin.admin_site.each_context(request), admin_site.each_context(request),
title=_("Are you sure?"), title=_("Are you sure?"),
objects_name=objects_name, objects_name=objects_name,
deletable_objects=[related_services], deletable_objects=[related_services],
@ -174,11 +182,85 @@ def delete_related_services(modeladmin, request, queryset):
opts=opts, opts=opts,
action_checkbox_name=helpers.ACTION_CHECKBOX_NAME, action_checkbox_name=helpers.ACTION_CHECKBOX_NAME,
) )
request.current_app = modeladmin.admin_site.name request.current_app = admin_site.name
# Display the confirmation page # Display the confirmation page
return TemplateResponse(request, modeladmin.delete_selected_confirmation_template or [ template = 'admin/%s/%s/delete_related_services_confirmation.html' % (app_label, opts.model_name)
"admin/%s/%s/delete_selected_confirmation.html" % (app_label, opts.model_name), return TemplateResponse(request, template, context)
"admin/%s/delete_selected_confirmation.html" % app_label,
"admin/delete_selected_confirmation.html"
], context)
delete_related_services.short_description = _("Delete related services") delete_related_services.short_description = _("Delete related services")
def disable_selected(modeladmin, request, queryset):
opts = modeladmin.model._meta
app_label = opts.app_label
# The user has already confirmed the deletion.
# Do the disable and return a None to display the change list view again.
if request.POST.get('post'):
n = 0
for account in queryset:
account.disable()
modeladmin.log_change(request, account, _("Disabled"))
n += 1
modeladmin.message_user(request, ungettext(
_("One account has been successfully disabled."),
_("%i accounts have been successfully disabled.") % n,
n)
)
return None
user = request.user
admin_site = modeladmin.admin_site
def format(obj):
has_admin = obj.__class__ in admin_site._registry
opts = obj._meta
no_edit_link = '%s: %s' % (capfirst(opts.verbose_name), force_text(obj))
if has_admin:
try:
admin_url = reverse(
'admin:%s_%s_change' % (opts.app_label, opts.model_name),
None,
(quote(obj._get_pk_val()),)
)
except NoReverseMatch:
# Change url doesn't exist -- don't display link to edit
return no_edit_link
p = '%s.%s' % (opts.app_label, get_permission_codename('delete', opts))
if not user.has_perm(p):
perms_needed.add(opts.verbose_name)
# Display a link to the admin page.
context = (capfirst(opts.verbose_name), admin_url, obj)
return format_html('{}: <a href="{}">{}</a>', *context)
else:
# Don't display link to edit, because it either has no
# admin or is edited inline.
return no_edit_link
display = []
for account in queryset:
current = []
for related in account.get_services_to_disable():
current.append(format(related))
display.append([format(account), current])
if len(queryset) == 1:
objects_name = force_text(opts.verbose_name)
else:
objects_name = force_text(opts.verbose_name_plural)
context = dict(
admin_site.each_context(request),
title=_("Are you sure?"),
objects_name=objects_name,
deletable_objects=display,
queryset=queryset,
opts=opts,
action_checkbox_name=helpers.ACTION_CHECKBOX_NAME,
)
request.current_app = admin_site.name
template = 'admin/%s/%s/disable_selected_confirmation.html' % (app_label, opts.model_name)
return TemplateResponse(request, template, context)
disable_selected.short_description = _("Disable selected accounts")
disable_selected.url = 'disable'
disable_selected.tool_description = _("Disable")

View File

@ -13,12 +13,12 @@ from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.admin import ExtendedModelAdmin, ChangePasswordAdminMixin from orchestra.admin import ExtendedModelAdmin, ChangePasswordAdminMixin
from orchestra.admin.actions import SendEmail, disable from orchestra.admin.actions import SendEmail
from orchestra.admin.utils import wrap_admin_view, admin_link, set_url_query, change_url from orchestra.admin.utils import wrap_admin_view, admin_link, set_url_query, change_url
from orchestra.core import services, accounts from orchestra.core import services, accounts
from orchestra.forms import UserChangeForm from orchestra.forms import UserChangeForm
from .actions import list_contacts, service_report, delete_related_services from .actions import list_contacts, service_report, delete_related_services, disable_selected
from .filters import HasMainUserListFilter from .filters import HasMainUserListFilter
from .forms import AccountCreationForm from .forms import AccountCreationForm
from .models import Account from .models import Account
@ -61,8 +61,10 @@ class AccountAdmin(ChangePasswordAdminMixin, auth.UserAdmin, ExtendedModelAdmin)
filter_horizontal = () filter_horizontal = ()
change_readonly_fields = ('username', 'main_systemuser_link', 'is_active') change_readonly_fields = ('username', 'main_systemuser_link', 'is_active')
change_form_template = 'admin/accounts/account/change_form.html' change_form_template = 'admin/accounts/account/change_form.html'
actions = [disable, list_contacts, service_report, SendEmail(), delete_related_services] actions = (
change_view_actions = [disable, service_report] disable_selected, delete_related_services, list_contacts, service_report, SendEmail()
)
change_view_actions = (disable_selected, service_report)
ordering = () ordering = ()
main_systemuser_link = admin_link('main_systemuser') main_systemuser_link = admin_link('main_systemuser')
@ -109,6 +111,12 @@ class AccountAdmin(ChangePasswordAdminMixin, auth.UserAdmin, ExtendedModelAdmin)
else: else:
super(AccountAdmin, self).save_model(request, obj, form, change) super(AccountAdmin, self).save_model(request, obj, form, change)
def get_actions(self, request):
actions = super(AccountAdmin, self).get_actions(request)
if 'delete_selected' in actions:
del actions['delete_selected']
return actions
admin.site.register(Account, AccountAdmin) admin.site.register(Account, AccountAdmin)

View File

@ -8,6 +8,7 @@ from django.utils.translation import ugettext_lazy as _
from orchestra.contrib.orchestration.middlewares import OperationsMiddleware from orchestra.contrib.orchestration.middlewares import OperationsMiddleware
from orchestra.contrib.orchestration import Operation from orchestra.contrib.orchestration import Operation
from orchestra.core import services
from orchestra.utils.mail import send_email_template from orchestra.utils.mail import send_email_template
from . import settings from . import settings
@ -82,13 +83,17 @@ class Account(auth.AbstractBaseUser):
self.save(update_fields=['is_active']) self.save(update_fields=['is_active'])
self.notify_related() self.notify_related()
def notify_related(self): def get_services_to_disable(self):
# Trigger save() on related objects that depend on this account
for rel in self._meta.get_all_related_objects(): for rel in self._meta.get_all_related_objects():
source = getattr(rel, 'related_model', rel.model) source = getattr(rel, 'related_model', rel.model)
if source in services and hasattr(source, 'active'): if source in services and hasattr(source, 'active'):
for obj in getattr(self, rel.get_accessor_name()).all(): for obj in getattr(self, rel.get_accessor_name()).all():
OperationsMiddleware.collect(Operation.SAVE, instance=obj, update_fields=[]) yield obj
def notify_related(self):
""" Trigger save() on related objects that depend on this account """
for obj in self.get_services_to_disable():
OperationsMiddleware.collect(Operation.SAVE, instance=obj, update_fields=[])
def send_email(self, template, context, email_from=None, contacts=[], attachments=[], html=None): def send_email(self, template, context, email_from=None, contacts=[], attachments=[], html=None):
contacts = self.contacts.filter(email_usages=contacts) contacts = self.contacts.filter(email_usages=contacts)

View File

@ -0,0 +1,39 @@
{% extends "admin/delete_selected_confirmation.html" %}
{% load i18n l10n admin_urls %}
{% block content %}
{% if perms_lacking %}
<p>{% blocktrans %}Deleting the selected {{ objects_name }} would result in deleting related objects, but your account doesn't have permission to delete the following types of objects:{% endblocktrans %}</p>
<ul>
{% for obj in perms_lacking %}
<li>{{ obj }}</li>
{% endfor %}
</ul>
{% elif protected %}
<p>{% blocktrans %}Deleting the selected {{ objects_name }} would require deleting the following protected related objects:{% endblocktrans %}</p>
<ul>
{% for obj in protected %}
<li>{{ obj }}</li>
{% endfor %}
</ul>
{% else %}
<p>{% blocktrans %}Are you sure you want to delete the selected {{ objects_name }}? All of the following objects and their related items will be deleted:{% endblocktrans %}</p>
{% include "admin/includes/object_delete_summary.html" %}
<h2>{% trans "Objects" %}</h2>
{% for deletable_object in deletable_objects %}
<ul>{{ deletable_object|unordered_list }}</ul>
{% endfor %}
<form action="" method="post">{% csrf_token %}
<div>
{% for obj in queryset %}
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk|unlocalize }}" />
{% endfor %}
<input type="hidden" name="action" value="delete_related_services" />
<input type="hidden" name="post" value="yes" />
<input type="submit" value="{% trans "Yes, I'm sure" %}" />
<a href="#" onclick="window.history.back(); return false;" class="button cancel-link">{% trans "No, take me back" %}</a>
</div>
</form>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,49 @@
{% extends "admin/base_site.html" %}
{% load i18n l10n admin_urls %}
{% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} delete-confirmation delete-selected-confirmation{% endblock %}
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a>
&rsaquo; <a href="{% url 'admin:app_list' app_label=opts.app_label %}">{{ opts.app_config.verbose_name }}</a>
&rsaquo; <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
&rsaquo; {% trans 'Disable accounts' %}
</div>
{% endblock %}
{% block content %}
{% if perms_lacking %}
<p>{% blocktrans %}Disabling the selected {{ objects_name }} would result in disabling related objects, but your account doesn't have permission to disable the following types of objects:{% endblocktrans %}</p>
<ul>
{% for obj in perms_lacking %}
<li>{{ obj }}</li>
{% endfor %}
</ul>
{% elif protected %}
<p>{% blocktrans %}Deleting the selected {{ objects_name }} would require deleting the following protected related objects:{% endblocktrans %}</p>
<ul>
{% for obj in protected %}
<li>{{ obj }}</li>
{% endfor %}
</ul>
{% else %}
<p>{% blocktrans %}Are you sure you want to disable the selected {{ objects_name }}? All of the following objects and their related items will be disabled:{% endblocktrans %}</p>
<h2>{% trans "Objects" %}</h2>
{% for deletable_object in deletable_objects %}
<ul>{{ deletable_object|unordered_list }}</ul>
{% endfor %}
<form action="" method="post">{% csrf_token %}
<div>
{% for obj in queryset %}
<input type="hidden" name="{{ action_checkbox_name }}" value="{{ obj.pk|unlocalize }}" />
{% endfor %}
<input type="hidden" name="action" value="disable_selected" />
<input type="hidden" name="post" value="yes" />
<input type="submit" value="{% trans "Yes, I'm sure" %}" />
<a href="#" onclick="window.history.back(); return false;" class="button cancel-link">{% trans "No, take me back" %}</a>
</div>
</form>
{% endif %}
{% endblock %}

View File

@ -120,10 +120,12 @@ class Resource(models.Model):
# This only works on tests (multiprocessing used on real deployments) # This only works on tests (multiprocessing used on real deployments)
apps.get_app_config('resources').reload_relations() apps.get_app_config('resources').reload_relations()
def sync_periodic_task(self): def sync_periodic_task(self, delete=False):
""" sync periodic task on save/delete resource operations """ """ sync periodic task on save/delete resource operations """
name = 'monitor.%s' % str(self) name = 'monitor.%s' % str(self)
if self.pk and self.crontab and self.is_active: if delete or not self.crontab or not self.is_active:
PeriodicTask.objects.filter(name=name).delete()
elif self.pk:
try: try:
task = PeriodicTask.objects.get(name=name) task = PeriodicTask.objects.get(name=name)
except PeriodicTask.DoesNotExist: except PeriodicTask.DoesNotExist:
@ -138,8 +140,6 @@ class Resource(models.Model):
if task.crontab != self.crontab: if task.crontab != self.crontab:
task.crontab = self.crontab task.crontab = self.crontab
task.save(update_fields=['crontab']) task.save(update_fields=['crontab'])
else:
PeriodicTask.objects.filter(name=name).delete()
def get_model_path(self, monitor): def get_model_path(self, monitor):
""" returns a model path between self.content_type and monitor.model """ """ returns a model path between self.content_type and monitor.model """

View File

@ -5,8 +5,14 @@ from .models import Resource
@receiver(post_save, sender=Resource, dispatch_uid="resources.sync_periodic_task") @receiver(post_save, sender=Resource, dispatch_uid="resources.sync_periodic_task")
@receiver(post_delete, sender=Resource, dispatch_uid="resources.sync_periodic_task")
def sync_periodic_task(sender, **kwargs): def sync_periodic_task(sender, **kwargs):
""" useing signals instead of Model.delete() override beucause of admin bulk delete() """ """ useing signals instead of Model.delete() override beucause of admin bulk delete() """
instance = kwargs['instance'] instance = kwargs['instance']
instance.sync_periodic_task() instance.sync_periodic_task()
@receiver(post_delete, sender=Resource, dispatch_uid="resources.delete_periodic_task")
def delete_periodic_task(sender, **kwargs):
""" useing signals instead of Model.delete() override beucause of admin bulk delete() """
instance = kwargs['instance']
instance.sync_periodic_task(delete=True)

View File

@ -69,8 +69,10 @@ class SaaSWebTraffic(ServiceMonitor):
try: try:
with open(access_log, 'r') as handler: with open(access_log, 'r') as handler:
for line in handler.readlines(): for line in handler.readlines():
meta, request, response, hostname, __ = line.split('"') line = line.split()
meta = line[:4]
host, __, __, date, tz = meta.split() host, __, __, date, tz = meta.split()
size, hostname = line[-2:]
if host in {ignore_hosts}: if host in {ignore_hosts}:
continue continue
try: try:
@ -78,7 +80,7 @@ class SaaSWebTraffic(ServiceMonitor):
except KeyError: except KeyError:
continue continue
else: else:
# [16/Sep/2015:11:40:38 +0200] # [16/Sep/2015:11:40:38
day, month, date = date[1:].split('/') day, month, date = date[1:].split('/')
year, hour, min, sec = date.split(':') year, hour, min, sec = date.split(':')
date = year + months[month] + day + hour + min + sec date = year + months[month] + day + hour + min + sec

View File

@ -116,25 +116,21 @@ class Apache2Backend(ServiceController):
if context['server_name'] and site.active: if context['server_name'] and site.active:
self.append(textwrap.dedent(""" self.append(textwrap.dedent("""
# Enable site %(site_name)s # Enable site %(site_name)s
if [[ ! -f %(sites_enabled)s ]]; then [[ $(a2ensite %(site_unique_name)s) =~ "already enabled" ]] || UPDATED_APACHE=1\
a2ensite %(site_unique_name)s.conf """) % context
UPDATED_APACHE=1
fi""") % context
) )
else: else:
self.append(textwrap.dedent(""" self.append(textwrap.dedent("""
# Disable site %(site_name)s # Disable site %(site_name)s
if [[ -f %(sites_enabled)s ]]; then [[ $(a2dissite %(site_unique_name)s) =~ "already disabled" ]] || UPDATED_APACHE=1\
a2dissite %(site_unique_name)s.conf; """) % context
UPDATED_APACHE=1
fi""") % context
) )
def delete(self, site): def delete(self, site):
context = self.get_context(site) context = self.get_context(site)
self.append(textwrap.dedent(""" self.append(textwrap.dedent("""
# Remove site configuration for %(site_name)s # Remove site configuration for %(site_name)s
a2dissite %(site_unique_name)s.conf && UPDATED_APACHE=1 [[ $(a2dissite %(site_unique_name)s) =~ "already disabled" ]] || UPDATED_APACHE=1
rm -f %(sites_available)s\ rm -f %(sites_available)s\
""") % context """) % context
) )

15
orchestra/getips.py Normal file
View File

@ -0,0 +1,15 @@
#! /usr/bin/env python
import requests
import json
SLICE_ID=1513
slice_req = requests.get('https://controller.community-lab.net/api/slices/%i' % SLICE_ID)
slice = json.loads(slice_req.content)
for sliver in slice['slivers']:
sliver_req = requests.get(sliver['uri'])
sliver = json.loads(sliver_req.content)
print sliver['mgmt_net']['address']