Updated ROADMAP
This commit is contained in:
parent
ccf50d0515
commit
6874060fc3
63
ROADMAP.md
63
ROADMAP.md
|
@ -1,45 +1,58 @@
|
||||||
# Roadmap
|
# Roadmap
|
||||||
|
|
||||||
|
|
||||||
### 1.0a1 Milestone (first alpha release on Sep '14)
|
### 1.0a1 Milestone (first alpha release on Oct '14)
|
||||||
|
|
||||||
1. [x] Automated deployment of the development environment
|
1. [x] Automated deployment of the development environment
|
||||||
2. [x] Automated installation and upgrading
|
2. [x] Automated installation and upgrading
|
||||||
2. [ ] Testing framework for running unittests and functional tests
|
2. [ ] Testing framework for running unittests and functional tests with LXC containers
|
||||||
2. [ ] Continuous integration environment
|
2. [ ] Continuous integration with Jenkins
|
||||||
2. [x] Admin interface based on django.contrib.admin foundations
|
2. [x] Admin interface based on django.contrib.admin
|
||||||
3. [x] REST API based on django-rest-framework foundations
|
3. [x] REST API for users
|
||||||
2. [x] [Orchestra-orm](https://github.com/glic3rinu/orchestra-orm) a Python library for easily interacting with the REST API
|
2. [x] [Orchestra-orm](https://github.com/glic3rinu/orchestra-orm) a Python library for easily interacting with the REST API
|
||||||
3. [x] Service orchestration framework
|
3. [x] Service orchestration framework
|
||||||
4. [ ] Data model, input validation, admin and REST interfaces, permissions, unit and functional tests, service management, migration scripts and some documentation of:
|
4. [ ] Data model, input validation, admin and REST interfaces, permissions, unit and functional tests, service management, migration scripts and documentation of:
|
||||||
1. [x] Web applications
|
1. [x] PHP/static Web applications
|
||||||
2. [ ] FTP accounts
|
1. [x] Websites with Apache
|
||||||
2. [ ] Databases
|
2. [-] FTP/rsync/scp/shell system accounts
|
||||||
1. [ ] Mail accounts, aliases, forwards
|
2. [-] Databases and database users
|
||||||
1. [x] DNS
|
1. [-] Mail accounts, aliases, forwards with Postfix and Dovecot
|
||||||
1. [ ] Mailing lists
|
1. [x] DNS with Bind
|
||||||
|
1. [-] Mailing lists with Mailman
|
||||||
1. [x] Contact management and service contraction
|
1. [x] Contact management and service contraction
|
||||||
1. [ ] Object level permissions system
|
1. [-] Unittests of the bussines logic logic
|
||||||
1. [ ] Unittests of all the logic
|
2. [-] Functional tests of all Admin UI and REST interations
|
||||||
2. [ ] Functional tests of all Admin and REST interations
|
|
||||||
1. [ ] Initial documentation
|
1. [ ] Initial documentation
|
||||||
|
|
||||||
|
|
||||||
### 1.0b1 Milestone (first beta release on Nov '14)
|
### 1.0b1 Milestone (first beta release on Dec '14)
|
||||||
|
|
||||||
1. [x] Resource monitoring
|
1. [x] Resource allocation and monitoring
|
||||||
1. [ ] Orders
|
1. [x] Order tracking
|
||||||
2. [ ] Pricing
|
2. [x] Service definition, service plans and pricing
|
||||||
3. [ ] Billing
|
3. [-] Billing
|
||||||
1. [ ] Payment methods
|
3. [x] Invoice
|
||||||
2. [ ] Scheduling of service cancellations and deactivations
|
3. [x] Membership fee
|
||||||
|
3. [-] Amendment invoice
|
||||||
|
3. [-] Amendment fee
|
||||||
|
3. [x] Pro Forma
|
||||||
|
3. [ ] Advanced bill handling (move lines, undo billing, ...)
|
||||||
|
1. [x] Payment methods
|
||||||
|
1. [x] SEPA Direct Debit
|
||||||
|
2. [x] SEPA Credit Transfer
|
||||||
1. [ ] Full documentation
|
1. [ ] Full documentation
|
||||||
|
2. [-] Additional services
|
||||||
|
2. [-] VPS with Proxmox/OpenVZ
|
||||||
|
2. [-] SaaS (Software as a Service) Redmine/phpList/BSCW/Wordpress/Moodle/Drupal
|
||||||
|
2. [x] Miscellaneous services
|
||||||
|
2. [x] Issue tracking system
|
||||||
|
|
||||||
|
|
||||||
### 1.0 Milestone (first stable release on Feb '15)
|
### 1.0 Milestone (first stable release on Apr '15)
|
||||||
|
|
||||||
1. [ ] Stabilize data model, internal APIs and REST API
|
1. [ ] Stabilize data model, internal APIs and REST API
|
||||||
1. [ ] Integration with third-party service providers, e.g. Gandi
|
1. [ ] Integration with third-party service providers, e.g. Gandi
|
||||||
1. [ ] Support for additional services like VPS
|
|
||||||
2. [ ] Issue tracking system
|
|
||||||
3. [ ] Translation to Spanish and Catalan
|
3. [ ] Translation to Spanish and Catalan
|
||||||
|
2. [ ] Scheduling of service cancellations and deactivations
|
||||||
|
1. [ ] Object level permissions system
|
||||||
|
2. [ ] API access for superusers
|
||||||
|
|
18
TODO.md
18
TODO.md
|
@ -76,7 +76,6 @@ Remember that, as always with QuerySets, any subsequent chained methods which im
|
||||||
|
|
||||||
* help_text on readonly_fields specialy Bill.state. (eg. A bill is in OPEN state when bla bla )
|
* help_text on readonly_fields specialy Bill.state. (eg. A bill is in OPEN state when bla bla )
|
||||||
|
|
||||||
|
|
||||||
* Transaction states: CREATED, PROCESSED, EXECUTED, COMMITED, ABORTED (SECURED, REJECTED?)
|
* Transaction states: CREATED, PROCESSED, EXECUTED, COMMITED, ABORTED (SECURED, REJECTED?)
|
||||||
* bill.send() -> transacction.EXECUTED when source=None
|
* bill.send() -> transacction.EXECUTED when source=None
|
||||||
* transaction.secured() -> bill.paid when bill.total == transaction.value else Error
|
* transaction.secured() -> bill.paid when bill.total == transaction.value else Error
|
||||||
|
@ -84,8 +83,8 @@ Remember that, as always with QuerySets, any subsequent chained methods which im
|
||||||
* bill.bad_debt() -> transaction.ABORTED
|
* bill.bad_debt() -> transaction.ABORTED
|
||||||
* transaction.ABORTED -> bill.bad_debt
|
* transaction.ABORTED -> bill.bad_debt
|
||||||
- Issue new transaction when current transaction is ABORTED
|
- Issue new transaction when current transaction is ABORTED
|
||||||
* underescore *every* private function
|
|
||||||
|
|
||||||
|
* underescore *every* private function
|
||||||
|
|
||||||
* create log file at /var/log/orchestra.log and rotate
|
* create log file at /var/log/orchestra.log and rotate
|
||||||
|
|
||||||
|
@ -94,7 +93,6 @@ Remember that, as always with QuerySets, any subsequent chained methods which im
|
||||||
def register_on(self):
|
def register_on(self):
|
||||||
return order.register_at.date()
|
return order.register_at.date()
|
||||||
|
|
||||||
|
|
||||||
* mail backend related_models = ('resources__content_type') ??
|
* mail backend related_models = ('resources__content_type') ??
|
||||||
* ignore orders
|
* ignore orders
|
||||||
|
|
||||||
|
@ -103,3 +101,17 @@ Remember that, as always with QuerySets, any subsequent chained methods which im
|
||||||
* Domain backend PowerDNS Bind validation support?
|
* Domain backend PowerDNS Bind validation support?
|
||||||
|
|
||||||
* Maildir billing tests/ webdisk billing tests (avg metric)
|
* Maildir billing tests/ webdisk billing tests (avg metric)
|
||||||
|
|
||||||
|
* move icons to apps, and use appconfig to cleanup config stuff
|
||||||
|
|
||||||
|
* when using modeladmin to store shit like self.account, make sure to have a cleanslate in each request
|
||||||
|
|
||||||
|
*jabber with mailbox accounts (dovecto mail notification)
|
||||||
|
|
||||||
|
* rename accounts register to manager register
|
||||||
|
|
||||||
|
* make accounts django auth users
|
||||||
|
- when an account is created a mirrored system user is created
|
||||||
|
- system users are independent users, so they can have different passwords and all.
|
||||||
|
|
||||||
|
* take a look icons from ajenti ;)
|
||||||
|
|
|
@ -4,6 +4,8 @@ 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 django.shortcuts import render, redirect
|
||||||
|
from django.utils.text import camel_case_to_spaces
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from .utils import set_url_query, action_to_view, wrap_admin_view
|
from .utils import set_url_query, action_to_view, wrap_admin_view
|
||||||
|
|
||||||
|
@ -175,11 +177,24 @@ class SelectPluginAdminMixin(object):
|
||||||
self.plugin_value = plugin_value
|
self.plugin_value = plugin_value
|
||||||
if not plugin_value:
|
if not plugin_value:
|
||||||
self.plugin_value = self.plugin.get_plugins()[0]
|
self.plugin_value = self.plugin.get_plugins()[0]
|
||||||
return super(SelectPluginAdminMixin, self).add_view(request,
|
context = {
|
||||||
form_url=form_url, extra_context=extra_context)
|
'title': _("Add new %s") % camel_case_to_spaces(self.plugin_value),
|
||||||
# TODO add plugin name on title
|
}
|
||||||
|
context.update(extra_context or {})
|
||||||
|
return super(SelectPluginAdminMixin, self).add_view(request, form_url=form_url,
|
||||||
|
extra_context=context)
|
||||||
return redirect('./select-plugin/?%s' % request.META['QUERY_STRING'])
|
return redirect('./select-plugin/?%s' % request.META['QUERY_STRING'])
|
||||||
|
|
||||||
|
def change_view(self, request, object_id, form_url='', extra_context=None):
|
||||||
|
obj = self.get_object(request, unquote(object_id))
|
||||||
|
plugin_value = getattr(obj, self.plugin_field)
|
||||||
|
context = {
|
||||||
|
'title': _("Change %s") % camel_case_to_spaces(plugin_value),
|
||||||
|
}
|
||||||
|
context.update(extra_context or {})
|
||||||
|
return super(SelectPluginAdminMixin, self).change_view(request, object_id,
|
||||||
|
form_url=form_url, extra_context=context)
|
||||||
|
|
||||||
def save_model(self, request, obj, form, change):
|
def save_model(self, request, obj, form, change):
|
||||||
if not change:
|
if not change:
|
||||||
setattr(obj, self.plugin_field, self.plugin_value)
|
setattr(obj, self.plugin_field, self.plugin_value)
|
||||||
|
|
|
@ -64,11 +64,12 @@ class ResourceAdmin(ExtendedModelAdmin):
|
||||||
|
|
||||||
class ResourceDataAdmin(admin.ModelAdmin):
|
class ResourceDataAdmin(admin.ModelAdmin):
|
||||||
list_display = (
|
list_display = (
|
||||||
'id', 'resource', 'used', 'allocated', 'updated_at', 'content_object_link'
|
'id', 'resource_link', 'used', 'allocated', 'updated_at', 'content_object_link'
|
||||||
)
|
)
|
||||||
list_filter = ('resource',)
|
list_filter = ('resource',)
|
||||||
readonly_fields = ('content_object_link',)
|
readonly_fields = ('content_object_link',)
|
||||||
|
|
||||||
|
resource_link = admin_link('resource')
|
||||||
content_object_link = admin_link('content_object')
|
content_object_link = admin_link('content_object')
|
||||||
|
|
||||||
def get_queryset(self, request):
|
def get_queryset(self, request):
|
||||||
|
|
|
@ -18,6 +18,9 @@ class SaaS(models.Model):
|
||||||
verbose_name = "SaaS"
|
verbose_name = "SaaS"
|
||||||
verbose_name_plural = "SaaS"
|
verbose_name_plural = "SaaS"
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return "%s (%s)" % (self.description, self.service_class.verbose_name)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def service_class(self):
|
def service_class(self):
|
||||||
return SoftwareService.get_plugin(self.service)
|
return SoftwareService.get_plugin(self.service)
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from .options import SoftwareService, SoftwareServiceForm
|
from orchestra.forms import PluginDataForm
|
||||||
|
|
||||||
|
from .options import SoftwareService
|
||||||
|
|
||||||
|
|
||||||
class BSCWForm(SoftwareServiceForm):
|
class BSCWForm(PluginDataForm):
|
||||||
|
username = forms.CharField(label=_("Username"), max_length=64)
|
||||||
|
password = forms.CharField(label=_("Password"), max_length=64)
|
||||||
quota = forms.IntegerField(label=_("Quota"))
|
quota = forms.IntegerField(label=_("Quota"))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from .options import SoftwareService, SoftwareServiceForm
|
from orchestra.forms import PluginDataForm
|
||||||
|
|
||||||
|
from .options import SoftwareService
|
||||||
|
|
||||||
|
|
||||||
class GitLabForm(SoftwareServiceForm):
|
class GitLabForm(PluginDataForm):
|
||||||
|
username = forms.CharField(label=_("Username"), max_length=64)
|
||||||
|
password = forms.CharField(label=_("Password"), max_length=64)
|
||||||
project_name = forms.CharField(label=_("Project name"), max_length=64)
|
project_name = forms.CharField(label=_("Project name"), max_length=64)
|
||||||
email = forms.CharField(label=_("Email"), max_length=64)
|
email = forms.EmailField(label=_("Email"))
|
||||||
|
|
||||||
|
|
||||||
class GitLabService(SoftwareService):
|
class GitLabService(SoftwareService):
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.forms import PluginDataForm
|
|
||||||
from orchestra.utils import plugins
|
from orchestra.utils import plugins
|
||||||
from orchestra.utils.functional import cached
|
from orchestra.utils.functional import cached
|
||||||
from orchestra.utils.python import import_class
|
from orchestra.utils.python import import_class
|
||||||
|
@ -9,17 +8,9 @@ from orchestra.utils.python import import_class
|
||||||
from .. import settings
|
from .. import settings
|
||||||
|
|
||||||
|
|
||||||
class SoftwareServiceForm(PluginDataForm):
|
|
||||||
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):
|
class SoftwareService(plugins.Plugin):
|
||||||
description_field = ''
|
description_field = ''
|
||||||
form = SoftwareServiceForm
|
form = None
|
||||||
serializer = None
|
serializer = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|
|
@ -4,4 +4,5 @@ from django.conf import settings
|
||||||
SAAS_ENABLED_SERVICES = getattr(settings, 'SAAS_ENABLED_SERVICES', (
|
SAAS_ENABLED_SERVICES = getattr(settings, 'SAAS_ENABLED_SERVICES', (
|
||||||
'orchestra.apps.saas.services.bscw.BSCWService',
|
'orchestra.apps.saas.services.bscw.BSCWService',
|
||||||
'orchestra.apps.saas.services.gitlab.GitLabService',
|
'orchestra.apps.saas.services.gitlab.GitLabService',
|
||||||
|
'orchestra.apps.saas.services.phplist.PHPListService',
|
||||||
))
|
))
|
||||||
|
|
|
@ -37,9 +37,9 @@
|
||||||
|
|
||||||
{% if not is_popup %}
|
{% if not is_popup %}
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
{% block header-stetic %}<div id="header">{% endblock %}
|
{% block header-stetic %}<div id="header"><div id="header-wrapper">{% endblock %}
|
||||||
<div id="branding">
|
<div id="branding">
|
||||||
{% block branding %}{% endblock %}
|
{% block branding %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
{% if user.is_active and user.is_staff %}
|
{% if user.is_active and user.is_staff %}
|
||||||
<div id="user-tools">
|
<div id="user-tools">
|
||||||
|
@ -69,7 +69,7 @@
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% block nav-global %}{% endblock %}
|
{% block nav-global %}{% endblock %}
|
||||||
</div>
|
</div></div>
|
||||||
<!-- END Header -->
|
<!-- END Header -->
|
||||||
{% block breadcrumbs %}<div class="breadcrumbs"><a href="/">{% trans 'Home' %}</a>{% if title %} › {{ title }}{% endif %}</div>{% endblock %}
|
{% block breadcrumbs %}<div class="breadcrumbs"><a href="/">{% trans 'Home' %}</a>{% if title %} › {{ title }}{% endif %}</div>{% endblock %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
<div style="margin:20px;">
|
<div style="margin:20px;">
|
||||||
<ul>
|
<ul>
|
||||||
{% for name, verbose in plugins %}
|
{% for name, verbose in plugins %}
|
||||||
<li><a href="../?{{ field }}={{ name }}&{{ request.META.QUERY_STRING }}">{{ verbose }}</<a></li>
|
<li><a style="font-size:small;" href="../?{{ field }}={{ name }}&{{ request.META.QUERY_STRING }}">{{ verbose }}</<a></li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue