Added SaaS application

This commit is contained in:
Marc 2014-09-26 19:21:09 +00:00
parent 99b14f9c9f
commit eec726fcc6
17 changed files with 295 additions and 57 deletions

View file

@ -98,4 +98,8 @@ Remember that, as always with QuerySets, any subsequent chained methods which im
* mail backend related_models = ('resources__content_type') ?? * mail backend related_models = ('resources__content_type') ??
* ignore orders * ignore orders
* Redmine, BSCW and other applications management * Dropdown menu for Account services/management object-tools
* Domain backend PowerDNS Bind validation support?
* Maildir billing tests/ webdisk billing tests (avg metric)

View file

@ -3,8 +3,9 @@ from django.conf.urls import patterns, url
from django.contrib import admin from django.contrib import admin
from django.contrib.admin.utils import unquote from django.contrib.admin.utils import unquote
from django.forms.models import BaseInlineFormSet from django.forms.models import BaseInlineFormSet
from django.shortcuts import render, redirect
from .utils import set_url_query, action_to_view from .utils import set_url_query, action_to_view, wrap_admin_view
class ChangeListDefaultFilter(object): class ChangeListDefaultFilter(object):
@ -129,3 +130,58 @@ class ChangeAddFieldsMixin(object):
class ExtendedModelAdmin(ChangeViewActionsMixin, ChangeAddFieldsMixin, admin.ModelAdmin): class ExtendedModelAdmin(ChangeViewActionsMixin, ChangeAddFieldsMixin, admin.ModelAdmin):
pass pass
class SelectPluginAdminMixin(object):
plugin = None
plugin_field = None
def get_form(self, request, obj=None, **kwargs):
if obj:
self.form = obj.method_class().get_form()
else:
self.form = self.plugin.get_plugin(self.plugin_value)().get_form()
return super(SelectPluginAdminMixin, self).get_form(request, obj=obj, **kwargs)
def get_urls(self):
""" Hooks select account url """
urls = super(SelectPluginAdminMixin, self).get_urls()
opts = self.model._meta
info = opts.app_label, opts.model_name
select_urls = patterns("",
url("/select-plugin/$",
wrap_admin_view(self, self.select_plugin_view),
name='%s_%s_select_plugin' % info),
)
return select_urls + urls
def select_plugin_view(self, request):
opts = self.model._meta
context = {
'opts': opts,
'app_label': opts.app_label,
'field': self.plugin_field,
'field_name': opts.get_field_by_name(self.plugin_field)[0].verbose_name,
'plugins': self.plugin.get_plugin_choices(),
}
template = 'admin/orchestra/select_plugin.html'
return render(request, template, context)
def add_view(self, request, form_url='', extra_context=None):
""" Redirects to select account view if required """
if request.user.is_superuser:
plugin_value = request.GET.get(self.plugin_field)
if plugin_value or self.plugin.get_plugins() == 1:
self.plugin_value = plugin_value
if not plugin_value:
self.plugin_value = self.plugin.get_plugins()[0]
return super(SelectPluginAdminMixin, self).add_view(request,
form_url=form_url, extra_context=extra_context)
# TODO add plugin name on title
return redirect('./select-plugin/?%s' % request.META['QUERY_STRING'])
def save_model(self, request, obj, form, change):
if not change:
setattr(obj, self.plugin_field, self.plugin_value)
obj.save()

View file

