Implemented enable admin action0

This commit is contained in:
Marc Aymerich 2016-03-31 16:02:50 +00:00
parent 6a34ba8fd2
commit 4adfd4c83a
26 changed files with 183 additions and 98 deletions

View File

@ -437,3 +437,11 @@ mkhomedir_helper or create ssh homes with bash.rc and such
# Warning websites with ssl options without https protocol # Warning websites with ssl options without https protocol
# Schedule cancellation # Schedule cancellation
# Multiple domains wordpress
# TODO: separate ports for fpm version
# Reversion
# implement re-enable account
# Disable/enable saas and VPS

View File

@ -1,3 +1,5 @@
from functools import partial
from django.contrib import admin from django.contrib import admin
from django.core.mail import send_mass_mail from django.core.mail import send_mass_mail
from django.shortcuts import render from django.shortcuts import render
@ -108,23 +110,36 @@ class SendEmail(object):
return render(request, self.template, self.context) return render(request, self.template, self.context)
@action_with_confirmation() def base_disable(modeladmin, request, queryset, disable=True):
def disable(modeladmin, request, queryset):
num = 0 num = 0
action_name = _("disabled") if disable else _("enabled")
for obj in queryset: for obj in queryset:
obj.disable() obj.disable() if disable else obj.enable()
modeladmin.log_change(request, obj, _("Disabled")) modeladmin.log_change(request, obj, action_name.capitalize())
num += 1 num += 1
opts = modeladmin.model._meta opts = modeladmin.model._meta
context = { context = {
'action_name': action_name,
'verbose_name': opts.verbose_name, 'verbose_name': opts.verbose_name,
'verbose_name_plural': opts.verbose_name_plural, 'verbose_name_plural': opts.verbose_name_plural,
'num': num 'num': num
} }
msg = ungettext( msg = ungettext(
_("Selected %(verbose_name)s and related services has been disabled.") % context, _("Selected %(verbose_name)s and related services has been %(action_name)s.") % context,
_("%(num)s selected %(verbose_name_plural)s and related services have been disabled.") % context, _("%(num)s selected %(verbose_name_plural)s and related services have been %(action_name)s.") % context,
num) num)
modeladmin.message_user(request, msg) modeladmin.message_user(request, msg)
@action_with_confirmation()
def disable(modeladmin, request, queryset):
return base_disable(modeladmin, request, queryset)
disable.url_name = 'disable' disable.url_name = 'disable'
disable.short_description = _("Disable") disable.short_description = _("Disable")
@action_with_confirmation()
def enable(modeladmin, request, queryset):
return base_disable(modeladmin, request, queryset, disable=False)
enable.url_name = 'enable'
enable.short_description = _("Enable")

View File

@ -149,7 +149,7 @@ class ChangeViewActionsMixin(object):
kwargs['extra_context']['object_tools_items'] = [ kwargs['extra_context']['object_tools_items'] = [
action.__dict__ for action in self.get_change_view_actions(obj) action.__dict__ for action in self.get_change_view_actions(obj)
] ]
return super(ChangeViewActionsMixin, self).change_view(request, object_id, **kwargs) return super().change_view(request, object_id, **kwargs)
class ChangeAddFieldsMixin(object): class ChangeAddFieldsMixin(object):

View File

