diff --git a/TODO.md b/TODO.md
index 1468bca8..e064b22f 100644
--- a/TODO.md
+++ b/TODO.md
@@ -160,9 +160,6 @@ Remember that, as always with QuerySets, any subsequent chained methods which im
* service.name / verbose_name instead of .description ?
* miscellaneous.name / verbose_name
-* service.invoice_name
-
-* Bills can have sublines?
* proforma without billing contact?
@@ -177,3 +174,5 @@ Remember that, as always with QuerySets, any subsequent chained methods which im
* ManyToManyField.symmetrical = False (user group)
* REST PERMISSIONS
+
+* caching based on def text2int(textnum, numwords={}):
diff --git a/orchestra/apps/accounts/admin.py b/orchestra/apps/accounts/admin.py
index 3ea1bdc8..26460916 100644
--- a/orchestra/apps/accounts/admin.py
+++ b/orchestra/apps/accounts/admin.py
@@ -42,7 +42,7 @@ class AccountAdmin(ChangePasswordAdminMixin, auth.UserAdmin, ExtendedModelAdmin)
)
fieldsets = (
(_("User"), {
- 'fields': ('username', 'password',)
+ 'fields': ('username', 'password', 'main_systemuser_link')
}),
(_("Personal info"), {
'fields': ('first_name', 'last_name', 'email', ('type', 'language'), 'comments'),
@@ -59,12 +59,14 @@ class AccountAdmin(ChangePasswordAdminMixin, auth.UserAdmin, ExtendedModelAdmin)
add_form = AccountCreationForm
form = UserChangeForm
filter_horizontal = ()
- change_readonly_fields = ('username',)
+ change_readonly_fields = ('username', 'main_systemuser_link')
change_form_template = 'admin/accounts/account/change_form.html'
actions = [disable]
change_view_actions = actions
list_select_related = ('billcontact',)
+ main_systemuser_link = admin_link('main_systemuser')
+
def formfield_for_dbfield(self, db_field, **kwargs):
""" Make value input widget bigger """
if db_field.name == 'comments':
@@ -101,9 +103,11 @@ class AccountAdmin(ChangePasswordAdminMixin, auth.UserAdmin, ExtendedModelAdmin)
return fieldsets
def save_model(self, request, obj, form, change):
- super(AccountAdmin, self).save_model(request, obj, form, change)
if not change:
+ form.save_model(obj)
form.save_related(obj)
+ else:
+ super(AccountAdmin, self).save_model(request, obj, form, change)
admin.site.register(Account, AccountAdmin)
diff --git a/orchestra/apps/accounts/forms.py b/orchestra/apps/accounts/forms.py
index 79ddfde1..b3907a63 100644
--- a/orchestra/apps/accounts/forms.py
+++ b/orchestra/apps/accounts/forms.py
@@ -1,3 +1,5 @@
+from collections import OrderedDict
+
from django import forms
from django.db.models.loading import get_model
from django.utils.translation import ugettext_lazy as _
@@ -9,12 +11,12 @@ from .models import Account
def create_account_creation_form():
- fields = {
- 'create_systemuser': forms.BooleanField(initial=True, required=False,
- label=_("Create systemuser"), widget=forms.CheckboxInput(attrs={'disabled': True}),
- help_text=_("Designates whether to creates a related system user with the same "
- "username and password or not."))
- }
+ fields = OrderedDict(**{
+ 'enable_systemuser': forms.BooleanField(initial=True, required=False,
+ label=_("Enable systemuser"),
+ help_text=_("Designates whether to creates an enabled or disabled related system user. "
+ "Notice that a related system user will be always created."))
+ })
for model, key, kwargs, help_text in settings.ACCOUNTS_CREATE_RELATED:
model = get_model(model)
field_name = 'create_%s' % model._meta.model_name
@@ -46,6 +48,8 @@ def create_account_creation_form():
raise forms.ValidationError(
_("A %s with this name already exists") % verbose_name
)
+ def save_model(self, account):
+ account.save(active_systemuser=self.cleaned_data['enable_systemuser'])
def save_related(self, account):
for model, key, related_kwargs, __ in settings.ACCOUNTS_CREATE_RELATED:
@@ -60,6 +64,7 @@ def create_account_creation_form():
fields.update({
'create_related_fields': fields.keys(),
'clean': clean,
+ 'save_model': save_model,
'save_related': save_related,
})
diff --git a/orchestra/apps/accounts/models.py b/orchestra/apps/accounts/models.py
index 28d404f0..23b4fea0 100644
--- a/orchestra/apps/accounts/models.py
+++ b/orchestra/apps/accounts/models.py
@@ -60,12 +60,12 @@ class Account(auth.AbstractBaseUser):
def get_main(cls):
return cls.objects.get(pk=settings.ACCOUNTS_MAIN_PK)
- def save(self, *args, **kwargs):
+ def save(self, active_systemuser=False, *args, **kwargs):
created = not self.pk
super(Account, self).save(*args, **kwargs)
if created:
self.main_systemuser = self.systemusers.create(account=self, username=self.username,
- password=self.password)
+ password=self.password, is_active=active_systemuser)
self.save(update_fields=['main_systemuser'])
def clean(self):
diff --git a/orchestra/apps/orders/models.py b/orchestra/apps/orders/models.py
index fe87735d..0cf2d507 100644
--- a/orchestra/apps/orders/models.py
+++ b/orchestra/apps/orders/models.py
@@ -146,10 +146,7 @@ class Order(models.Model):
if account_id is None:
# New account workaround -> user.account_id == None
continue
- ignore = False
- account = getattr(instance, 'account', instance)
- if account.is_superuser:
- ignore = service.ignore_superusers
+ ignore = service.handler.get_ignore(instance)
order = cls(content_object=instance, service=service,
account_id=account_id, ignore=ignore)
if commit:
@@ -163,8 +160,7 @@ class Order(models.Model):
order.update()
elif orders:
order = orders.get()
- if commit:
- order.cancel()
+ order.cancel(commit=commit)
updates.append((order, 'cancelled'))
return updates
@@ -188,10 +184,12 @@ class Order(models.Model):
self.description = description
self.save(update_fields=['description'])
- def cancel(self):
+ def cancel(self, commit=True):
self.cancelled_on = timezone.now()
- self.save(update_fields=['cancelled_on'])
- logger.info("CANCELLED order id: {id}".format(id=self.id))
+ self.ignore = self.service.handler.get_order_ignore(self)
+ if commit:
+ self.save(update_fields=['cancelled_on', 'ignore'])
+ logger.info("CANCELLED order id: {id}".format(id=self.id))
def mark_as_ignored(self):
self.ignore = True
diff --git a/orchestra/apps/saas/settings.py b/orchestra/apps/saas/settings.py
index c143b2f7..bb782819 100644
--- a/orchestra/apps/saas/settings.py
+++ b/orchestra/apps/saas/settings.py
@@ -10,3 +10,23 @@ SAAS_ENABLED_SERVICES = getattr(settings, 'SAAS_ENABLED_SERVICES', (
'orchestra.apps.saas.services.gitlab.GitLabService',
'orchestra.apps.saas.services.phplist.PHPListService',
))
+
+
+SAAS_WORDPRESSMU_BASE_URL = getattr(settings, 'SAAS_WORDPRESSMU_BASE_URL',
+ 'http://%(site_name)s.example.com')
+
+
+SAAS_WORDPRESSMU_ADMIN_PASSWORD = getattr(settings, 'SAAS_WORDPRESSMU_ADMIN_PASSWORD',
+ 'secret')
+
+
+SAAS_DOKUWIKIMU_TEMPLATE_PATH = setattr(settings, 'SAAS_DOKUWIKIMU_TEMPLATE_PATH',
+ '/home/httpd/htdocs/wikifarm/template.tar.gz')
+
+
+SAAS_DOKUWIKIMU_FARM_PATH = getattr(settings, 'SAAS_DOKUWIKIMU_FARM_PATH',
+ '/home/httpd/htdocs/wikifarm/farm')
+
+
+SAAS_DRUPAL_SITES_PATH = getattr(settings, 'SAAS_DRUPAL_SITES_PATH',
+ '/home/httpd/htdocs/drupal-mu/sites/%(site_name)s')
diff --git a/orchestra/apps/services/admin.py b/orchestra/apps/services/admin.py
index 6e645abf..7ff80239 100644
--- a/orchestra/apps/services/admin.py
+++ b/orchestra/apps/services/admin.py
@@ -42,7 +42,8 @@ class ServiceAdmin(ChangeViewActionsMixin, admin.ModelAdmin):
}),
(_("Billing options"), {
'classes': ('wide',),
- 'fields': ('billing_period', 'billing_point', 'is_fee', 'order_description')
+ 'fields': ('billing_period', 'billing_point', 'is_fee', 'order_description',
+ 'ignore_period')
}),
(_("Pricing options"), {
'classes': ('wide',),
diff --git a/orchestra/apps/services/handlers.py b/orchestra/apps/services/handlers.py
index 5aeeeb75..d555375a 100644
--- a/orchestra/apps/services/handlers.py
+++ b/orchestra/apps/services/handlers.py
@@ -55,6 +55,32 @@ class ServiceHandler(plugins.Plugin):
}
return eval(self.match, safe_locals)
+ def get_ignore_delta(self):
+ if self.ignore_period == self.NEVER:
+ return None
+ value, unit = self.ignore_period.split('_')
+ value = text2int(value)
+ if unit.lowe().startswith('day'):
+ return timedelta(days=value)
+ if unit.lowe().startswith('month'):
+ return timedelta(months=value)
+ else:
+ raise ValueError("Unknown unit %s" % unit)
+
+ def get_order_ignore(self, order):
+ """ service trial delta """
+ ignore_delta = self.get_ignore_delta()
+ if ignore_delta and (order.cancelled_on-ignore_delta).date() <= order.registered_on:
+ return True
+ return order.ignore
+
+ def get_ignore(self, instance):
+ ignore = False
+ account = getattr(instance, 'account', instance)
+ if account.is_superuser:
+ ignore = self.ignore_superusers
+ return ignore
+
def get_metric(self, instance):
if self.metric:
safe_locals = {
diff --git a/orchestra/apps/services/models.py b/orchestra/apps/services/models.py
index 6b1099d8..fc09fd49 100644
--- a/orchestra/apps/services/models.py
+++ b/orchestra/apps/services/models.py
@@ -89,6 +89,8 @@ class Service(models.Model):
# DAILY = 'DAILY'
MONTHLY = 'MONTHLY'
ANUAL = 'ANUAL'
+ ONE_DAY = 'ONE_DAY'
+ TWO_DAYS = 'TWO_DAYS'
TEN_DAYS = 'TEN_DAYS'
ONE_MONTH = 'ONE_MONTH'
ALWAYS = 'ALWAYS'
@@ -158,6 +160,17 @@ class Service(models.Model):
"used for generating the description for the bill lines of this services.
"
"Defaults to '%s: %s' % (handler.description, instance)"
))
+ ignore_period = models.CharField(_("ignore period"), max_length=16, blank=True,
+ help_text=_("Period in which orders will be ignored if cancelled. "
+ "Useful for designating trial periods"),
+ choices=(
+ (NEVER, _("No ignore")),
+ (ONE_DAY, _("One day")),
+ (TWO_DAYS, _("Two days")),
+ (TEN_DAYS, _("Ten days")),
+ (ONE_MONTH, _("One month")),
+ ),
+ default=settings.SERVICES_DEFAULT_IGNORE_PERIOD)
# Pricing
metric = models.CharField(_("metric"), max_length=256, blank=True,
help_text=_(
diff --git a/orchestra/apps/services/settings.py b/orchestra/apps/services/settings.py
index 3605bf35..835b47a6 100644
--- a/orchestra/apps/services/settings.py
+++ b/orchestra/apps/services/settings.py
@@ -1,3 +1,5 @@
+from datetime import timedelta
+
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
@@ -14,3 +16,6 @@ SERVICES_SERVICE_ANUAL_BILLING_MONTH = getattr(settings, 'SERVICES_SERVICE_ANUAL
SERVICES_ORDER_MODEL = getattr(settings, 'SERVICES_ORDER_MODEL', 'orders.Order')
+
+
+SERVICES_DEFAULT_IGNORE_PERIOD = getattr(settings, 'SERVICES_DEFAULT_IGNORE_PERIOD', 'TWO_DAYS')
diff --git a/orchestra/apps/webapps/backends/__init__.py b/orchestra/apps/webapps/backends/__init__.py
index 5173b5fd..b96a592a 100644
--- a/orchestra/apps/webapps/backends/__init__.py
+++ b/orchestra/apps/webapps/backends/__init__.py
@@ -55,7 +55,6 @@ class WebAppServiceMixin(object):
}
-
for __, module_name, __ in pkgutil.walk_packages(__path__):
# sorry for the exec(), but Import module function fails :(
exec('from . import %s' % module_name)
diff --git a/orchestra/apps/webapps/models.py b/orchestra/apps/webapps/models.py
index aa7b9e92..03eba5a1 100644
--- a/orchestra/apps/webapps/models.py
+++ b/orchestra/apps/webapps/models.py
@@ -5,23 +5,17 @@ from django.db import models
from django.utils.translation import ugettext_lazy as _
from orchestra.core import validators, services
+from orchestra.utils import tuple_setting_to_choices, dict_setting_to_choices
from orchestra.utils.functional import cached
from . import settings
-def settings_to_choices(choices):
- return sorted(
- [ (name, opt[0]) for name,opt in choices.iteritems() ],
- key=lambda e: e[0]
- )
-
-
class WebApp(models.Model):
""" Represents a web application """
name = models.CharField(_("name"), max_length=128, validators=[validators.validate_name])
type = models.CharField(_("type"), max_length=32,
- choices=settings_to_choices(settings.WEBAPPS_TYPES),
+ choices=dict_setting_to_choices(settings.WEBAPPS_TYPES),
default=settings.WEBAPPS_DEFAULT_TYPE)
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
related_name='webapps')
@@ -41,20 +35,23 @@ class WebApp(models.Model):
def get_fpm_port(self):
return settings.WEBAPPS_FPM_START_PORT + self.account.pk
- def get_method(self):
- method = settings.WEBAPPS_TYPES[self.type]
- args = method[2] if len(method) == 4 else ()
- return method[1], args
+ def get_directive(self):
+ directive = settings.WEBAPPS_TYPES[self.type]['directive']
+ args = directive[1:] if len(directive) > 1 else ()
+ return directive[0], args
def get_path(self):
context = {
- 'user': self.account.username,
+ 'home': webapp.get_user().get_home(),
'app_name': self.name,
}
return settings.WEBAPPS_BASE_ROOT % context
+ def get_user(self):
+ return self.account.main_systemuser
+
def get_username(self):
- return self.account.username
+ return self.get_user().username
def get_groupname(self):
return self.get_username()
@@ -64,7 +61,7 @@ class WebAppOption(models.Model):
webapp = models.ForeignKey(WebApp, verbose_name=_("Web application"),
related_name='options')
name = models.CharField(_("name"), max_length=128,
- choices=settings_to_choices(settings.WEBAPPS_OPTIONS))
+ choices=tuple_setting_to_choices(settings.WEBAPPS_OPTIONS))
value = models.CharField(_("value"), max_length=256)
class Meta:
diff --git a/orchestra/apps/webapps/settings.py b/orchestra/apps/webapps/settings.py
index 11c7fc8f..2d35dd86 100644
--- a/orchestra/apps/webapps/settings.py
+++ b/orchestra/apps/webapps/settings.py
@@ -2,8 +2,7 @@ from django.conf import settings
from django.utils.translation import ugettext_lazy as _
-# TODO make '%(mainuser_home)s/webapps...
-WEBAPPS_BASE_ROOT = getattr(settings, 'WEBAPPS_BASE_ROOT', '/home/%(user)s/webapps/%(app_name)s/')
+WEBAPPS_BASE_ROOT = getattr(settings, 'WEBAPPS_BASE_ROOT', '%(home)s/webapps/%(app_name)s/')
WEBAPPS_FPM_LISTEN = getattr(settings, 'WEBAPPS_FPM_LISTEN',
@@ -23,57 +22,36 @@ WEBAPPS_FCGID_PATH = getattr(settings, 'WEBAPPS_FCGID_PATH',
WEBAPPS_TYPES = getattr(settings, 'WEBAPPS_TYPES', {
- # { name: ( verbose_name, method_name, method_args, description) }
- 'php5.5': (
- _("PHP 5.5"),
+ 'php5.5': {
+ 'verbose_name': "PHP 5.5",
# 'fpm', ('unix:/var/run/%(user)s-%(app_name)s.sock|fcgi://127.0.0.1%(app_path)s',),
- 'fpm', ('fcgi://127.0.0.1:%(fpm_port)s%(app_path)s',),
- _("This creates a PHP5.5 application under ~/webapps/\n"
- "PHP-FPM will be used to execute PHP files.")
- ),
- 'php5.2': (
- _("PHP 5.2"),
- 'fcgid', (WEBAPPS_FCGID_PATH,),
- _("This creates a PHP5.2 application under ~/webapps/\n"
- "Apache-mod-fcgid will be used to execute PHP files.")
- ),
- 'php4': (
- _("PHP 4"),
- 'fcgid', (WEBAPPS_FCGID_PATH,),
- _("This creates a PHP4 application under ~/webapps/\n"
- "Apache-mod-fcgid will be used to execute PHP files.")
- ),
- 'static': (
- _("Static"),
- 'alias', (),
- _("This creates a Static application under ~/webapps/\n"
- "Apache2 will be used to serve static content and execute CGI files.")
- ),
-# 'wordpress': (
-# _("Wordpress"),
-# 'fpm', ('fcgi://127.0.0.1:8990/home/httpd/wordpress-mu/',),
-# _("This creates a Wordpress site into a shared Wordpress server\n"
-# "By default this blog will be accessible via http://.blogs.example.com")
-#
-# ),
-# 'dokuwiki': (
-# _("DokuWiki"),
-# 'alias', ('/home/httpd/wikifarm/farm/',),
-# _("This create a Dokuwiki wiki into a shared Dokuwiki server\n")
-# ),
-# 'drupal': (
-# _("Drupdal"),
-# 'fpm', ('fcgi://127.0.0.1:8991/home/httpd/drupal-mu/',),
-# _("This creates a Drupal site into a shared Drupal server\n"
-# "The installation will be completed after visiting "
-# "http://.drupal.example.com/install.php?profile=standard&locale=ca\n"
-# "By default this site will be accessible via http://.drupal.example.com")
-# ),
- 'webalizer': (
- _("Webalizer"),
- 'alias', ('%(app_path)s%(site_name)s',),
- _("This creates a Webalizer application under ~/webapps/-\n")
- ),
+ 'directive': ('fpm', 'fcgi://{}%(app_path)s'.format(WEBAPPS_FPM_LISTEN)),
+ 'help_text': _("This creates a PHP5.5 application under ~/webapps/<app_name>
"
+ "PHP-FPM will be used to execute PHP files.")
+ },
+ 'php5.2': {
+ 'verbose_name': "PHP 5.2",
+ 'directive': ('fcgi', WEBAPPS_FCGID_PATH),
+ 'help_text': _("This creates a PHP5.2 application under ~/webapps/<app_name>
"
+ "Apache-mod-fcgid will be used to execute PHP files.")
+ },
+ 'php4': {
+ 'verbose_name': "PHP 4",
+ 'directive': ('fcgi', WEBAPPS_FCGID_PATH,),
+ 'help_text': _("This creates a PHP4 application under ~/webapps/<app_name>
"
+ "Apache-mod-fcgid will be used to execute PHP files.")
+ },
+ 'static': {
+ 'verbose_name': _("Static"),
+ 'directive': ('static',),
+ 'help_text': _("This creates a Static application under ~/webapps/<app_name>
"
+ "Apache2 will be used to serve static content and execute CGI files.")
+ },
+ 'webalizer': {
+ 'verbose_name': "Webalizer",
+ 'directive': ('static', '%(app_path)s%(site_name)s'),
+ 'help_text': _("This creates a Webalizer application under ~/webapps/-")
+ },
})
@@ -194,25 +172,3 @@ WEBAPPS_PHP_DISABLED_FUNCTIONS = getattr(settings, 'WEBAPPS_PHP_DISABLED_FUNCTIO
'escapeshellarg',
'dl'
])
-
-
-# TODO
-WEBAPPS_WORDPRESSMU_BASE_URL = getattr(settings, 'WEBAPPS_WORDPRESSMU_BASE_URL',
- 'http://blogs.example.com')
-
-
-WEBAPPS_WORDPRESSMU_ADMIN_PASSWORD = getattr(settings, 'WEBAPPS_WORDPRESSMU_ADMIN_PASSWORD',
- 'secret')
-
-
-WEBAPPS_DOKUWIKIMU_TEMPLATE_PATH = setattr(settings, 'WEBAPPS_DOKUWIKIMU_TEMPLATE_PATH',
- '/home/httpd/htdocs/wikifarm/template.tar.gz')
-
-
-WEBAPPS_DOKUWIKIMU_FARM_PATH = getattr(settings, 'WEBAPPS_DOKUWIKIMU_FARM_PATH',
- '/home/httpd/htdocs/wikifarm/farm')
-
-
-WEBAPPS_DRUPAL_SITES_PATH = getattr(settings, 'WEBAPPS_DRUPAL_SITES_PATH',
- '/home/httpd/htdocs/drupal-mu/sites/%(app_name)s')
-
diff --git a/orchestra/apps/websites/backends/apache.py b/orchestra/apps/websites/backends/apache.py
index 54a7dc04..1924fd69 100644
--- a/orchestra/apps/websites/backends/apache.py
+++ b/orchestra/apps/websites/backends/apache.py
@@ -65,12 +65,12 @@ class Apache2Backend(ServiceController):
def get_content_directives(self, site):
directives = ''
for content in site.content_set.all().order_by('-path'):
- method, args = content.webapp.get_method()
+ method, args = content.webapp.get_directive()
method = getattr(self, 'get_%s_directives' % method)
directives += method(content, *args)
return directives
- def get_alias_directives(self, content, *args):
+ def get_static_directives(self, content, *args):
context = self.get_content_context(content)
context['path'] = args[0] % context if args else content.webapp.get_path()
return "Alias %(location)s %(path)s\n" % context
@@ -81,10 +81,10 @@ class Apache2Backend(ServiceController):
directive = "ProxyPassMatch ^%(location)s(.*\.php(/.*)?)$ %(fcgi_path)s$1\n"
return directive % context
- def get_fcgid_directives(self, content, fcgid_path):
+ def get_fcgi_directives(self, content, fcgid_path):
context = self.get_content_context(content)
context['fcgid_path'] = fcgid_path % context
- fcgid = self.get_alias_directives(content)
+ fcgid = self.get_static_directives(content)
fcgid += textwrap.dedent("""\
ProxyPass %(location)s !
diff --git a/orchestra/apps/websites/models.py b/orchestra/apps/websites/models.py
index d6a4737a..372aa98f 100644
--- a/orchestra/apps/websites/models.py
+++ b/orchestra/apps/websites/models.py
@@ -5,18 +5,12 @@ from django.db import models
from django.utils.translation import ugettext_lazy as _
from orchestra.core import validators, services
+from orchestra.utils import tuple_setting_to_choices
from orchestra.utils.functional import cached
from . import settings
-def settings_to_choices(choices):
- return sorted(
- [ (name, opt[0]) for name,opt in choices.iteritems() ],
- key=lambda e: e[0]
- )
-
-
class Website(models.Model):
name = models.CharField(_("name"), max_length=128, unique=True,
validators=[validators.validate_name])
@@ -67,7 +61,7 @@ class WebsiteOption(models.Model):
website = models.ForeignKey(Website, verbose_name=_("web site"),
related_name='options')
name = models.CharField(_("name"), max_length=128,
- choices=settings_to_choices(settings.WEBSITES_OPTIONS))
+ choices=tuple_setting_to_choices(settings.WEBSITES_OPTIONS))
value = models.CharField(_("value"), max_length=256)
class Meta:
diff --git a/orchestra/forms/options.py b/orchestra/forms/options.py
index b509763a..5ae8ad9d 100644
--- a/orchestra/forms/options.py
+++ b/orchestra/forms/options.py
@@ -2,6 +2,7 @@ from django import forms
from django.contrib.auth import forms as auth_forms
from django.utils.translation import ugettext, ugettext_lazy as _
+from .. import settings
from ..core.validators import validate_password
@@ -51,8 +52,8 @@ class UserCreationForm(forms.ModelForm):
# self.fields['password1'].validators.append(validate_password)
def clean_password2(self):
- password1 = self.cleaned_data.get("password1")
- password2 = self.cleaned_data.get("password2")
+ password1 = self.cleaned_data.get('password1')
+ password2 = self.cleaned_data.get('password2')
if password1 and password2 and password1 != password2:
raise forms.ValidationError(
self.error_messages['password_mismatch'],
@@ -72,7 +73,10 @@ class UserCreationForm(forms.ModelForm):
def save(self, commit=True):
user = super(UserCreationForm, self).save(commit=False)
- user.set_password(self.cleaned_data["password1"])
+ if settings.ORCHESTRA_MIGRATION_MODE:
+ user.password = self.cleaned_data['password1']
+ else:
+ user.set_password(self.cleaned_data['password1'])
if commit:
user.save()
return user
diff --git a/orchestra/settings.py b/orchestra/settings.py
index 0663969d..268d3889 100644
--- a/orchestra/settings.py
+++ b/orchestra/settings.py
@@ -28,3 +28,6 @@ STOP_SERVICES = getattr(settings, 'STOP_SERVICES',
API_ROOT_VIEW = getattr(settings, 'API_ROOT_VIEW', 'orchestra.api.root.APIRoot')
+
+
+ORCHESTRA_MIGRATION_MODE = getattr(settings, 'ORCHESTRA_MIGRATION_MODE', False)
diff --git a/orchestra/utils/humanize.py b/orchestra/utils/humanize.py
index 855b12b5..2794a313 100644
--- a/orchestra/utils/humanize.py
+++ b/orchestra/utils/humanize.py
@@ -128,3 +128,39 @@ def naturaldate(date):
count = abs(count)
fmt = pluralizefun(count)
return fmt.format(num=count, ago=ago)
+
+
+def text2int(textnum, numwords={}):
+ if not numwords:
+ units = (
+ 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight',
+ 'nine', 'ten', 'eleven', 'twelve', 'thirteen', 'fourteen', 'fifteen',
+ 'sixteen', 'seventeen', 'eighteen', 'nineteen',
+ )
+
+ tens = ('', '', 'twenty', 'thirty', 'forty', 'fifty', 'sixty', 'seventy', 'eighty', 'ninety')
+
+ scales = ['hundred', 'thousand', 'million', 'billion', 'trillion']
+
+ numwords['and'] = (1, 0)
+ for idx, word in enumerate(units):
+ numwords[word] = (1, idx)
+ for idx, word in enumerate(tens):
+ numwords[word] = (1, idx * 10)
+ for idx, word in enumerate(scales):
+ numwords[word] = (10 ** (idx * 3 or 2), 0)
+
+ current = result = 0
+ for word in textnum.split():
+ word = word.lower()
+ if word not in numwords:
+ raise Exception("Illegal word: " + word)
+
+ scale, increment = numwords[word]
+ current = current * scale + increment
+ if scale > 100:
+ result += current
+ current = 0
+
+ return result + current
+
diff --git a/orchestra/utils/options.py b/orchestra/utils/options.py
index 02deedde..0e52ad1b 100644
--- a/orchestra/utils/options.py
+++ b/orchestra/utils/options.py
@@ -45,3 +45,17 @@ def running_syncdb():
def database_ready():
return not running_syncdb() and 'setuppostgres' not in sys.argv and 'test' not in sys.argv
+
+
+def dict_setting_to_choices(choices):
+ return sorted(
+ [ (name, opt.get('verbose_name', 'name')) for name, opt in choices.iteritems() ],
+ key=lambda e: e[0]
+ )
+
+
+def tuple_setting_to_choices(choices):
+ return sorted(
+ [ (name, opt[0]) for name,opt in choices.iteritems() ],
+ key=lambda e: e[0]
+ )