Updated ROADMAP

This commit is contained in:
Marc 2014-09-28 12:28:57 +00:00
parent ccf50d0515
commit 6874060fc3
11 changed files with 95 additions and 51 deletions

View file

@ -1,45 +1,58 @@
# 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
2. [x] Automated installation and upgrading
2. [ ] Testing framework for running unittests and functional tests
2. [ ] Continuous integration environment
2. [x] Admin interface based on django.contrib.admin foundations
3. [x] REST API based on django-rest-framework foundations
2. [ ] Testing framework for running unittests and functional tests with LXC containers
2. [ ] Continuous integration with Jenkins
2. [x] Admin interface based on django.contrib.admin
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
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:
1. [x] Web applications
2. [ ] FTP accounts
2. [ ] Databases
1. [ ] Mail accounts, aliases, forwards
1. [x] DNS
1. [ ] Mailing lists
4. [ ] Data model, input validation, admin and REST interfaces, permissions, unit and functional tests, service management, migration scripts and documentation of:
1. [x] PHP/static Web applications
1. [x] Websites with Apache
2. [-] FTP/rsync/scp/shell system accounts
2. [-] Databases and database users
1. [-] Mail accounts, aliases, forwards with Postfix and Dovecot
1. [x] DNS with Bind
1. [-] Mailing lists with Mailman
1. [x] Contact management and service contraction
1. [ ] Object level permissions system
1. [ ] Unittests of all the logic
2. [ ] Functional tests of all Admin and REST interations
1. [-] Unittests of the bussines logic logic
2. [-] Functional tests of all Admin UI and REST interations
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. [ ] Orders
2. [ ] Pricing
3. [ ] Billing
1. [ ] Payment methods
2. [ ] Scheduling of service cancellations and deactivations
1. [x] Resource allocation and monitoring
1. [x] Order tracking
2. [x] Service definition, service plans and pricing
3. [-] Billing
3. [x] Invoice
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
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. [ ] 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
2. [ ] Scheduling of service cancellations and deactivations
1. [ ] Object level permissions system
2. [ ] API access for superusers

18
TODO.md
View file

@ -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 )
* Transaction states: CREATED, PROCESSED, EXECUTED, COMMITED, ABORTED (SECURED, REJECTED?)
* bill.send() -> transacction.EXECUTED when source=None
* 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
* transaction.ABORTED -> bill.bad_debt
- 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
@ -94,7 +93,6 @@ Remember that, as always with QuerySets, any subsequent chained methods which im
def register_on(self):
return order.register_at.date()
* mail backend related_models = ('resources__content_type') ??
* ignore orders
@ -103,3 +101,17 @@ Remember that, as always with QuerySets, any subsequent chained methods which im
* Domain backend PowerDNS Bind validation support?
* 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 ;)

View file

@ -4,6 +4,8 @@ from django.contrib import admin
from django.contrib.admin.utils import unquote
from django.forms.models import BaseInlineFormSet
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
@ -175,11 +177,24 @@ class SelectPluginAdminMixin(object):
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
context = {
'title': _("Add new %s") % camel_case_to_spaces(self.plugin_value),
}
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'])
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):
if not change:
setattr(obj, self.plugin_field, self.plugin_value)

View file

@ -64,11 +64,12 @@ class ResourceAdmin(ExtendedModelAdmin):
class ResourceDataAdmin(admin.ModelAdmin):
list_display = (
'id', 'resource', 'used', 'allocated', 'updated_at', 'content_object_link'
'id', 'resource_link', 'used', 'allocated', 'updated_at', 'content_object_link'
)
list_filter = ('resource',)
readonly_fields = ('content_object_link',)
resource_link = admin_link('resource')
content_object_link = admin_link('content_object')
def get_queryset(self, request):

View file

@ -18,6 +18,9 @@ class SaaS(models.Model):
verbose_name = "SaaS"
verbose_name_plural = "SaaS"
def __unicode__(self):
return "%s (%s)" % (self.description, self.service_class.verbose_name)
@cached_property
def service_class(self):
return SoftwareService.get_plugin(self.service)

View file

@ -1,10 +1,14 @@
from django import forms
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"))

View file

@ -1,12 +1,16 @@
from django import forms
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)
email = forms.CharField(label=_("Email"), max_length=64)
email = forms.EmailField(label=_("Email"))
class GitLabService(SoftwareService):

View file

@ -1,7 +1,6 @@
from django import forms
from django.utils.translation import ugettext_lazy as _
from orchestra.forms import PluginDataForm
from orchestra.utils import plugins
from orchestra.utils.functional import cached
from orchestra.utils.python import import_class
@ -9,17 +8,9 @@ from orchestra.utils.python import import_class
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):
description_field = ''
form = SoftwareServiceForm
form = None
serializer = None
@classmethod

View file

@ -4,4 +4,5 @@ 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',
'orchestra.apps.saas.services.phplist.PHPListService',
))

View file

@ -37,9 +37,9 @@
{% if not is_popup %}
<!-- Header -->
{% block header-stetic %}<div id="header">{% endblock %}
{% block header-stetic %}<div id="header"><div id="header-wrapper">{% endblock %}
<div id="branding">
{% block branding %}{% endblock %}
{% block branding %}{% endblock %}
</div>
{% if user.is_active and user.is_staff %}
<div id="user-tools">
@ -69,7 +69,7 @@
</div>
{% endif %}
{% block nav-global %}{% endblock %}
</div>
</div></div>
<!-- END Header -->
{% block breadcrumbs %}<div class="breadcrumbs"><a href="/">{% trans 'Home' %}</a>{% if title %} &rsaquo; {{ title }}{% endif %}</div>{% endblock %}
{% endif %}

View file

@ -9,7 +9,7 @@
<div style="margin:20px;">
<ul>
{% 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 %}
</ul>
</div>