@ -1,3 +1,5 @@
from functools import partial, wraps
from django.contrib import messages from django.contrib import messages
from django.contrib.admin import helpers from django.contrib.admin import helpers
from django.contrib.admin.utils import NestedObjects, quote from django.contrib.admin.utils import NestedObjects, quote
@ -186,21 +188,21 @@ def delete_related_services(modeladmin, request, queryset):
delete_related_services.short_description = _("Delete related services") delete_related_services.short_description = _("Delete related services")
def disable_selected(modeladmin, request, queryset): def disable_selected(modeladmin, request, queryset, disable=True):
opts = modeladmin.model._meta opts = modeladmin.model._meta
app_label = opts.app_label app_label = opts.app_label
verbose_action_name = _("disabled") if disable else _("enabled")
# The user has already confirmed the deletion. # The user has already confirmed the deletion.
# Do the disable and return a None to display the change list view again. # Do the disable and return a None to display the change list view again.
if request.POST.get('post'): if request.POST.get('post'):
n = 0 n = 0
for account in queryset: for account in queryset:
account.disable() account.disable() if disable else account.enable()
modeladmin.log_change(request, account, _("Disabled")) modeladmin.log_change(request, account, verbose_action_name.capitalize())
n += 1 n += 1
modeladmin.message_user(request, ungettext( modeladmin.message_user(request, ungettext(
_("One account has been successfully disabled."), _("One account has been successfully %s.") % verbose_action_name,
_("%i accounts have been successfully disabled.") % n, _("%i accounts have been successfully %s.") % (n, verbose_action_name),
n) n)
) )
return None return None
@ -248,6 +250,8 @@ def disable_selected(modeladmin, request, queryset):
context = dict( context = dict(
admin_site.each_context(request), admin_site.each_context(request),
action_name='disable_selected' if disable else 'enable_selected',
disable=disable,
title=_("Are you sure?"), title=_("Are you sure?"),
objects_name=objects_name, objects_name=objects_name,
deletable_objects=display, deletable_objects=display,
@ -259,5 +263,11 @@ def disable_selected(modeladmin, request, queryset):
template = 'admin/%s/%s/disable_selected_confirmation.html' % (app_label, opts.model_name) template = 'admin/%s/%s/disable_selected_confirmation.html' % (app_label, opts.model_name)
return TemplateResponse(request, template, context) return TemplateResponse(request, template, context)
disable_selected.short_description = _("Disable selected accounts") disable_selected.short_description = _("Disable selected accounts")
disable_selected.url = 'disable' disable_selected.url_name = 'disable'
disable_selected.tool_description = _("Disable") disable_selected.tool_description = _("Disable")
enable_selected = partial(disable_selected, disable=False)
enable_selected.__name__ = 'enable_selected'
enable_selected.url_name = 'enable'
enable_selected.tool_description = _("Enable")

View File

@ -20,7 +20,8 @@ from orchestra.admin.utils import wrap_admin_view, admin_link, set_url_query
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, disable_selected from .actions import (list_contacts, service_report, delete_related_services, disable_selected,
enable_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
@ -64,9 +65,10 @@ class AccountAdmin(ChangePasswordAdminMixin, auth.UserAdmin, ExtendedModelAdmin)
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 = ( actions = (
disable_selected, delete_related_services, list_contacts, service_report, SendEmail() disable_selected, enable_selected, delete_related_services, list_contacts, service_report,
SendEmail()
) )
change_view_actions = (disable_selected, service_report) change_view_actions = (disable_selected, service_report, enable_selected)
ordering = () ordering = ()
main_systemuser_link = admin_link('main_systemuser') main_systemuser_link = admin_link('main_systemuser')
@ -111,6 +113,14 @@ 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_change_view_actions(self, obj=None):
views = super().get_change_view_actions(obj=obj)
if obj is not None:
if obj.is_active:
return [view for view in views if view.url_name != 'enable']
return [view for view in views if view.url_name != 'disable']
return views
def get_actions(self, request): def get_actions(self, request):
actions = super(AccountAdmin, self).get_actions(request) actions = super(AccountAdmin, self).get_actions(request)
if 'delete_selected' in actions: if 'delete_selected' in actions:

View File

@ -83,6 +83,11 @@ class Account(auth.AbstractBaseUser):
self.save(update_fields=('is_active',)) self.save(update_fields=('is_active',))
self.notify_related() self.notify_related()
def enable(self):
self.is_active = True
self.save(update_fields=('is_active',))
self.notify_related()
def get_services_to_disable(self): def get_services_to_disable(self):
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)

View File

@ -8,42 +8,28 @@
<a href="{% url 'admin:index' %}">{% trans 'Home' %}</a> <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 '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; <a href="{% url opts|admin_urlname:'changelist' %}">{{ opts.verbose_name_plural|capfirst }}</a>
&rsaquo; {% trans 'Disable accounts' %} &rsaquo; {% if disable%}{% blocktrans %}Disable {{ objects_name }}{% endblocktrans %}{% else %}{% blocktrans %}Enable {{ objects_name }}{% endblocktrans %}{% endif %}
</div> </div>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
{% if perms_lacking %} {% if disable%}<p>{% blocktrans %}Are you sure you want to disable selected {{ objects_name }}?{% endblocktrans %}</p>
<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> {% else %}<p>{% blocktrans %}Are you sure you want to enable selected {{ objects_name }}?{% 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 %} {% endif %}
<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="{{ action_name }}" />
<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>
{% endblock %} {% endblock %}

View File

@ -292,7 +292,7 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
state = bill.get_payment_state_display().upper() state = bill.get_payment_state_display().upper()
title = '' title = ''
if bill.closed_amends: if bill.closed_amends:
state += '*' state = '<strike>%s*</strike>' % state
title = _("This bill has been amended, this value may not be valid.") title = _("This bill has been amended, this value may not be valid.")
color = PAYMENT_STATE_COLORS.get(bill.payment_state, 'grey') color = PAYMENT_STATE_COLORS.get(bill.payment_state, 'grey')
return '<a href="{url}" style="color:{color}" title="{title}">{name}</a>'.format( return '<a href="{url}" style="color:{color}" title="{title}">{name}</a>'.format(

View File

@ -78,7 +78,10 @@ class Domain(models.Model):
@property @property
def is_top(self): def is_top(self):
# don't cache, don't replace by top_id # don't cache, don't replace by top_id
return not bool(self.top) try:
return not bool(self.top)
except Domain.DoesNotExist:
return False
@property @property
def subdomains(self): def subdomains(self):

View File

@ -11,7 +11,7 @@ from .helpers import markdown_formated_changes
from .models import Queue, Ticket from .models import Queue, Ticket
def change_ticket_state_factory(action, final_state): def change_ticket_state_factory(action, verbose_name, final_state):
context = { context = {
'action': action, 'action': action,
'form': ChangeReasonForm() 'form': ChangeReasonForm()
@ -40,30 +40,31 @@ def change_ticket_state_factory(action, final_state):
'count': queryset.count(), 'count': queryset.count(),
'state': final_state.lower() 'state': final_state.lower()
} }
msg = _("%s selected tickets are now %s.") % context msg = _("%(count)s selected tickets are now %(state)s.") % context
modeladmin.message_user(request, msg) modeladmin.message_user(request, msg)
else: else:
context['form'] = form context['form'] = form
# action_with_confirmation must display form validation errors # action_with_confirmation must display form validation errors
return True return True
change_ticket_state.url_name = action change_ticket_state.url_name = action
change_ticket_state.verbose_name = action change_ticket_state.tool_description = verbose_name
change_ticket_state.short_description = _('%s selected tickets') % action.capitalize() change_ticket_state.short_description = _('%s selected tickets') % verbose_name
change_ticket_state.help_text = _('Mark ticket as %s.') % final_state.lower() change_ticket_state.help_text = _('Mark ticket as %s.') % final_state.lower()
change_ticket_state.__name__ = action change_ticket_state.__name__ = action
return change_ticket_state return change_ticket_state
action_map = { action_map = {
Ticket.RESOLVED: 'resolve', Ticket.RESOLVED: ('resolve', _("Resolve")),
Ticket.REJECTED: 'reject', Ticket.REJECTED: ('reject', _("Reject")),
Ticket.CLOSED: 'close' Ticket.CLOSED: ('close', _("Close")),
} }
thismodule = sys.modules[__name__] thismodule = sys.modules[__name__]
for state, name in action_map.items(): for state, names in action_map.items():
action = change_ticket_state_factory(name, state) name, verbose_name = names
action = change_ticket_state_factory(name, verbose_name, state)
setattr(thismodule, '%s_tickets' % name, action) setattr(thismodule, '%s_tickets' % name, action)
@ -89,6 +90,7 @@ def take_tickets(modeladmin, request, queryset):
msg = _("%(count)s selected tickets are now owned by %(user)s.") % context msg = _("%(count)s selected tickets are now owned by %(user)s.") % context
modeladmin.message_user(request, msg) modeladmin.message_user(request, msg)
take_tickets.url_name = 'take' take_tickets.url_name = 'take'
take_tickets.tool_description = _("Take")
take_tickets.short_description = _("Take selected tickets") take_tickets.short_description = _("Take selected tickets")
take_tickets.help_text = _("Make yourself owner of the ticket.") take_tickets.help_text = _("Make yourself owner of the ticket.")

View File

@ -64,7 +64,6 @@ class MessageReadOnlyInline(admin.TabularInline):
return header + content return header + content
content_html.short_description = _("Content") content_html.short_description = _("Content")
content_html.allow_tags = True content_html.allow_tags = True
def has_add_permission(self, request): def has_add_permission(self, request):
return False return False
@ -125,11 +124,10 @@ class TicketAdmin(ExtendedModelAdmin):
) )
list_display_links = ('unbold_id', 'bold_subject') list_display_links = ('unbold_id', 'bold_subject')
list_filter = ( list_filter = (
MyTicketsListFilter, 'queue__name', 'priority', TicketStateListFilter, MyTicketsListFilter, 'queue', 'priority', TicketStateListFilter,
) )
default_changelist_filters = ( default_changelist_filters = (
('my_tickets', lambda r: 'True' if not r.user.is_superuser else 'False'), ('state', 'OPEN'),
('state', 'OPEN')
) )
date_hierarchy = 'created_at' date_hierarchy = 'created_at'
search_fields = ( search_fields = (
@ -298,7 +296,7 @@ class QueueAdmin(admin.ModelAdmin):
def num_tickets(self, queue): def num_tickets(self, queue):
num = queue.tickets__count num = queue.tickets__count
url = reverse('admin:issues_ticket_changelist') url = reverse('admin:issues_ticket_changelist')
url += '?my_tickets=False&queue=%i' % queue.pk url += '?queue=%i' % queue.pk
return '<a href="%s">%d</a>' % (url, num) return '<a href="%s">%d</a>' % (url, num)
num_tickets.short_description = _("Tickets") num_tickets.short_description = _("Tickets")
num_tickets.admin_order_field = 'tickets__count' num_tickets.admin_order_field = 'tickets__count'

View File

@ -12,18 +12,11 @@ class MyTicketsListFilter(SimpleListFilter):
def lookups(self, request, model_admin): def lookups(self, request, model_admin):
return ( return (
('True', _("My Tickets")), ('True', _("My Tickets")),
('False', _("All")),
) )
def queryset(self, request, queryset): def queryset(self, request, queryset):
if self.value() == 'True': if self.value() == 'True':
return queryset.involved_by(request.user) return queryset.involved_by(request.user)
def choices(self, cl):
""" Remove default All """
choices = iter(super(MyTicketsListFilter, self).choices(cl))
next(choices)
return choices
class TicketStateListFilter(SimpleListFilter): class TicketStateListFilter(SimpleListFilter):

View File

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import datetime
from django.utils.timezone import utc
class Migration(migrations.Migration):
dependencies = [
('issues', '0002_auto_20150709_1018'),
]
operations = [
migrations.RemoveField(
model_name='message',
name='created_on',
),
migrations.AddField(
model_name='message',
name='created_at',
field=models.DateTimeField(auto_now_add=True, default=datetime.datetime(2016, 3, 20, 10, 27, 45, 766388, tzinfo=utc), verbose_name='created at'),
preserve_default=False,
),
]

View File

@ -1,5 +1,6 @@
from django.conf import settings as djsettings from django.conf import settings as djsettings
from django.db import models from django.db import models
from django.db.models import query, Q
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.contrib.contacts import settings as contacts_settings from orchestra.contrib.contacts import settings as contacts_settings
@ -32,6 +33,12 @@ class Queue(models.Model):
super(Queue, self).save(*args, **kwargs) super(Queue, self).save(*args, **kwargs)
class TicketQuerySet(query.QuerySet):
def involved_by(self, user, *args, **kwargs):
qset = Q(creator=user) | Q(owner=user) | Q(messages__author=user)
return self.filter(qset, *args, **kwargs).distinct()
class Ticket(models.Model): class Ticket(models.Model):
HIGH = 'HIGH' HIGH = 'HIGH'
MEDIUM = 'MEDIUM' MEDIUM = 'MEDIUM'
@ -65,13 +72,13 @@ class Ticket(models.Model):
queue = models.ForeignKey(Queue, related_name='tickets', null=True, blank=True) queue = models.ForeignKey(Queue, related_name='tickets', null=True, blank=True)
subject = models.CharField(_("subject"), max_length=256) subject = models.CharField(_("subject"), max_length=256)
description = models.TextField(_("description")) description = models.TextField(_("description"))
priority = models.CharField(_("priority"), max_length=32, choices=PRIORITIES, priority = models.CharField(_("priority"), max_length=32, choices=PRIORITIES, default=MEDIUM)
default=MEDIUM)
state = models.CharField(_("state"), max_length=32, choices=STATES, default=NEW) state = models.CharField(_("state"), max_length=32, choices=STATES, default=NEW)
created_at = models.DateTimeField(_("created"), auto_now_add=True, db_index=True) created_at = models.DateTimeField(_("created"), auto_now_add=True, db_index=True)
updated_at = models.DateTimeField(_("modified"), auto_now=True) updated_at = models.DateTimeField(_("modified"), auto_now=True)
cc = models.TextField("CC", help_text=_("emails to send a carbon copy to"), cc = models.TextField("CC", help_text=_("emails to send a carbon copy to"), blank=True)
blank=True)
objects = TicketQuerySet.as_manager()
class Meta: class Meta:
ordering = ['-updated_at'] ordering = ['-updated_at']
@ -158,7 +165,7 @@ class Message(models.Model):
related_name='ticket_messages') related_name='ticket_messages')
author_name = models.CharField(_("author name"), max_length=256, blank=True) author_name = models.CharField(_("author name"), max_length=256, blank=True)
content = models.TextField(_("content")) content = models.TextField(_("content"))
created_on = models.DateTimeField(_("created on"), auto_now_add=True) created_at = models.DateTimeField(_("created at"), auto_now_add=True)
class Meta: class Meta:
get_latest_by = 'id' get_latest_by = 'id'

View File

@ -13,8 +13,8 @@ class QueueSerializer(serializers.HyperlinkedModelSerializer):
class MessageSerializer(serializers.HyperlinkedModelSerializer): class MessageSerializer(serializers.HyperlinkedModelSerializer):
class Meta: class Meta:
model = Message model = Message
fields = ('id', 'author', 'author_name', 'content', 'created_on') fields = ('id', 'author', 'author_name', 'content', 'created_at')
read_only_fields = ('author', 'author_name', 'created_on') read_only_fields = ('author', 'author_name', 'created_at')
def get_identity(self, data): def get_identity(self, data):
return data.get('id') return data.get('id')

View File

@ -18,7 +18,7 @@ Issue #{{ ticket.id }} has been updated by {{ ticket_message.author }}.
----------------------------------------------------------------- -----------------------------------------------------------------
Issue #{{ ticket.pk }}: {{ ticket.subject }} Issue #{{ ticket.pk }}: {{ ticket.subject }}
* Author: {{ ticket.created_by }} * Author: {{ ticket.creator_name }}
* Status: {{ ticket.get_state_display }} * Status: {{ ticket.get_state_display }}
* Priority: {{ ticket.get_priority_display }} * Priority: {{ ticket.get_priority_display }}
* Visibility: {{ ticket.get_visibility_display }} * Visibility: {{ ticket.get_visibility_display }}

View File

@ -41,7 +41,7 @@ Issue #{{ ticket.id }} has been updated by {{ ticket_message.author }}.
<h1><a href="http://{{ site.domain }}{% url 'admin:issues_ticket_change' ticket.id %}">Issue #{{ ticket.pk }}: {{ ticket.subject }}</a></h1> <h1><a href="http://{{ site.domain }}{% url 'admin:issues_ticket_change' ticket.id %}">Issue #{{ ticket.pk }}: {{ ticket.subject }}</a></h1>
<ul> <ul>
<li>Author: {{ ticket.created_by }}</li> <li>Author: {{ ticket.creator_name }}</li>
<li>Status: {{ ticket.get_state_display }}</li> <li>Status: {{ ticket.get_state_display }}</li>
<li>Priority: {{ ticket.get_priority_display }}</li> <li>Priority: {{ ticket.get_priority_display }}</li>
<li>Visibility: {{ ticket.get_visibility_display }}</li> <li>Visibility: {{ ticket.get_visibility_display }}</li>

View File

@ -4,7 +4,7 @@ from django.contrib.auth.admin import UserAdmin
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 disable from orchestra.admin.actions import disable, enable
from orchestra.admin.utils import admin_link from orchestra.admin.utils import admin_link
from orchestra.contrib.accounts.actions import list_accounts from orchestra.contrib.accounts.actions import list_accounts
from orchestra.contrib.accounts.admin import SelectAccountAdminMixin from orchestra.contrib.accounts.admin import SelectAccountAdminMixin
@ -58,7 +58,7 @@ class ListAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedModel
add_form = ListCreationForm add_form = ListCreationForm
list_select_related = ('account', 'address_domain',) list_select_related = ('account', 'address_domain',)
filter_by_account_fields = ['address_domain'] filter_by_account_fields = ['address_domain']
actions = (disable, list_accounts) actions = (disable, enable, list_accounts)
address_domain_link = admin_link('address_domain', order='address_domain__name') address_domain_link = admin_link('address_domain', order='address_domain__name')

View File

@ -10,7 +10,7 @@ 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 disable from orchestra.admin.actions import disable, enable
from orchestra.admin.utils import admin_link, change_url from orchestra.admin.utils import admin_link, change_url
from orchestra.contrib.accounts.actions import list_accounts from orchestra.contrib.accounts.actions import list_accounts
from orchestra.contrib.accounts.admin import SelectAccountAdminMixin from orchestra.contrib.accounts.admin import SelectAccountAdminMixin
@ -74,7 +74,7 @@ class MailboxAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, ExtendedMo
add_form = MailboxCreationForm add_form = MailboxCreationForm
form = MailboxChangeForm form = MailboxChangeForm
list_prefetch_related = ('addresses__domain',) list_prefetch_related = ('addresses__domain',)
actions = (disable, list_accounts) actions = (disable, enable, list_accounts)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(MailboxAdmin, self).__init__(*args, **kwargs) super(MailboxAdmin, self).__init__(*args, **kwargs)

View File

@ -3,7 +3,7 @@ from django.core.exceptions import ObjectDoesNotExist
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 disable from orchestra.admin.actions import disable, enable
from orchestra.admin.utils import change_url from orchestra.admin.utils import change_url
from orchestra.contrib.accounts.actions import list_accounts from orchestra.contrib.accounts.actions import list_accounts
from orchestra.contrib.accounts.admin import AccountAdminMixin from orchestra.contrib.accounts.admin import AccountAdminMixin
@ -24,7 +24,7 @@ class SaaSAdmin(SelectPluginAdminMixin, ChangePasswordAdminMixin, AccountAdminMi
plugin = SoftwareService plugin = SoftwareService
plugin_field = 'service' plugin_field = 'service'
plugin_title = 'Software as a Service' plugin_title = 'Software as a Service'
actions = (disable, list_accounts) actions = (disable, enable, list_accounts)
def display_url(self, saas): def display_url(self, saas):
site_domain = saas.get_site_domain() site_domain = saas.get_site_domain()

View File

@ -21,6 +21,7 @@ class DrupalMuController(ServiceController):
def save(self, webapp): def save(self, webapp):
context = self.get_context(webapp) context = self.get_context(webapp)
# TODO set password
self.append(textwrap.dedent("""\ self.append(textwrap.dedent("""\
mkdir %(drupal_path)s mkdir %(drupal_path)s
chown -R www-data %(drupal_path)s chown -R www-data %(drupal_path)s
@ -35,6 +36,7 @@ class DrupalMuController(ServiceController):
def delete(self, webapp): def delete(self, webapp):
context = self.get_context(webapp) context = self.get_context(webapp)
# TODO delete tables
self.append("rm -fr %(app_path)s" % context) self.append("rm -fr %(app_path)s" % context)
def get_context(self, webapp): def get_context(self, webapp):

View File

@ -22,8 +22,9 @@ class WordpressMuController(ServiceController):
model = 'saas.SaaS' model = 'saas.SaaS'
default_route_match = "saas.service == 'wordpress'" default_route_match = "saas.service == 'wordpress'"
doc_settings = (settings, doc_settings = (settings,
('SAAS_WORDPRESS_ADMIN_PASSWORD', 'SAAS_WORDPRESS_MAIN_URL') ('SAAS_WORDPRESS_ADMIN_PASSWORD', 'SAAS_WORDPRESS_MAIN_URL', 'SAAS_WORDPRESS_VERIFY_SSL')
) )
VERIFY = settings.SAAS_WORDPRESS_VERIFY_SSL
def login(self, session): def login(self, session):
main_url = self.get_main_url() main_url = self.get_main_url()
@ -33,9 +34,10 @@ class WordpressMuController(ServiceController):
'pwd': settings.SAAS_WORDPRESS_ADMIN_PASSWORD, 'pwd': settings.SAAS_WORDPRESS_ADMIN_PASSWORD,
'redirect_to': '/wp-admin/' 'redirect_to': '/wp-admin/'
} }
response = session.post(login_url, data=login_data) sys.stdout.write("Login URL: %s\n" % login_url)
response = session.post(login_url, data=login_data, verify=self.VERIFY)
if response.url != main_url + '/wp-admin/': if response.url != main_url + '/wp-admin/':
raise IOError("Failure login to remote application") raise IOError("Failure login to remote application (%s)" % login_url)
def get_main_url(self): def get_main_url(self):
main_url = settings.SAAS_WORDPRESS_MAIN_URL main_url = settings.SAAS_WORDPRESS_MAIN_URL
@ -54,7 +56,8 @@ class WordpressMuController(ServiceController):
'<a href="http://[\.\-\w]+/wp-admin/network/site-info\.php\?id=([0-9]+)"\s+' '<a href="http://[\.\-\w]+/wp-admin/network/site-info\.php\?id=([0-9]+)"\s+'
'class="edit">%s</a>' % saas.name 'class="edit">%s</a>' % saas.name
) )
content = session.get(search).content.decode('utf8') sys.stdout.write("Search URL: %s\n" % search)
content = session.get(search, verify=self.VERIFY).content.decode('utf8')
# Get id # Get id
ids = regex.search(content) ids = regex.search(content)
if not ids and not blog_id: if not ids and not blog_id:
@ -85,7 +88,8 @@ class WordpressMuController(ServiceController):
except RuntimeError: except RuntimeError:
url = self.get_main_url() url = self.get_main_url()
url += '/wp-admin/network/site-new.php' url += '/wp-admin/network/site-new.php'
content = session.get(url).content.decode('utf8') sys.stdout.write("Create URL: %s\n" % url)
content = session.get(url, verify=self.VERIFY).content.decode('utf8')
wpnonce = re.compile('name="_wpnonce_add-blog"\s+value="([^"]*)"') wpnonce = re.compile('name="_wpnonce_add-blog"\s+value="([^"]*)"')
wpnonce = wpnonce.search(content).groups()[0] wpnonce = wpnonce.search(content).groups()[0]
@ -99,7 +103,7 @@ class WordpressMuController(ServiceController):
} }
# Validate response # Validate response
response = session.post(url, data=data) response = session.post(url, data=data, verify=self.VERIFY)
self.validate_response(response) self.validate_response(response)
blog_id = re.compile(r'<link id="wp-admin-canonical" rel="canonical" href="http(?:[^ ]+)/wp-admin/network/site-new.php\?id=([0-9]+)" />') blog_id = re.compile(r'<link id="wp-admin-canonical" rel="canonical" href="http(?:[^ ]+)/wp-admin/network/site-new.php\?id=([0-9]+)" />')
content = response.content.decode('utf8') content = response.content.decode('utf8')
@ -124,8 +128,9 @@ class WordpressMuController(ServiceController):
delete = self.get_main_url() delete = self.get_main_url()
delete += '/wp-admin/network/sites.php?action=confirm&action2=deleteblog' delete += '/wp-admin/network/sites.php?action=confirm&action2=deleteblog'
delete += '&id=%d&_wpnonce=%s' % (id, wpnonce) delete += '&id=%d&_wpnonce=%s' % (id, wpnonce)
sys.stdout.write("Search URL: %s\n" % delete)
content = session.get(delete).content.decode('utf8') content = session.get(delete, verify=self.VERIFY).content.decode('utf8')
wpnonce = re.compile('name="_wpnonce"\s+value="([^"]*)"') wpnonce = re.compile('name="_wpnonce"\s+value="([^"]*)"')
wpnonce = wpnonce.search(content).groups()[0] wpnonce = wpnonce.search(content).groups()[0]
data = { data = {
@ -136,7 +141,8 @@ class WordpressMuController(ServiceController):
} }
delete = self.get_main_url() delete = self.get_main_url()
delete += '/wp-admin/network/sites.php?action=deleteblog' delete += '/wp-admin/network/sites.php?action=deleteblog'
response = session.post(delete, data=data) sys.stdout.write("Delete URL: %s\n" % delete)
response = session.post(delete, data=data, verify=self.VERIFY)
self.validate_response(response) self.validate_response(response)
def save(self, saas): def save(self, saas):

View File

@ -1,7 +1,10 @@
from .options import SoftwareService from .options import SoftwareService
from .. import settings
class DrupalService(SoftwareService): class DrupalService(SoftwareService):
name = 'drupal' name = 'drupal'
verbose_name = "Drupal" verbose_name = "Drupal"
icon = 'orchestra/icons/apps/Drupal.png' icon = 'orchestra/icons/apps/Drupal.png'
site_domain = settings.SAAS_MOODLE_DOMAIN

View File

@ -65,6 +65,11 @@ SAAS_WORDPRESS_DB_NAME = Setting('SAAS_WORDPRESS_DB_NAME',
help_text=_("Needed for domain mapping when <tt>SAAS_WORDPRESS_ALLOW_CUSTOM_URL</tt> is enabled."), help_text=_("Needed for domain mapping when <tt>SAAS_WORDPRESS_ALLOW_CUSTOM_URL</tt> is enabled."),
) )
SAAS_WORDPRESS_VERIFY_SSL = Setting('SAAS_WORDPRESS_VERIFY_SSL',
True,
help_text=_("Verify SSL certificate on the HTTP requests performed by the backend."),
)
# DokuWiki # DokuWiki
@ -119,6 +124,11 @@ SAAS_DRUPAL_SITES_PATH = Setting('WEBSITES_DRUPAL_SITES_PATH',
'/home/httpd/htdocs/drupal-mu/sites/%(site_name)s', '/home/httpd/htdocs/drupal-mu/sites/%(site_name)s',
) )
SAAS_DRUPAL_DOMAIN = Setting('SAAS_DRUPAL_DOMAIN',
'%(site_name)s.drupal.{}'.format(ORCHESTRA_BASE_DOMAIN),
help_text="Uses <tt>ORCHESTRA_BASE_DOMAIN</tt> by default.",
)
# PhpList # PhpList