@ -1,11 +1,9 @@
from django.conf.urls import patterns, url
from django.contrib import admin from django.contrib import admin
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.shortcuts import render, redirect
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from orchestra.admin import ChangeViewActionsMixin from orchestra.admin import ChangeViewActionsMixin, SelectPluginAdminMixin
from orchestra.admin.utils import admin_colored, admin_link, wrap_admin_view from orchestra.admin.utils import admin_colored, admin_link
from orchestra.apps.accounts.admin import AccountAdminMixin from orchestra.apps.accounts.admin import AccountAdminMixin
from . import actions from . import actions
@ -22,55 +20,11 @@ STATE_COLORS = {
} }
class PaymentSourceAdmin(AccountAdminMixin, admin.ModelAdmin): class PaymentSourceAdmin(SelectPluginAdminMixin, AccountAdminMixin, admin.ModelAdmin):
list_display = ('label', 'method', 'number', 'account_link', 'is_active') list_display = ('label', 'method', 'number', 'account_link', 'is_active')
list_filter = ('method', 'is_active') list_filter = ('method', 'is_active')
plugin = PaymentMethod
def get_form(self, request, obj=None, **kwargs): plugin_field = 'method'
if obj:
self.form = obj.method_class().get_form()
else:
self.form = PaymentMethod.get_plugin(self.method)().get_form()
return super(PaymentSourceAdmin, self).get_form(request, obj=obj, **kwargs)
def get_urls(self):
""" Hooks select account url """
urls = super(PaymentSourceAdmin, self).get_urls()
opts = self.model._meta
info = opts.app_label, opts.model_name
select_urls = patterns("",
url("/select-method/$",
wrap_admin_view(self, self.select_method_view),
name='%s_%s_select_method' % info),
)
return select_urls + urls
def select_method_view(self, request):
opts = self.model._meta
context = {
'opts': opts,
'app_label': opts.app_label,
'methods': PaymentMethod.get_plugin_choices(),
}
template = 'admin/payments/payment_source/select_method.html'
return render(request, template, context)
def add_view(self, request, form_url='', extra_context=None):
""" Redirects to select account view if required """
if request.user.is_superuser:
method = request.GET.get('method')
if method or PaymentMethod.get_plugins() == 1:
self.method = method
if not method:
self.method = PaymentMethod.get_plugins()[0]
return super(PaymentSourceAdmin, self).add_view(request,
form_url=form_url, extra_context=extra_context)
return redirect('./select-method/?%s' % request.META['QUERY_STRING'])
def save_model(self, request, obj, form, change):
if not change:
obj.method = self.method
obj.save()
class TransactionInline(admin.TabularInline): class TransactionInline(admin.TabularInline):

View file

@ -44,7 +44,7 @@ class PaymentMethod(plugins.Plugin):
class PaymentSourceDataForm(forms.ModelForm): class PaymentSourceDataForm(forms.ModelForm):
class Meta: class Meta:
exclude = ('data',) # TODO add 'method' exclude = ('data', 'method')
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(PaymentSourceDataForm, self).__init__(*args, **kwargs) super(PaymentSourceDataForm, self).__init__(*args, **kwargs)

View file

View file

@ -0,0 +1,17 @@
from django.contrib import admin
from orchestra.admin import SelectPluginAdminMixin
from orchestra.apps.accounts.admin import AccountAdminMixin
from .models import SaaS
from .services import SoftwareService
class SaaSAdmin(SelectPluginAdminMixin, AccountAdminMixin, admin.ModelAdmin):
list_display = ('id', 'service', 'account_link')
list_filter = ('service',)
plugin = SoftwareService
plugin_field = 'service'
admin.site.register(SaaS, SaaSAdmin)

View file

@ -0,0 +1,21 @@
from django.db import models
from django.utils.translation import ugettext_lazy as _
from jsonfield import JSONField
from orchestra.core import services
from .services import SoftwareService
class SaaS(models.Model):
service = models.CharField(_("service"), max_length=32,
choices=SoftwareService.get_plugin_choices())
account = models.ForeignKey('accounts.Account', verbose_name=_("account"), related_name='saas')
data = JSONField(_("data"))
class Meta:
verbose_name = "SaaS"
verbose_name_plural = "SaaS"
services.register(SaaS)

View file

@ -0,0 +1 @@
from .options import SoftwareService

View file

@ -0,0 +1,13 @@
from django import forms
from django.utils.translation import ugettext_lazy as _
from .options import SoftwareService, SoftwareServiceForm
class BSCWForm(SoftwareServiceForm):
quota = forms.IntegerField(label=_("Quota"))
class BSCWService(SoftwareService):
verbose_name = "BSCW"
form = BSCWForm

