diff --git a/TODO.md b/TODO.md index 9a88a19a..c1d7ad1f 100644 --- a/TODO.md +++ b/TODO.md @@ -391,4 +391,6 @@ TODO mount the filesystem with "nosuid" option # wkhtmltopdf -> reportlab +# MAKE DEPENDENCIES OPTIONAL, check on deploy and warn that functionallity will not be available + diff --git a/orchestra/bin/orchestra-admin b/orchestra/bin/orchestra-admin index 90df6bbc..35448916 100755 --- a/orchestra/bin/orchestra-admin +++ b/orchestra/bin/orchestra-admin @@ -126,13 +126,17 @@ function install_requirements () { python3-pip \ python3-dev \ libxml2-dev \ + bind9utils \ libxslt1-dev \ wkhtmltopdf \ xvfb \ ca-certificates \ gettext" - PIP=$(wget https://raw.githubusercontent.com/glic3rinu/django-orchestra/master/requirements.txt -q -O - | tr '\n' ' ') + PIP="$(wget https://raw.githubusercontent.com/glic3rinu/django-orchestra/master/requirements.txt -q -O - | tr '\n' ' ') \ + https://github.com/glic3rinu/passlib/archive/master.zip \ + cracklib \ + lxml==3.3.5" if $testing; then APT="${APT} \ diff --git a/orchestra/conf/project_template/project_name/settings.py b/orchestra/conf/project_template/project_name/settings.py index 0d239ea6..6c63c438 100644 --- a/orchestra/conf/project_template/project_name/settings.py +++ b/orchestra/conf/project_template/project_name/settings.py @@ -66,6 +66,7 @@ INSTALLED_APPS = [ 'passlib.ext.django', 'django_countries', # 'django_mailer', +# 'debug_toolbar', # Django.contrib 'django.contrib.auth', diff --git a/orchestra/contrib/accounts/apps.py b/orchestra/contrib/accounts/apps.py index 0e3980bd..e3d54378 100644 --- a/orchestra/contrib/accounts/apps.py +++ b/orchestra/contrib/accounts/apps.py @@ -2,7 +2,7 @@ from django.apps import AppConfig from django.db.models.signals import post_migrate from django.utils.translation import ugettext_lazy as _ -from .management import create_initial_superuser +from orchestra.core import services, accounts class AccountConfig(AppConfig): @@ -10,5 +10,9 @@ class AccountConfig(AppConfig): verbose_name = _("Accounts") def ready(self): + from .management import create_initial_superuser + from .models import Account + services.register(Account, menu=False) + accounts.register(Account) post_migrate.connect(create_initial_superuser, dispatch_uid="orchestra.contrib.accounts.management.createsuperuser") diff --git a/orchestra/contrib/accounts/management/__init__.py b/orchestra/contrib/accounts/management/__init__.py index 896207c2..c77b34aa 100644 --- a/orchestra/contrib/accounts/management/__init__.py +++ b/orchestra/contrib/accounts/management/__init__.py @@ -2,7 +2,9 @@ import sys import textwrap from django.contrib.auth import get_user_model +from django.core.exceptions import FieldError from django.core.management import execute_from_command_line +from django.db import models def create_initial_superuser(**kwargs): @@ -15,5 +17,11 @@ def create_initial_superuser(**kwargs): """) ) + from ..models import Account + try: + Account.systemusers.related.model.objects.filter(account_id=1).exists() + except FieldError: + # avoid creating a systemuser when systemuser table is not ready + Account.save = models.Model.save manager = sys.argv[0] execute_from_command_line(argv=[manager, 'createsuperuser']) diff --git a/orchestra/contrib/accounts/models.py b/orchestra/contrib/accounts/models.py index 3ba10b56..5033eeed 100644 --- a/orchestra/contrib/accounts/models.py +++ b/orchestra/contrib/accounts/models.py @@ -7,7 +7,6 @@ from django.utils.translation import ugettext_lazy as _ from orchestra.contrib.orchestration.middlewares import OperationsMiddleware from orchestra.contrib.orchestration import Operation -from orchestra.core import services, accounts from orchestra.utils import send_email_template from . import settings @@ -156,7 +155,3 @@ class Account(auth.AbstractBaseUser): continue related.append(rel) return related - - -services.register(Account, menu=False) -accounts.register(Account) diff --git a/orchestra/contrib/domains/bin/named-checkzone b/orchestra/contrib/domains/bin/named-checkzone deleted file mode 100755 index 3d2961c8..00000000 Binary files a/orchestra/contrib/domains/bin/named-checkzone and /dev/null differ diff --git a/orchestra/contrib/domains/validators.py b/orchestra/contrib/domains/validators.py index 7147c9c1..0b5a67b2 100644 --- a/orchestra/contrib/domains/validators.py +++ b/orchestra/contrib/domains/validators.py @@ -1,3 +1,4 @@ +import logging import os import re @@ -11,6 +12,9 @@ from orchestra.utils.sys import run from .. import domains +logger = logging.getLogger(__name__) + + def validate_allowed_domain(value): context = { 'site_dir': paths.get_site_dir() @@ -114,9 +118,11 @@ def validate_zone(zone): with open(zone_path, 'wb') as f: f.write(zone.encode('ascii')) # Don't use /dev/stdin becuase the 'argument list is too long' error - check = run(' '.join([checkzone, zone_name, zone_path]), error_codes=[0,1], display=False) + check = run(' '.join([checkzone, zone_name, zone_path]), error_codes=[0,1,127], display=False) finally: os.unlink(zone_path) - if check.return_code == 1: + if check.return_code == 127: + logger.error("Cannot validate domain zone: %s not installed." % checkzone) + elif check.return_code == 1: errors = re.compile(r'zone.*: (.*)').findall(check.stdout)[:-1] raise ValidationError(', '.join(errors)) diff --git a/orchestra/contrib/orders/__init__.py b/orchestra/contrib/orders/__init__.py index e69de29b..753c362c 100644 --- a/orchestra/contrib/orders/__init__.py +++ b/orchestra/contrib/orders/__init__.py @@ -0,0 +1 @@ +default_app_config = 'orchestra.contrib.orders.apps.OrdersConfig' diff --git a/orchestra/contrib/orders/apps.py b/orchestra/contrib/orders/apps.py new file mode 100644 index 00000000..39934323 --- /dev/null +++ b/orchestra/contrib/orders/apps.py @@ -0,0 +1,15 @@ +from django.apps import AppConfig + +from orchestra.core import accounts +from orchestra.utils import database_ready + + +class OrdersConfig(AppConfig): + name = 'orchestra.contrib.orders' + verbose_name = 'Orders' + + def ready(self): + from .models import Order + accounts.register(Order) + if database_ready(): + from . import signals diff --git a/orchestra/contrib/orders/models.py b/orchestra/contrib/orders/models.py index 87652add..51ff7f44 100644 --- a/orchestra/contrib/orders/models.py +++ b/orchestra/contrib/orders/models.py @@ -5,18 +5,16 @@ import logging from django.db import models from django.db.models import F, Q from django.apps import apps -from django.db.models.signals import post_delete, post_save, pre_delete -from django.dispatch import receiver from django.contrib.contenttypes.fields import GenericForeignKey from django.contrib.contenttypes.models import ContentType from django.utils import timezone from django.utils.translation import ugettext_lazy as _ -from orchestra.core import accounts, services +from orchestra.core import services from orchestra.models import queryset from orchestra.utils.python import import_class -from . import helpers, settings +from . import settings logger = logging.getLogger(__name__) @@ -273,38 +271,3 @@ class MetricStorage(models.Model): else: last.updated_on = now last.save(update_fields=['updated_on']) - - -accounts.register(Order) - - -# TODO perhas use cache = caches.get_request_cache() to cache an account delete and don't processes get_related_objects() if the case -# FIXME https://code.djangoproject.com/ticket/24576 -# TODO build a cache hash table {model: related, model: None} -@receiver(post_delete, dispatch_uid="orders.cancel_orders") -def cancel_orders(sender, **kwargs): - if sender._meta.app_label not in settings.ORDERS_EXCLUDED_APPS: - instance = kwargs['instance'] - # Account delete will delete all related orders, no need to maintain order consistency - if isinstance(instance, Order.account.field.rel.to): - return - if type(instance) in services: - for order in Order.objects.by_object(instance).active(): - order.cancel() - elif not hasattr(instance, 'account'): - # FIXME Indeterminate behaviour - related = helpers.get_related_object(instance) - if related and related != instance: - type(related).objects.get(pk=related.pk) - - -@receiver(post_save, dispatch_uid="orders.update_orders") -def update_orders(sender, **kwargs): - if sender._meta.app_label not in settings.ORDERS_EXCLUDED_APPS: - instance = kwargs['instance'] - if type(instance) in services: - Order.update_orders(instance) - elif not hasattr(instance, 'account'): - related = helpers.get_related_object(instance) - if related and related != instance: - Order.update_orders(related) diff --git a/orchestra/contrib/orders/signals.py b/orchestra/contrib/orders/signals.py new file mode 100644 index 00000000..58f5cc84 --- /dev/null +++ b/orchestra/contrib/orders/signals.py @@ -0,0 +1,39 @@ +from django.db.models.signals import post_delete, post_save, pre_delete +from django.dispatch import receiver + +from orchestra.core import services + +from . import helpers, settings +from .models import Order + + +# TODO perhas use cache = caches.get_request_cache() to cache an account delete and don't processes get_related_objects() if the case +# FIXME https://code.djangoproject.com/ticket/24576 +# TODO build a cache hash table {model: related, model: None} +@receiver(post_delete, dispatch_uid="orders.cancel_orders") +def cancel_orders(sender, **kwargs): + if sender._meta.app_label not in settings.ORDERS_EXCLUDED_APPS: + instance = kwargs['instance'] + # Account delete will delete all related orders, no need to maintain order consistency + if isinstance(instance, Order.account.field.rel.to): + return + if type(instance) in services: + for order in Order.objects.by_object(instance).active(): + order.cancel() + elif not hasattr(instance, 'account'): + # FIXME Indeterminate behaviour + related = helpers.get_related_object(instance) + if related and related != instance: + type(related).objects.get(pk=related.pk) + + +@receiver(post_save, dispatch_uid="orders.update_orders") +def update_orders(sender, **kwargs): + if sender._meta.app_label not in settings.ORDERS_EXCLUDED_APPS: + instance = kwargs['instance'] + if type(instance) in services: + Order.update_orders(instance) + elif not hasattr(instance, 'account'): + related = helpers.get_related_object(instance) + if related and related != instance: + Order.update_orders(related) diff --git a/orchestra/contrib/payments/methods/options.py b/orchestra/contrib/payments/methods/options.py index 092e96dc..fce3215c 100644 --- a/orchestra/contrib/payments/methods/options.py +++ b/orchestra/contrib/payments/methods/options.py @@ -1,3 +1,4 @@ +import logging from dateutil import relativedelta from orchestra import plugins @@ -7,6 +8,9 @@ from orchestra.utils.python import import_class from .. import settings +logger = logging.getLogger(__name__) + + class PaymentMethod(plugins.Plugin): label_field = 'label' number_field = 'number' @@ -19,7 +23,10 @@ class PaymentMethod(plugins.Plugin): def get_plugins(cls): plugins = [] for cls in settings.PAYMENTS_ENABLED_METHODS: - plugins.append(import_class(cls)) + try: + plugins.append(import_class(cls)) + except ImportError as exc: + logger.error(str(exc)) return plugins def get_label(self): diff --git a/orchestra/contrib/resources/apps.py b/orchestra/contrib/resources/apps.py index b543e9bc..aa49c144 100644 --- a/orchestra/contrib/resources/apps.py +++ b/orchestra/contrib/resources/apps.py @@ -1,3 +1,4 @@ +from django import db from django.apps import AppConfig from orchestra.utils import database_ready @@ -10,7 +11,11 @@ class ResourcesConfig(AppConfig): def ready(self): if database_ready(): from .models import create_resource_relation - create_resource_relation() + try: + create_resource_relation() + except db.utils.OperationalError: + # Not ready afterall + pass def reload_relations(self): from .admin import insert_resource_inlines diff --git a/orchestra/contrib/systemusers/apps.py b/orchestra/contrib/systemusers/apps.py index b1cca6e6..418f364e 100644 --- a/orchestra/contrib/systemusers/apps.py +++ b/orchestra/contrib/systemusers/apps.py @@ -1,6 +1,28 @@ +import sys + from django.apps import AppConfig +from django.db.models.signals import post_migrate + +from orchestra.core import services class SystemUsersConfig(AppConfig): name = 'orchestra.contrib.systemusers' verbose_name = "System users" + + def ready(self): + from .models import SystemUser + services.register(SystemUser) + if 'migrate' in sys.argv and 'accounts' not in sys.argv: + post_migrate.connect(self.create_initial_systemuser, + dispatch_uid="orchestra.contrib.systemusers.apps.create_initial_systemuser") + + def create_initial_systemuser(self, **kwargs): + from .models import SystemUser + Account = SystemUser.account.field.related.model + for account in Account.objects.filter(is_superuser=True, main_systemuser_id__isnull=True): + systemuser = SystemUser.objects.create(username=account.username, + password=account.password, account=account) + account.main_systemuser = systemuser + account.save() + sys.stdout.write("Created initial systemuser %s.\n" % systemuser.username) diff --git a/orchestra/contrib/systemusers/models.py b/orchestra/contrib/systemusers/models.py index 59a8cc96..11190f93 100644 --- a/orchestra/contrib/systemusers/models.py +++ b/orchestra/contrib/systemusers/models.py @@ -108,6 +108,3 @@ class SystemUser(models.Model): def get_home(self): return os.path.normpath(os.path.join(self.home, self.directory)) - - -services.register(SystemUser) diff --git a/orchestra/core/validators.py b/orchestra/core/validators.py index 90fb0950..ae8906ef 100644 --- a/orchestra/core/validators.py +++ b/orchestra/core/validators.py @@ -1,11 +1,7 @@ +import logging import re -try: - import crack -except: - import cracklib as crack import phonenumbers - from django.core import validators from django.core.exceptions import ValidationError from django.utils.translation import ugettext_lazy as _ @@ -14,6 +10,9 @@ from IPy import IP from ..utils.python import import_class +logger = logging.getLogger(__name__) + + def all_valid(*args): """ helper function to merge multiple validators at once """ if len(args) == 1: @@ -103,6 +102,14 @@ def validate_username(value): def validate_password(value): + try: + import crack + except: + try: + import cracklib as crack + except: + logger.error("Can not validate password. Cracklib bindings are not installed.") + return try: crack.VeryFascistCheck(value) except ValueError as message: diff --git a/orchestra/urls.py b/orchestra/urls.py index 74cc45ae..f0053a51 100644 --- a/orchestra/urls.py +++ b/orchestra/urls.py @@ -3,11 +3,13 @@ from django.conf import settings from django.conf.urls import patterns, include, url from . import api +from .utils.apps import isinstalled admin.autodiscover() api.autodiscover() + urlpatterns = patterns('', # Admin url(r'^admin/', include(admin.site.urls)), @@ -27,7 +29,7 @@ urlpatterns = patterns('', ) -if settings.DEBUG: +if isinstalled('debug_toolbar'): import debug_toolbar urlpatterns += patterns('', url(r'^__debug__/', include(debug_toolbar.urls)), diff --git a/requirements.txt b/requirements.txt index 610e5292..9981a207 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ -cracklib django==1.8.1 django-celery-email==1.0.4 django-fluent-dashboard==0.5 @@ -16,9 +15,7 @@ paramiko==1.15.1 ecdsa==0.11 Pygments==1.6 django-filter==0.7 -https://github.com/glic3rinu/passlib/archive/master.zip jsonfield==0.9.22 -lxml==3.3.5 python-dateutil==2.2 django-iban==0.3.0 requests