View File

@ -2,6 +2,7 @@ from django.contrib import admin
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 disable, enable
from orchestra.contrib.accounts.actions import list_accounts from orchestra.contrib.accounts.actions import list_accounts
from orchestra.contrib.accounts.admin import SelectAccountAdminMixin from orchestra.contrib.accounts.admin import SelectAccountAdminMixin
from orchestra.contrib.accounts.filters import IsActiveListFilter from orchestra.contrib.accounts.filters import IsActiveListFilter
@ -42,7 +43,7 @@ class SystemUserAdmin(ChangePasswordAdminMixin, SelectAccountAdminMixin, Extende
form = SystemUserChangeForm form = SystemUserChangeForm
ordering = ('-id',) ordering = ('-id',)
change_view_actions = (set_permission, create_link) change_view_actions = (set_permission, create_link)
actions = (delete_selected, list_accounts) + change_view_actions actions = (disable, enable, delete_selected, list_accounts) + change_view_actions
def display_main(self, user): def display_main(self, user):
return user.is_main return user.is_main

View File

@ -6,7 +6,7 @@ from django.utils.encoding import force_text
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.admin import ExtendedModelAdmin from orchestra.admin import ExtendedModelAdmin
from orchestra.admin.actions import disable from orchestra.admin.actions import disable, enable
from orchestra.admin.utils import admin_link, change_url from orchestra.admin.utils import admin_link, change_url
from orchestra.contrib.accounts.actions import list_accounts from orchestra.contrib.accounts.actions import list_accounts
from orchestra.contrib.accounts.admin import AccountAdminMixin, SelectAccountAdminMixin from orchestra.contrib.accounts.admin import AccountAdminMixin, SelectAccountAdminMixin
@ -76,7 +76,7 @@ class WebsiteAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
filter_by_account_fields = ['domains'] filter_by_account_fields = ['domains']
list_prefetch_related = ('domains', 'content_set__webapp') list_prefetch_related = ('domains', 'content_set__webapp')
search_fields = ('name', 'account__username', 'domains__name', 'content__webapp__name') search_fields = ('name', 'account__username', 'domains__name', 'content__webapp__name')
actions = (disable, list_accounts) actions = (disable, enable, list_accounts)
def display_domains(self, website): def display_domains(self, website):
domains = [] domains = []