View file

@ -0,0 +1,13 @@
from django import forms
from django.utils.translation import ugettext_lazy as _
from .options import SoftwareService, SoftwareServiceForm
class GitLabForm(SoftwareServiceForm):
project_name = forms.CharField(label=_("Project name"), max_length=64)
class GitLabService(SoftwareService):
verbose_name = "GitLab"
form = GitLabForm

View file

@ -0,0 +1,38 @@
from django import forms
from django.utils.translation import ugettext_lazy as _
from orchestra.utils import plugins
from orchestra.utils.functional import cached
from orchestra.utils.python import import_class
from .. import settings
class SoftwareServiceForm(forms.ModelForm):
username = forms.CharField(label=_("Username"), max_length=64)
password = forms.CharField(label=_("Password"), max_length=64)
class Meta:
exclude = ('data', 'service')
class SoftwareService(plugins.Plugin):
label_field = 'label'
form = SoftwareServiceForm
serializer = None
@classmethod
@cached
def get_plugins(cls):
plugins = []
for cls in settings.SAAS_ENABLED_SERVICES:
plugins.append(import_class(cls))
return plugins
def get_form(self):
self.form.plugin = self
return self.form
def get_serializer(self):
self.serializer.plugin = self
return self.serializer

View file

@ -0,0 +1,7 @@
from django.conf import settings
SAAS_ENABLED_SERVICES = getattr(settings, 'SAAS_ENABLED_SERVICES', (
'orchestra.apps.saas.services.bscw.BSCWService',
'orchestra.apps.saas.services.gitlab.GitLabService',
))

View file

@ -31,6 +31,9 @@ class ContractedPlan(models.Model):
account = models.ForeignKey('accounts.Account', verbose_name=_("account"), account = models.ForeignKey('accounts.Account', verbose_name=_("account"),
related_name='plans') related_name='plans')
class Meta:
verbose_name_plural = _("plans")
def __unicode__(self): def __unicode__(self):
return str(self.plan) return str(self.plan)

View file

@ -78,6 +78,7 @@ INSTALLED_APPS = (
'orchestra.apps.websites', 'orchestra.apps.websites',
'orchestra.apps.databases', 'orchestra.apps.databases',
'orchestra.apps.vps', 'orchestra.apps.vps',
'orchestra.apps.saas',
'orchestra.apps.issues', 'orchestra.apps.issues',
'orchestra.apps.services', 'orchestra.apps.services',
'orchestra.apps.orders', 'orchestra.apps.orders',
@ -184,6 +185,7 @@ FLUENT_DASHBOARD_APP_ICONS = {
'databases/databaseuser': 'postgresql.png', 'databases/databaseuser': 'postgresql.png',
'vps/vps': 'TuxBox.png', 'vps/vps': 'TuxBox.png',
'miscellaneous/miscellaneous': 'applications-other.png', 'miscellaneous/miscellaneous': 'applications-other.png',
'saas/saas': 'saas.png',
# Accounts # Accounts
'accounts/account': 'Face-monkey.png', 'accounts/account': 'Face-monkey.png',
'contacts/contact': 'contact_book.png', 'contacts/contact': 'contact_book.png',

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -3,13 +3,13 @@
{% block content %} {% block content %}
<h1>Select a method for the new payment source</h1> <h1>Select a {{ field_name }} for the new {{ opts.object_name }} instance</h1>
<form action="" method="post">{% csrf_token %} <form action="" method="post">{% csrf_token %}
<div> <div>
<div style="margin:20px;"> <div style="margin:20px;">
<ul> <ul>
{% for name, verbose in methods %} {% for name, verbose in plugins %}
<li><a href="../?method={{ name }}&{{ request.META.QUERY_STRING }}">{{ verbose }}</<a></li> <li><a href="../?{{ field }}={{ name }}&{{ request.META.QUERY_STRING }}">{{ verbose }}</<a></li>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>