diff --git a/TODO.md b/TODO.md index d6bf4848..ecbd5b3b 100644 --- a/TODO.md +++ b/TODO.md @@ -376,3 +376,18 @@ TODO mount the filesystem with "nosuid" option # orchestrate async stdout stderr (inspired on pangea managemengt commands) # orchestra-beat support for uwsgi cron + +# message.log if 1: return changeform + +# generate nginx certs on project dir rather than nginx + +# Register icons + +# send_message doesn't log task + +make django admin taskstate uncollapse fucking traceback, ( if exists ?) + +# receive tass stuck at RECEIVED +# monitor tasks in started and backend already in success? + +# custom message on admin save diff --git a/orchestra/admin/dashboard.py b/orchestra/admin/dashboard.py index 88e30b60..2bbbc51a 100644 --- a/orchestra/admin/dashboard.py +++ b/orchestra/admin/dashboard.py @@ -1,45 +1,61 @@ from django.core.urlresolvers import reverse +from django.utils.translation import ugettext_lazy as _ from fluent_dashboard import dashboard from fluent_dashboard.modules import CmsAppIconList -from orchestra.core import services +from orchestra.core import services, accounts, administration + + +class AppDefaultIconList(CmsAppIconList): + def __init__(self, *args, **kwargs): + self.icons = kwargs.pop('icons') + super(AppDefaultIconList, self).__init__(*args, **kwargs) + + def get_icon_for_model(self, app_name, model_name, default=None): + icon = self.icons.get('.'.join((app_name, model_name))) + return super(AppDefaultIconList, self).get_icon_for_model(app_name, model_name, default=icon) class OrchestraIndexDashboard(dashboard.FluentIndexDashboard): - _registry = {} - - @classmethod - def register_link(cls, module, view_name, title): - registered = cls._registry.get(module, []) - registered.append((view_name, title)) - cls._registry[module] = registered + def process_registered_view(self, module, view_name, options): + app_name, name = view_name.split('_')[:-1] + module.icons['.'.join((app_name, name))] = options.get('icon') + url = reverse('admin:' + view_name) + add_url = '/'.join(url.split('/')[:-2]) + module.children.append({ + 'models': [{ + 'add_url': add_url, + 'app_name': app_name, + 'change_url': url, + 'name': name, + 'title': options.get('verbose_name')}], + 'name': app_name, + 'title': options.get('verbose_name'), + 'url': add_url, + }) def get_application_modules(self): - modules = super(OrchestraIndexDashboard, self).get_application_modules() - models = [] - for model, options in services.get().items(): - if options.get('menu', True): - models.append("%s.%s" % (model.__module__, model._meta.object_name)) - - for module in modules: - registered = self._registry.get(module.title, None) - if registered: - for view_name, title in registered: - # This values are shit, but it is how fluent dashboard will look for the icon - app_name, name = view_name.split('_')[:-1] - url = reverse('admin:' + view_name) - add_url = '/'.join(url.split('/')[:-2]) - module.children.append({ - 'models': [{ - 'add_url': add_url, - 'app_name': app_name, - 'change_url': url, - 'name': name, - 'title': title }], - 'name': app_name, - 'title': title, - 'url': add_url, - }) - service_icon_list = CmsAppIconList('Services', models=models, collapsible=True) - modules.append(service_icon_list) + from fluent_dashboard import appsettings + modules = [] + # Honor settings override, hacky. I Know + if appsettings.FLUENT_DASHBOARD_APP_GROUPS[0][0] != _('CMS'): + modules = super(OrchestraIndexDashboard, self).get_application_modules() + for register in (accounts, administration, services): + title = register.verbose_name + models = [] + icons = {} + views = [] + for model, options in register.get().items(): + if isinstance(model, str): + views.append((model, options)) + elif options.get('dashboard', True): + opts = model._meta + label = "%s.%s" % (model.__module__, opts.object_name) + models.append(label) + label = '.'.join((opts.app_label, opts.model_name)) + icons[label] = options.get('icon') + module = AppDefaultIconList(title, models=models, icons=icons, collapsible=True) + for view_name, options in views: + self.process_registered_view(module, view_name, options) + modules.append(module) return modules diff --git a/orchestra/admin/menu.py b/orchestra/admin/menu.py index 27d3b4df..788336bd 100644 --- a/orchestra/admin/menu.py +++ b/orchestra/admin/menu.py @@ -27,80 +27,37 @@ def api_link(context): return reverse('api-root') -def process_registered_models(register): - childrens = [] - for model, options in register.get().items(): - if options.get('menu', True): +from copy import copy +def process_registry(register): + def get_item(model, options): + if isinstance(model, str): + url = reverse('admin:'+model) + else: opts = model._meta url = reverse('admin:{}_{}_changelist'.format( - opts.app_label, opts.model_name)) - name = capfirst(options.get('verbose_name_plural')) - childrens.append(items.MenuItem(name, url)) - return childrens - - -def get_services(): - childrens = process_registered_models(services) - return sorted(childrens, key=lambda i: i.title) - - -def get_accounts(): - childrens=[] - if isinstalled('orchestra.contrib.payments'): - url = reverse('admin:payments_transactionprocess_changelist') - childrens.append(items.MenuItem(_("Transaction processes"), url)) - if isinstalled('orchestra.contrib.issues'): - url = reverse('admin:issues_ticket_changelist') - childrens.append(items.MenuItem(_("Tickets"), url)) - childrens.extend(process_registered_models(accounts)) - return sorted(childrens, key=lambda i: i.title) - - -def get_administration_items(): - childrens = [] - if isinstalled('orchestra.contrib.settings'): - url = reverse('admin:settings_setting_change') - childrens.append(items.MenuItem(_("Settings"), url)) - if isinstalled('orchestra.contrib.services'): - url = reverse('admin:services_service_changelist') - childrens.append(items.MenuItem(_("Services"), url)) - url = reverse('admin:plans_plan_changelist') - childrens.append(items.MenuItem(_("Plans"), url)) - if isinstalled('orchestra.contrib.orchestration'): - route = reverse('admin:orchestration_route_changelist') - backendlog = reverse('admin:orchestration_backendlog_changelist') - server = reverse('admin:orchestration_server_changelist') - childrens.append(items.MenuItem(_("Orchestration"), route, children=[ - items.MenuItem(_("Routes"), route), - items.MenuItem(_("Backend logs"), backendlog), - items.MenuItem(_("Servers"), server), - ])) - if isinstalled('orchestra.contrib.resources'): - resource = reverse('admin:resources_resource_changelist') - data = reverse('admin:resources_resourcedata_changelist') - monitor = reverse('admin:resources_monitordata_changelist') - childrens.append(items.MenuItem(_("Resources"), resource, children=[ - items.MenuItem(_("Resources"), resource), - items.MenuItem(_("Data"), data), - items.MenuItem(_("Monitoring"), monitor), - ])) - if isinstalled('orchestra.contrib.miscellaneous'): - url = reverse('admin:miscellaneous_miscservice_changelist') - childrens.append(items.MenuItem(_("Miscellaneous"), url)) - if isinstalled('orchestra.contrib.issues'): - url = reverse('admin:issues_queue_changelist') - childrens.append(items.MenuItem(_("Ticket queues"), url)) - if isinstalled('djcelery'): - task = reverse('admin:djcelery_taskstate_changelist') - periodic = reverse('admin:djcelery_periodictask_changelist') - worker = reverse('admin:djcelery_workerstate_changelist') - childrens.append(items.MenuItem(_("Tasks"), task, children=[ - items.MenuItem(_("Logs"), task), - items.MenuItem(_("Periodic tasks"), periodic), - items.MenuItem(_("Workers"), worker), - ])) - childrens.extend(process_registered_models(administration)) - return childrens + opts.app_label, opts.model_name)) + name = capfirst(options.get('verbose_name_plural')) + return items.MenuItem(name, url) + + childrens = {} + for model, options in register.get().items(): + if options.get('menu', True): + parent = options.get('parent') + if parent: + parent_item = childrens.get(parent) + if parent_item: + if not parent_item.children: + parent_item.children.append(copy(parent_item)) + else: + parent_item = get_item(parent, register[parent]) + parent_item.children = [] + parent_item.children.append(get_item(model, options)) + childrens[parent] = parent_item + elif model not in childrens: + childrens[model] = get_item(model, options) + else: + childrens[model].children.insert(0, get_item(model, options)) + return sorted(childrens.values(), key=lambda i: i.title) class OrchestraMenu(Menu): @@ -122,16 +79,16 @@ class OrchestraMenu(Menu): # items.Bookmarks(), items.MenuItem( _("Services"), - children=get_services() + children=process_registry(services) ), items.MenuItem( _("Accounts"), reverse('admin:accounts_account_changelist'), - children=get_accounts() + children=process_registry(accounts) ), items.MenuItem( _("Administration"), - children=get_administration_items() + children=process_registry(administration) ), items.MenuItem("API", api_link(context)), ] diff --git a/orchestra/conf/project_template/project_name/settings.py b/orchestra/conf/project_template/project_name/settings.py index 81462018..6c940c63 100644 --- a/orchestra/conf/project_template/project_name/settings.py +++ b/orchestra/conf/project_template/project_name/settings.py @@ -233,75 +233,6 @@ ADMIN_TOOLS_MENU = 'orchestra.admin.menu.OrchestraMenu' # Fluent dashboard ADMIN_TOOLS_INDEX_DASHBOARD = 'orchestra.admin.dashboard.OrchestraIndexDashboard' FLUENT_DASHBOARD_ICON_THEME = '../orchestra/icons' -FLUENT_DASHBOARD_APP_GROUPS = ( - # Services group is generated by orchestra.admin.dashboard - ('Accounts', { - 'models': ( - 'orchestra.contrib.accounts.models.Account', - 'orchestra.contrib.contacts.models.Contact', - 'orchestra.contrib.orders.models.Order', - 'orchestra.contrib.plans.models.ContractedPlan', - 'orchestra.contrib.bills.models.Bill', - 'orchestra.contrib.payments.models.Transaction', - 'orchestra.contrib.issues.models.Ticket', - ), - 'collapsible': True, - }), - ('Administration', { - 'models': ( - 'djcelery.models.TaskState', - 'orchestra.contrib.orchestration.models.Route', - 'orchestra.contrib.orchestration.models.BackendLog', - 'orchestra.contrib.orchestration.models.Server', - 'orchestra.contrib.resources.models.Resource', - 'orchestra.contrib.resources.models.ResourceData', - 'orchestra.contrib.services.models.Service', - 'orchestra.contrib.plans.models.Plan', - 'orchestra.contrib.miscellaneous.models.MiscService', - ), - 'collapsible': True, - }), -) - -FLUENT_DASHBOARD_APP_ICONS = { - # Services - 'webs/web': 'web.png', - 'mail/address': 'X-office-address-book.png', - 'mailboxes/mailbox': 'email.png', - 'mailboxes/address': 'X-office-address-book.png', - 'lists/list': 'email-alter.png', - 'domains/domain': 'domain.png', - 'multitenance/tenant': 'apps.png', - 'webapps/webapp': 'Applications-other.png', - 'websites/website': 'Applications-internet.png', - 'databases/database': 'database.png', - 'databases/databaseuser': 'postgresql.png', - 'vps/vps': 'TuxBox.png', - 'miscellaneous/miscellaneous': 'applications-other.png', - 'saas/saas': 'saas.png', - 'systemusers/systemuser': 'roleplaying.png', - # Accounts - 'accounts/account': 'Face-monkey.png', - 'contacts/contact': 'contact_book.png', - 'orders/order': 'basket.png', - 'plans/contractedplan': 'ContractedPack.png', - 'services/service': 'price.png', - 'bills/bill': 'invoice.png', - 'payments/paymentsource': 'card_in_use.png', - 'payments/transaction': 'transaction.png', - 'payments/transactionprocess': 'transactionprocess.png', - 'issues/ticket': 'Ticket_star.png', - 'miscellaneous/miscservice': 'Misc-Misc-Box-icon.png', - # Administration - 'settings/setting': 'preferences.png', - 'djcelery/taskstate': 'taskstate.png', - 'orchestration/server': 'vps.png', - 'orchestration/route': 'hal.png', - 'orchestration/backendlog': 'scriptlog.png', - 'resources/resource': "gauge.png", - 'resources/resourcedata': "monitor.png", - 'plans/plan': 'Pack.png', -} # Django-celery diff --git a/orchestra/contrib/accounts/apps.py b/orchestra/contrib/accounts/apps.py index e3d54378..81e73667 100644 --- a/orchestra/contrib/accounts/apps.py +++ b/orchestra/contrib/accounts/apps.py @@ -12,7 +12,7 @@ class AccountConfig(AppConfig): def ready(self): from .management import create_initial_superuser from .models import Account - services.register(Account, menu=False) - accounts.register(Account) + services.register(Account, menu=False, dashboard=False) + accounts.register(Account, icon='Face-monkey.png') post_migrate.connect(create_initial_superuser, dispatch_uid="orchestra.contrib.accounts.management.createsuperuser") diff --git a/orchestra/contrib/bills/__init__.py b/orchestra/contrib/bills/__init__.py index e69de29b..c568ce60 100644 --- a/orchestra/contrib/bills/__init__.py +++ b/orchestra/contrib/bills/__init__.py @@ -0,0 +1 @@ +default_app_config = 'orchestra.contrib.bills.apps.BillsConfig' diff --git a/orchestra/contrib/bills/apps.py b/orchestra/contrib/bills/apps.py new file mode 100644 index 00000000..ecc74588 --- /dev/null +++ b/orchestra/contrib/bills/apps.py @@ -0,0 +1,12 @@ +from django.apps import AppConfig + +from orchestra.core import accounts + + +class BillsConfig(AppConfig): + name = 'orchestra.contrib.bills' + verbose_name = 'Bills' + + def ready(self): + from .models import Bill + accounts.register(Bill, icon='invoice.png') diff --git a/orchestra/contrib/bills/models.py b/orchestra/contrib/bills/models.py index 9ab06ee0..6e924e8d 100644 --- a/orchestra/contrib/bills/models.py +++ b/orchestra/contrib/bills/models.py @@ -13,7 +13,7 @@ from django.utils.translation import ugettext_lazy as _ from orchestra.contrib.accounts.models import Account from orchestra.contrib.contacts.models import Contact -from orchestra.core import accounts, validators +from orchestra.core import validators from orchestra.utils.html import html_to_pdf from . import settings @@ -351,6 +351,3 @@ class BillSubline(models.Model): # if self.line.bill.is_open: # self.line.bill.total = self.line.bill.get_total() # self.line.bill.save(update_fields=['total']) - - -accounts.register(Bill) diff --git a/orchestra/contrib/contacts/__init__.py b/orchestra/contrib/contacts/__init__.py index e69de29b..3af15748 100644 --- a/orchestra/contrib/contacts/__init__.py +++ b/orchestra/contrib/contacts/__init__.py @@ -0,0 +1 @@ +default_app_config = 'orchestra.contrib.contacts.apps.ContactsConfig' diff --git a/orchestra/contrib/contacts/apps.py b/orchestra/contrib/contacts/apps.py new file mode 100644 index 00000000..4ed7fe70 --- /dev/null +++ b/orchestra/contrib/contacts/apps.py @@ -0,0 +1,12 @@ +from django.apps import AppConfig + +from orchestra.core import accounts + + +class ContactsConfig(AppConfig): + name = 'orchestra.contrib.contacts' + verbose_name = 'Contacts' + + def ready(self): + from .models import Contact + accounts.register(Contact, icon='contact_book.png') diff --git a/orchestra/contrib/contacts/models.py b/orchestra/contrib/contacts/models.py index e981b761..42fee3fe 100644 --- a/orchestra/contrib/contacts/models.py +++ b/orchestra/contrib/contacts/models.py @@ -3,7 +3,7 @@ from django.core.validators import RegexValidator from django.db import models from django.utils.translation import ugettext_lazy as _ -from orchestra.core import accounts, validators +from orchestra.core import validators from orchestra.models.fields import MultiSelectField from . import settings @@ -78,6 +78,3 @@ class Contact(models.Model): errors['zipcode'] = error if errors: raise ValidationError(errors) - - -accounts.register(Contact) diff --git a/orchestra/contrib/databases/__init__.py b/orchestra/contrib/databases/__init__.py index e69de29b..f21f8dda 100644 --- a/orchestra/contrib/databases/__init__.py +++ b/orchestra/contrib/databases/__init__.py @@ -0,0 +1 @@ +default_app_config = 'orchestra.contrib.databases.apps.DatabasesConfig' diff --git a/orchestra/contrib/databases/apps.py b/orchestra/contrib/databases/apps.py new file mode 100644 index 00000000..97f8ef4a --- /dev/null +++ b/orchestra/contrib/databases/apps.py @@ -0,0 +1,14 @@ +from django.apps import AppConfig +from django.utils.translation import ugettext_lazy as _ + +from orchestra.core import services + + +class DatabasesConfig(AppConfig): + name = 'orchestra.contrib.databases' + verbose_name = 'Databases' + + def ready(self): + from .models import Database, DatabaseUser + services.register(Database, icon='database.png') + services.register(DatabaseUser, icon='postgresql.png', verbose_name_plural=_("Database users")) diff --git a/orchestra/contrib/databases/models.py b/orchestra/contrib/databases/models.py index b04e1ff1..2270a1f8 100644 --- a/orchestra/contrib/databases/models.py +++ b/orchestra/contrib/databases/models.py @@ -3,7 +3,7 @@ import hashlib from django.db import models from django.utils.translation import ugettext_lazy as _ -from orchestra.core import validators, services +from orchestra.core import validators from . import settings @@ -76,7 +76,3 @@ class DatabaseUser(models.Model): self.password = '*%s' % hexdigest.upper() else: raise TypeError("Database type '%s' not supported" % self.type) - - -services.register(Database) -services.register(DatabaseUser, verbose_name_plural=_("Database users")) diff --git a/orchestra/contrib/domains/__init__.py b/orchestra/contrib/domains/__init__.py index e69de29b..5c85353e 100644 --- a/orchestra/contrib/domains/__init__.py +++ b/orchestra/contrib/domains/__init__.py @@ -0,0 +1 @@ +default_app_config = 'orchestra.contrib.domains.apps.DomainsConfig' diff --git a/orchestra/contrib/domains/admin.py b/orchestra/contrib/domains/admin.py index 78ff706f..1d7e7ce5 100644 --- a/orchestra/contrib/domains/admin.py +++ b/orchestra/contrib/domains/admin.py @@ -11,8 +11,8 @@ from orchestra.contrib.accounts.admin import AccountAdminMixin from orchestra.utils import apps from .actions import view_zone -from .forms import RecordInlineFormSet, BatchDomainCreationAdminForm from .filters import TopDomainListFilter +from .forms import RecordInlineFormSet, BatchDomainCreationAdminForm from .models import Domain, Record @@ -21,11 +21,6 @@ class RecordInline(admin.TabularInline): formset = RecordInlineFormSet verbose_name_plural = _("Extra records") -# class Media: -# css = { -# 'all': ('orchestra/css/hide-inline-id.css',) -# } -# def formfield_for_dbfield(self, db_field, **kwargs): """ Make value input widget bigger """ if db_field.name == 'value': @@ -73,9 +68,9 @@ class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin): change_view_actions = [view_zone] def structured_name(self, domain): - if not domain.is_top: - return ' '*4 + domain.name - return domain.name + if domain.is_top: + return domain.name + return ' '*4 + domain.name structured_name.short_description = _("name") structured_name.allow_tags = True structured_name.admin_order_field = 'structured_name' diff --git a/orchestra/contrib/domains/apps.py b/orchestra/contrib/domains/apps.py new file mode 100644 index 00000000..559166c4 --- /dev/null +++ b/orchestra/contrib/domains/apps.py @@ -0,0 +1,12 @@ +from django.apps import AppConfig + +from orchestra.core import services + + +class DomainsConfig(AppConfig): + name = 'orchestra.contrib.domains' + verbose_name = 'Domains' + + def ready(self): + from .models import Domain + services.register(Domain, icon='domain.png') diff --git a/orchestra/contrib/domains/models.py b/orchestra/contrib/domains/models.py index 5b44e476..f73743bb 100644 --- a/orchestra/contrib/domains/models.py +++ b/orchestra/contrib/domains/models.py @@ -2,7 +2,6 @@ from django.core.exceptions import ValidationError from django.db import models from django.utils.translation import ungettext, ugettext_lazy as _ -from orchestra.core import services from orchestra.core.validators import validate_ipv4_address, validate_ipv6_address, validate_ascii from orchestra.utils.python import AttrDict @@ -271,6 +270,3 @@ class Record(models.Model): def get_ttl(self): return self.ttl or settings.DOMAINS_DEFAULT_TTL - - -services.register(Domain) diff --git a/orchestra/contrib/issues/__init__.py b/orchestra/contrib/issues/__init__.py index edea65c4..650ba7fe 100644 --- a/orchestra/contrib/issues/__init__.py +++ b/orchestra/contrib/issues/__init__.py @@ -1 +1 @@ -REQUIRED_APPS = ['slices'] +default_app_config = 'orchestra.contrib.issues.apps.IssuesConfig' diff --git a/orchestra/contrib/issues/apps.py b/orchestra/contrib/issues/apps.py new file mode 100644 index 00000000..fdc35f24 --- /dev/null +++ b/orchestra/contrib/issues/apps.py @@ -0,0 +1,13 @@ +from django.apps import AppConfig + +from orchestra.core import accounts, administration + + +class IssuesConfig(AppConfig): + name = 'orchestra.contrib.issues' + verbose_name = "Issues" + + def ready(self): + from .models import Queue, Ticket + accounts.register(Ticket, icon='Ticket_star.png') + administration.register(Queue, dashboard=False) diff --git a/orchestra/contrib/lists/__init__.py b/orchestra/contrib/lists/__init__.py index e69de29b..413f2e03 100644 --- a/orchestra/contrib/lists/__init__.py +++ b/orchestra/contrib/lists/__init__.py @@ -0,0 +1 @@ +default_app_config = 'orchestra.contrib.lists.apps.ListsConfig' diff --git a/orchestra/contrib/lists/apps.py b/orchestra/contrib/lists/apps.py new file mode 100644 index 00000000..7e6e772c --- /dev/null +++ b/orchestra/contrib/lists/apps.py @@ -0,0 +1,12 @@ +from django.apps import AppConfig + +from orchestra.core import services + + +class ListsConfig(AppConfig): + name = 'orchestra.contrib.lists' + verbose_name = 'Lists' + + def ready(self): + from .models import List + services.register(List, icon='email-alter.png') diff --git a/orchestra/contrib/lists/models.py b/orchestra/contrib/lists/models.py index 8d1ba5ed..cb9caefa 100644 --- a/orchestra/contrib/lists/models.py +++ b/orchestra/contrib/lists/models.py @@ -2,7 +2,6 @@ from django.db import models from django.utils.functional import cached_property from django.utils.translation import ugettext_lazy as _ -from orchestra.core import services from orchestra.core.validators import validate_name from . import settings @@ -70,6 +69,3 @@ class List(models.Model): 'name': self.name } return settings.LISTS_LIST_URL % context - - -services.register(List) diff --git a/orchestra/contrib/mailboxes/__init__.py b/orchestra/contrib/mailboxes/__init__.py index e69de29b..dbf89749 100644 --- a/orchestra/contrib/mailboxes/__init__.py +++ b/orchestra/contrib/mailboxes/__init__.py @@ -0,0 +1 @@ +default_app_config = 'orchestra.contrib.mailboxes.apps.MailboxesConfig' diff --git a/orchestra/contrib/mailboxes/apps.py b/orchestra/contrib/mailboxes/apps.py new file mode 100644 index 00000000..9171c4ea --- /dev/null +++ b/orchestra/contrib/mailboxes/apps.py @@ -0,0 +1,13 @@ +from django.apps import AppConfig + +from orchestra.core import services + + +class MailboxesConfig(AppConfig): + name = 'orchestra.contrib.mailboxes' + verbose_name = 'Mailboxes' + + def ready(self): + from .models import Mailbox, Address + services.register(Mailbox, icon='email.png') + services.register(Address, icon='X-office-address-book.png') diff --git a/orchestra/contrib/mailboxes/models.py b/orchestra/contrib/mailboxes/models.py index 7a3811f6..8f4cad4b 100644 --- a/orchestra/contrib/mailboxes/models.py +++ b/orchestra/contrib/mailboxes/models.py @@ -6,8 +6,6 @@ from django.db import models from django.utils.functional import cached_property from django.utils.translation import ugettext_lazy as _ -from orchestra.core import services - from . import validators, settings @@ -152,7 +150,3 @@ class Autoresponse(models.Model): def __str__(self): return self.address - - -services.register(Mailbox) -services.register(Address) diff --git a/orchestra/contrib/mailer/apps.py b/orchestra/contrib/mailer/apps.py index fd8cd958..c680cef8 100644 --- a/orchestra/contrib/mailer/apps.py +++ b/orchestra/contrib/mailer/apps.py @@ -9,4 +9,4 @@ class MailerConfig(AppConfig): def ready(self): from .models import Message - administration.register(Message) + administration.register(Message, icon='Mail-send.png') diff --git a/orchestra/contrib/mailer/models.py b/orchestra/contrib/mailer/models.py index 6a943f47..21786c1e 100644 --- a/orchestra/contrib/mailer/models.py +++ b/orchestra/contrib/mailer/models.py @@ -43,7 +43,7 @@ class Message(models.Model): # Max tries if self.retries >= len(settings.MAILER_DEFERE_SECONDS): self.state = self.FAILED - self.save(update_fields=('state', 'retries')) + self.save(update_fields=('state', 'retries', 'last_retry')) def sent(self): self.state = self.SENT diff --git a/orchestra/contrib/miscellaneous/__init__.py b/orchestra/contrib/miscellaneous/__init__.py index e69de29b..6294909f 100644 --- a/orchestra/contrib/miscellaneous/__init__.py +++ b/orchestra/contrib/miscellaneous/__init__.py @@ -0,0 +1 @@ +default_app_config = 'orchestra.contrib.miscellaneous.apps.MiscellaneousConfig' diff --git a/orchestra/contrib/miscellaneous/apps.py b/orchestra/contrib/miscellaneous/apps.py new file mode 100644 index 00000000..2f5763ae --- /dev/null +++ b/orchestra/contrib/miscellaneous/apps.py @@ -0,0 +1,15 @@ +from django.apps import AppConfig + +from orchestra.core import services, administration +from orchestra.core.translations import ModelTranslation + + +class MiscellaneousConfig(AppConfig): + name = 'orchestra.contrib.miscellaneous' + verbose_name = 'Miscellaneous' + + def ready(self): + from .models import MiscService, Miscellaneous + services.register(Miscellaneous, icon='applications-other.png') + administration.register(MiscService, icon='Misc-Misc-Box-icon.png') + ModelTranslation.register(MiscService, ('verbose_name',)) diff --git a/orchestra/contrib/miscellaneous/models.py b/orchestra/contrib/miscellaneous/models.py index 663b4232..f3233133 100644 --- a/orchestra/contrib/miscellaneous/models.py +++ b/orchestra/contrib/miscellaneous/models.py @@ -2,8 +2,6 @@ from django.db import models from django.utils.functional import cached_property from django.utils.translation import ugettext_lazy as _ -from orchestra.core import services -from orchestra.core.translations import ModelTranslation from orchestra.core.validators import validate_name from orchestra.models.fields import NullableCharField @@ -69,8 +67,3 @@ class Miscellaneous(models.Model): if self.identifier: self.identifier = self.identifier.strip() self.description = self.description.strip() - - -services.register(Miscellaneous) - -ModelTranslation.register(MiscService, ('verbose_name',)) diff --git a/orchestra/contrib/orchestration/__init__.py b/orchestra/contrib/orchestration/__init__.py index 89d0113b..262c9d81 100644 --- a/orchestra/contrib/orchestration/__init__.py +++ b/orchestra/contrib/orchestration/__init__.py @@ -3,6 +3,9 @@ import copy from .backends import ServiceBackend, ServiceController, replace +default_app_config = 'orchestra.contrib.orchestration.apps.OrchestrationConfig' + + class Operation(): DELETE = 'delete' SAVE = 'save' @@ -30,10 +33,10 @@ class Operation(): self.routes = routes @classmethod - def execute(cls, operations, async=False): + def execute(cls, operations, serialize=False, async=False): from . import manager - scripts, block = manager.generate(operations) - return manager.execute(scripts, block=block, async=async) + scripts, oserialize = manager.generate(operations) + return manager.execute(scripts, serialize=(serialize or oserialize), async=async) @classmethod def execute_action(cls, instance, action): diff --git a/orchestra/contrib/orchestration/apps.py b/orchestra/contrib/orchestration/apps.py new file mode 100644 index 00000000..6de145dd --- /dev/null +++ b/orchestra/contrib/orchestration/apps.py @@ -0,0 +1,14 @@ +from django.apps import AppConfig + +from orchestra.core import administration + + +class OrchestrationConfig(AppConfig): + name = 'orchestra.contrib.orchestration' + verbose_name = "Orchestration" + + def ready(self): + from .models import Server, Route, BackendLog + administration.register(BackendLog, icon='scriptlog.png') + administration.register(Server, parent=BackendLog, icon='vps.png') + administration.register(Route, parent=BackendLog, icon='hal.png') diff --git a/orchestra/contrib/orchestration/backends.py b/orchestra/contrib/orchestration/backends.py index d32a2bdf..cd52aa79 100644 --- a/orchestra/contrib/orchestration/backends.py +++ b/orchestra/contrib/orchestration/backends.py @@ -45,7 +45,7 @@ class ServiceBackend(plugins.Plugin, metaclass=ServiceMount): actions = [] default_route_match = 'True' # Force the backend manager to block in multiple backend executions executing them synchronously - block = False + serialize = False doc_settings = None # By default backend will not run if actions do not generate insctructions, # If your backend uses prepare() or commit() only then you should set force_empty_action_execution = True diff --git a/orchestra/contrib/orchestration/management/commands/orchestrate.py b/orchestra/contrib/orchestration/management/commands/orchestrate.py index 69fc11fc..defeb4c4 100644 --- a/orchestra/contrib/orchestration/management/commands/orchestrate.py +++ b/orchestra/contrib/orchestration/management/commands/orchestrate.py @@ -71,7 +71,7 @@ class Command(BaseCommand): else: for instance in queryset: manager.collect(instance, action, operations=operations, route_cache=route_cache) - scripts, block = manager.generate(operations) + scripts, serialize = manager.generate(operations) servers = [] # Print scripts for key, value in scripts.items(): @@ -96,7 +96,7 @@ class Command(BaseCommand): return break if not dry: - logs = manager.execute(scripts, block=block) + logs = manager.execute(scripts, serialize=serialize) for log in logs: self.stdout.write(log.stdout) self.stderr.write(log.stderr) diff --git a/orchestra/contrib/orchestration/manager.py b/orchestra/contrib/orchestration/manager.py index 9cc86d70..0ee544f4 100644 --- a/orchestra/contrib/orchestration/manager.py +++ b/orchestra/contrib/orchestration/manager.py @@ -20,7 +20,7 @@ logger = logging.getLogger(__name__) router = import_class(settings.ORCHESTRATION_ROUTER) -def as_task(execute, log, operations): +def keep_log(execute, log, operations): def wrapper(*args, **kwargs): """ send report """ # Remember that threads have their oun connection poll @@ -30,14 +30,14 @@ def as_task(execute, log, operations): if log.state != log.SUCCESS: send_report(execute, args, log) except Exception as e: - subject = 'EXCEPTION executing backend(s) %s %s' % (str(args), str(kwargs)) - message = traceback.format_exc() - logger.error(subject) - logger.error(message) - mail_admins(subject, message) + trace = traceback.format_exc() log.state = BackendLog.EXCEPTION - log.stderr = traceback.format_exc() - log.save(update_fields=('state', 'stderr')) + log.stderr = trace + log.save() + subject = 'EXCEPTION executing backend(s) %s %s' % (str(args), str(kwargs)) + logger.error(subject) + logger.error(trace) + mail_admins(subject, trace) # We don't propagate the exception further to avoid transaction rollback finally: # Store and log the operation @@ -56,7 +56,7 @@ def as_task(execute, log, operations): def generate(operations): scripts = OrderedDict() cache = {} - block = False + serialize = False # Generate scripts per route+backend for operation in operations: logger.debug("Queued %s" % str(operation)) @@ -86,18 +86,18 @@ def generate(operations): pre_action.send(**kwargs) method(operation.instance) post_action.send(**kwargs) - if backend.block: - block = True + if backend.serialize: + serialize = True for value in scripts.values(): backend, operations = value backend.set_tail() pre_commit.send(sender=backend.__class__, backend=backend) backend.commit() post_commit.send(sender=backend.__class__, backend=backend) - return scripts, block + return scripts, serialize -def execute(scripts, block=False, async=False): +def execute(scripts, serialize=False, async=False): """ executes the operations on the servers """ if settings.ORCHESTRATION_DISABLE_EXECUTION: logger.info('Orchestration execution is dissabled by ORCHESTRATION_DISABLE_EXECUTION settings.') @@ -110,21 +110,22 @@ def execute(scripts, block=False, async=False): route, __ = key backend, operations = value args = (route.host,) + async = not serialize and (async or route.async) kwargs = { - 'async': async or route.async + 'async': async, } log = backend.create_log(*args, **kwargs) kwargs['log'] = log - task = as_task(backend.execute, log, operations) + task = keep_log(backend.execute, log, operations) logger.debug('%s is going to be executed on %s' % (backend, route.host)) - if block: + if serialize: # Execute one backend at a time, no need for threads task(*args, **kwargs) else: task = close_connection(task) thread = threading.Thread(target=task, args=args, kwargs=kwargs) thread.start() - if not route.async: + if not async: threads_to_join.append(thread) logs.append(log) [ thread.join() for thread in threads_to_join ] diff --git a/orchestra/contrib/orchestration/middlewares.py b/orchestra/contrib/orchestration/middlewares.py index 95d68725..94fa73d3 100644 --- a/orchestra/contrib/orchestration/middlewares.py +++ b/orchestra/contrib/orchestration/middlewares.py @@ -97,14 +97,14 @@ class OperationsMiddleware(object): operations = self.get_pending_operations() if operations: try: - scripts, block = manager.generate(operations) + scripts, serialize = manager.generate(operations) except Exception as exception: self.leave_transaction_management(exception) raise # We commit transaction just before executing operations # because here is when IntegrityError show up self.leave_transaction_management() - logs = manager.execute(scripts, block=block) + logs = manager.execute(scripts, serialize=serialize) if logs and resolve(request.path).app_name == 'admin': message_user(request, logs) return response diff --git a/orchestra/contrib/orders/apps.py b/orchestra/contrib/orders/apps.py index 733d281b..ae588789 100644 --- a/orchestra/contrib/orders/apps.py +++ b/orchestra/contrib/orders/apps.py @@ -10,6 +10,6 @@ class OrdersConfig(AppConfig): def ready(self): from .models import Order - accounts.register(Order) + accounts.register(Order, icon='basket.png') if database_ready(): from . import signals diff --git a/orchestra/contrib/payments/__init__.py b/orchestra/contrib/payments/__init__.py index e69de29b..970bd432 100644 --- a/orchestra/contrib/payments/__init__.py +++ b/orchestra/contrib/payments/__init__.py @@ -0,0 +1 @@ +default_app_config = 'orchestra.contrib.payments.apps.PaymentsConfig' diff --git a/orchestra/contrib/payments/apps.py b/orchestra/contrib/payments/apps.py new file mode 100644 index 00000000..7ae1bae4 --- /dev/null +++ b/orchestra/contrib/payments/apps.py @@ -0,0 +1,14 @@ +from django.apps import AppConfig + +from orchestra.core import accounts + + +class PaymentsConfig(AppConfig): + name = 'orchestra.contrib.payments' + verbose_name = "Payments" + + def ready(self): + from .models import PaymentSource, Transaction, TransactionProcess + accounts.register(PaymentSource, dashboard=False) + accounts.register(Transaction, icon='transaction.png') + accounts.register(TransactionProcess, icon='transactionprocess.png', dashboard=False) diff --git a/orchestra/contrib/payments/models.py b/orchestra/contrib/payments/models.py index 520d681f..79ce714a 100644 --- a/orchestra/contrib/payments/models.py +++ b/orchestra/contrib/payments/models.py @@ -4,7 +4,6 @@ from django.utils.functional import cached_property from django.utils.translation import ugettext_lazy as _ from jsonfield import JSONField -from orchestra.core import accounts from orchestra.models.queryset import group_by from . import settings @@ -200,7 +199,3 @@ class TransactionProcess(models.Model): for transaction in self.transactions.processing(): transaction.mark_as_secured() self.save(update_fields=['state']) - - -accounts.register(PaymentSource) -accounts.register(Transaction) diff --git a/orchestra/contrib/plans/__init__.py b/orchestra/contrib/plans/__init__.py index e69de29b..e09642bf 100644 --- a/orchestra/contrib/plans/__init__.py +++ b/orchestra/contrib/plans/__init__.py @@ -0,0 +1 @@ +default_app_config = 'orchestra.contrib.plans.apps.PlansConfig' diff --git a/orchestra/contrib/plans/apps.py b/orchestra/contrib/plans/apps.py new file mode 100644 index 00000000..153501c9 --- /dev/null +++ b/orchestra/contrib/plans/apps.py @@ -0,0 +1,15 @@ +from django.apps import AppConfig + +from orchestra.core import administration, accounts +from orchestra.core.translations import ModelTranslation + + +class PlansConfig(AppConfig): + name = 'orchestra.contrib.plans' + verbose_name = 'Plans' + + def ready(self): + from .models import Plan, ContractedPlan + accounts.register(ContractedPlan, icon='ContractedPack.png') + administration.register(Plan, icon='Pack.png') + ModelTranslation.register(Plan, ('verbose_name',)) diff --git a/orchestra/contrib/plans/models.py b/orchestra/contrib/plans/models.py index 5a9fae38..7caf1a57 100644 --- a/orchestra/contrib/plans/models.py +++ b/orchestra/contrib/plans/models.py @@ -5,7 +5,6 @@ from django.utils.functional import cached_property from django.utils.translation import ugettext_lazy as _ from orchestra.core import services, accounts -from orchestra.core.translations import ModelTranslation from orchestra.core.validators import validate_name from orchestra.models import queryset @@ -100,9 +99,3 @@ class Rate(models.Model): for name, method in cls.RATE_METHODS.items(): choices.append((name, method.verbose_name)) return choices - - -accounts.register(ContractedPlan) -services.register(ContractedPlan, menu=False) - -ModelTranslation.register(Plan, ('verbose_name',)) diff --git a/orchestra/contrib/resources/apps.py b/orchestra/contrib/resources/apps.py index ca3c4215..2cfbde65 100644 --- a/orchestra/contrib/resources/apps.py +++ b/orchestra/contrib/resources/apps.py @@ -1,6 +1,7 @@ from django import db from django.apps import AppConfig +from orchestra.core import administration from orchestra.utils.db import database_ready @@ -16,6 +17,10 @@ class ResourcesConfig(AppConfig): except db.utils.OperationalError: # Not ready afterall pass + from .models import Resource, ResourceData, MonitorData + administration.register(Resource, icon='gauge.png') + administration.register(ResourceData, parent=Resource, icon='monitor.png') + administration.register(MonitorData, parent=Resource, dashboard=False) def reload_relations(self): from .admin import insert_resource_inlines diff --git a/orchestra/contrib/resources/backends.py b/orchestra/contrib/resources/backends.py index e5e7103e..41dd3f39 100644 --- a/orchestra/contrib/resources/backends.py +++ b/orchestra/contrib/resources/backends.py @@ -72,7 +72,7 @@ class ServiceMonitor(ServiceBackend): MonitorData.objects.create(monitor=name, object_id=object_id, content_type=ct, value=value, created_at=self.current_date) - def execute(self, server, async=False): - log = super(ServiceMonitor, self).execute(server, async=async) + def execute(self, *args, **kwargs): + log = super(ServiceMonitor, self).execute(*args, **kwargs) self.store(log) return log diff --git a/orchestra/contrib/resources/migrations/0002_auto_20150502_1429.py b/orchestra/contrib/resources/migrations/0002_auto_20150502_1429.py new file mode 100644 index 00000000..83c9b197 --- /dev/null +++ b/orchestra/contrib/resources/migrations/0002_auto_20150502_1429.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('resources', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='CrontabSchedule', + fields=[ + ('id', models.AutoField(serialize=False, primary_key=True, auto_created=True, verbose_name='ID')), + ('minute', models.CharField(max_length=64, verbose_name='minute', default='*')), + ('hour', models.CharField(max_length=64, verbose_name='hour', default='*')), + ('day_of_week', models.CharField(max_length=64, verbose_name='day of week', default='*')), + ('day_of_month', models.CharField(max_length=64, verbose_name='day of month', default='*')), + ('month_of_year', models.CharField(max_length=64, verbose_name='month of year', default='*')), + ], + options={ + 'verbose_name': 'crontab', + 'ordering': ('month_of_year', 'day_of_month', 'day_of_week', 'hour', 'minute'), + 'verbose_name_plural': 'crontabs', + }, + ), + ] diff --git a/orchestra/contrib/resources/migrations/0003_auto_20150502_1433.py b/orchestra/contrib/resources/migrations/0003_auto_20150502_1433.py new file mode 100644 index 00000000..7a3f3472 --- /dev/null +++ b/orchestra/contrib/resources/migrations/0003_auto_20150502_1433.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('resources', '0002_auto_20150502_1429'), + ] + + operations = [ + ] diff --git a/orchestra/contrib/resources/migrations/0004_auto_20150503_1559.py b/orchestra/contrib/resources/migrations/0004_auto_20150503_1559.py new file mode 100644 index 00000000..1b717fe6 --- /dev/null +++ b/orchestra/contrib/resources/migrations/0004_auto_20150503_1559.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('resources', '0003_auto_20150502_1433'), + ] + + operations = [ + ] diff --git a/orchestra/contrib/resources/models.py b/orchestra/contrib/resources/models.py index df9ceb29..53a1c7ac 100644 --- a/orchestra/contrib/resources/models.py +++ b/orchestra/contrib/resources/models.py @@ -148,9 +148,8 @@ class Resource(models.Model): def monitor(self, async=True): if async: - print(tasks.monitor.delay) - return tasks.monitor.delay(self.pk, async=async) - return tasks.monitor(self.pk, async=async) + return tasks.monitor.apply_async(self.pk) + return tasks.monitor(self.pk) class ResourceData(models.Model): diff --git a/orchestra/contrib/resources/tasks.py b/orchestra/contrib/resources/tasks.py index ee983fa3..e440a36c 100644 --- a/orchestra/contrib/resources/tasks.py +++ b/orchestra/contrib/resources/tasks.py @@ -7,7 +7,7 @@ from .backends import ServiceMonitor @task(name='resources.Monitor') -def monitor(resource_id, ids=None, async=True): +def monitor(resource_id, ids=None): with LockFile('/dev/shm/resources.monitor-%i.lock' % resource_id, expire=60*60): from .models import ResourceData, Resource resource = Resource.objects.get(pk=resource_id) @@ -29,9 +29,7 @@ def monitor(resource_id, ids=None, async=True): for obj in model.objects.filter(**kwargs): op = Operation(backend, obj, Operation.MONITOR) monitorings.append(op) - # TODO async=True only when running with celery - # monitor.request.id - logs += Operation.execute(monitorings, async=async) + logs += Operation.execute(monitorings, async=False) kwargs = {'id__in': ids} if ids else {} # Update used resources and trigger resource exceeded and revovery diff --git a/orchestra/contrib/saas/apps.py b/orchestra/contrib/saas/apps.py index bed646b0..8ad3f8c8 100644 --- a/orchestra/contrib/saas/apps.py +++ b/orchestra/contrib/saas/apps.py @@ -10,6 +10,4 @@ class SaaSConfig(AppConfig): def ready(self): from . import signals from .models import SaaS - services.register(SaaS) - - + services.register(SaaS, icon='saas.png') diff --git a/orchestra/contrib/saas/backends/gitlab.py b/orchestra/contrib/saas/backends/gitlab.py index 55c13cd5..77b8389a 100644 --- a/orchestra/contrib/saas/backends/gitlab.py +++ b/orchestra/contrib/saas/backends/gitlab.py @@ -12,7 +12,7 @@ class GitLabSaaSBackend(ServiceController): verbose_name = _("GitLab SaaS") model = 'saas.SaaS' default_route_match = "saas.service == 'gitlab'" - block = True + serialize = True actions = ('save', 'delete', 'validate_creation') doc_settings = (settings, ('SAAS_GITLAB_DOMAIN', 'SAAS_GITLAB_ROOT_PASSWORD'), diff --git a/orchestra/contrib/saas/backends/phplist.py b/orchestra/contrib/saas/backends/phplist.py index e7ed0aa0..62b8a39b 100644 --- a/orchestra/contrib/saas/backends/phplist.py +++ b/orchestra/contrib/saas/backends/phplist.py @@ -18,7 +18,7 @@ class PhpListSaaSBackend(ServiceController): verbose_name = _("phpList SaaS") model = 'saas.SaaS' default_route_match = "saas.service == 'phplist'" - block = True + serialize = True def _save(self, saas, server): admin_link = 'http://%s/admin/' % saas.get_site_domain() diff --git a/orchestra/contrib/services/apps.py b/orchestra/contrib/services/apps.py index 51f56168..43b2f55c 100644 --- a/orchestra/contrib/services/apps.py +++ b/orchestra/contrib/services/apps.py @@ -1,6 +1,14 @@ from django.apps import AppConfig +from orchestra.core import administration, accounts +from orchestra.core.translations import ModelTranslation + class ServicesConfig(AppConfig): name = 'orchestra.contrib.services' verbose_name = 'Services' + + def ready(self): + from .models import Service + administration.register(Service, icon='price.png') + ModelTranslation.register(Service, ('description',)) diff --git a/orchestra/contrib/services/models.py b/orchestra/contrib/services/models.py index 27daf1ab..862f026d 100644 --- a/orchestra/contrib/services/models.py +++ b/orchestra/contrib/services/models.py @@ -8,7 +8,6 @@ from django.utils.module_loading import autodiscover_modules from django.utils.translation import string_concat, ugettext_lazy as _ from orchestra.core import caches, validators -from orchestra.core.translations import ModelTranslation from orchestra.utils.python import import_class from . import settings @@ -244,6 +243,3 @@ class Service(models.Model): for instance in queryset: updates += order_model.update_orders(instance, service=self, commit=commit) return updates - - -ModelTranslation.register(Service, ('description',)) diff --git a/orchestra/contrib/settings/admin.py b/orchestra/contrib/settings/admin.py index 3a503591..93e44325 100644 --- a/orchestra/contrib/settings/admin.py +++ b/orchestra/contrib/settings/admin.py @@ -103,7 +103,5 @@ class SettingFileView(generic.TemplateView): return context - admin.site.register_url(r'^settings/setting/view/$', SettingFileView.as_view(), 'settings_setting_view') admin.site.register_url(r'^settings/setting/$', SettingView.as_view(), 'settings_setting_change') -OrchestraIndexDashboard.register_link('Administration', 'settings_setting_change', _("Settings")) diff --git a/orchestra/contrib/settings/apps.py b/orchestra/contrib/settings/apps.py index 83f1b749..f93361c7 100644 --- a/orchestra/contrib/settings/apps.py +++ b/orchestra/contrib/settings/apps.py @@ -1,13 +1,21 @@ from django.apps import AppConfig from django.core.checks import register, Error from django.core.exceptions import ValidationError +from django.utils.translation import ngettext, ugettext_lazy as _ + +from orchestra.core import administration from . import Setting + class SettingsConfig(AppConfig): name = 'orchestra.contrib.settings' verbose_name = 'Settings' + def ready(self): + administration.register_view('settings_setting_change', verbose_name=_("Settings"), + icon='Multimedia-volume-control.png') + @register() def check_settings(app_configs, **kwargs): """ perfroms all the validation """ diff --git a/orchestra/contrib/systemusers/apps.py b/orchestra/contrib/systemusers/apps.py index 418f364e..021d92d9 100644 --- a/orchestra/contrib/systemusers/apps.py +++ b/orchestra/contrib/systemusers/apps.py @@ -12,7 +12,7 @@ class SystemUsersConfig(AppConfig): def ready(self): from .models import SystemUser - services.register(SystemUser) + services.register(SystemUser, icon='roleplaying.png') 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") diff --git a/orchestra/contrib/tasks/__init__.py b/orchestra/contrib/tasks/__init__.py index 210d0ac6..e380d425 100644 --- a/orchestra/contrib/tasks/__init__.py +++ b/orchestra/contrib/tasks/__init__.py @@ -2,3 +2,6 @@ import sys from . import settings from .decorators import task, periodic_task, keep_state, apply_async + + +default_app_config = 'orchestra.contrib.tasks.apps.TasksConfig' diff --git a/orchestra/contrib/tasks/apps.py b/orchestra/contrib/tasks/apps.py new file mode 100644 index 00000000..a6fd8f7e --- /dev/null +++ b/orchestra/contrib/tasks/apps.py @@ -0,0 +1,14 @@ +from django.apps import AppConfig + +from orchestra.core import administration + + +class TasksConfig(AppConfig): + name = 'orchestra.contrib.tasks' + verbose_name = "Tasks" + + def ready(self): + from djcelery.models import PeriodicTask, TaskState, WorkerState + administration.register(TaskState, icon='Edit-check-sheet.png') + administration.register(PeriodicTask, parent=TaskState, icon='Appointment.png') + administration.register(WorkerState, parent=TaskState, dashboard=False) diff --git a/orchestra/contrib/tasks/decorators.py b/orchestra/contrib/tasks/decorators.py index c08af43e..725d4a8e 100644 --- a/orchestra/contrib/tasks/decorators.py +++ b/orchestra/contrib/tasks/decorators.py @@ -6,6 +6,7 @@ from threading import Thread from celery import shared_task as celery_shared_task from celery import states from celery.decorators import periodic_task as celery_periodic_task +from django.core.mail import mail_admins from django.utils import timezone from orchestra.utils.db import close_connection @@ -26,11 +27,15 @@ def keep_state(fn): result = fn(*args, **kwargs) except Exception as exc: state.state = states.FAILURE - state.traceback = traceback.format_exc() + state.traceback = trace state.runtime = (timezone.now()-now).total_seconds() state.save() + subject = 'EXCEPTION executing task %s(args=%s, kwargs=%s)' % (name, str(args), str(kwargs)) + trace = traceback.format_exc() + logger.error(subject) + logger.error(trace) + mail_admins(subject, trace) return - # TODO send email else: state.state = states.SUCCESS state.result = str(result) diff --git a/orchestra/contrib/tasks/management/commands/syncperiodictasks.py b/orchestra/contrib/tasks/management/commands/syncperiodictasks.py index d638d99e..6b2b4412 100644 --- a/orchestra/contrib/tasks/management/commands/syncperiodictasks.py +++ b/orchestra/contrib/tasks/management/commands/syncperiodictasks.py @@ -1,2 +1,15 @@ -# create crontab entries for defines periodic tasks +from django.core.management.base import BaseCommand, CommandError +from djcelery.app import app +from djcelery.schedulers import DatabaseScheduler + +class Command(BaseCommand): + help = 'Runs Orchestra method.' + + def handle(self, *args, **options): + dbschedule = DatabaseScheduler(app=app) + self.stdout.write('\033[1m%i periodic tasks have been syncronized:\033[0m' % len(dbschedule.schedule)) + size = max([len(name) for name in dbschedule.schedule])+1 + for name, task in dbschedule.schedule.items(): + spaces = ' '*(size-len(name)) + self.stdout.write(' %s%s%s' % (name, spaces, task.schedule)) diff --git a/orchestra/contrib/vps/__init__.py b/orchestra/contrib/vps/__init__.py index e69de29b..96cf9729 100644 --- a/orchestra/contrib/vps/__init__.py +++ b/orchestra/contrib/vps/__init__.py @@ -0,0 +1 @@ +default_app_config = 'orchestra.contrib.vps.apps.VPSConfig' diff --git a/orchestra/contrib/vps/apps.py b/orchestra/contrib/vps/apps.py new file mode 100644 index 00000000..919bb54d --- /dev/null +++ b/orchestra/contrib/vps/apps.py @@ -0,0 +1,12 @@ +from django.apps import AppConfig + +from orchestra.core import services + + +class VPSConfig(AppConfig): + name = 'orchestra.contrib.vps' + verbose_name = 'VPS' + + def ready(self): + from .models import VPS + services.register(VPS, icon='TuxBox.png') diff --git a/orchestra/contrib/vps/models.py b/orchestra/contrib/vps/models.py index d647871d..27691f3c 100644 --- a/orchestra/contrib/vps/models.py +++ b/orchestra/contrib/vps/models.py @@ -1,8 +1,7 @@ +from django.contrib.auth.hashers import make_password from django.db import models from django.utils.translation import ugettext_lazy as _ -from django.contrib.auth.hashers import make_password -from orchestra.core import services from orchestra.core.validators import validate_hostname from . import settings @@ -32,6 +31,3 @@ class VPS(models.Model): def get_username(self): return self.hostname - - -services.register(VPS) diff --git a/orchestra/contrib/webapps/apps.py b/orchestra/contrib/webapps/apps.py index 6f7c4650..7eb86eef 100644 --- a/orchestra/contrib/webapps/apps.py +++ b/orchestra/contrib/webapps/apps.py @@ -10,4 +10,4 @@ class WebAppsConfig(AppConfig): def ready(self): from . import signals from .models import WebApp - services.register(WebApp) + services.register(WebApp, icon='Applications-other.png') diff --git a/orchestra/contrib/websites/__init__.py b/orchestra/contrib/websites/__init__.py index e69de29b..93cab2d3 100644 --- a/orchestra/contrib/websites/__init__.py +++ b/orchestra/contrib/websites/__init__.py @@ -0,0 +1 @@ +default_app_config = 'orchestra.contrib.websites.apps.WebsitesConfig' diff --git a/orchestra/contrib/websites/apps.py b/orchestra/contrib/websites/apps.py index 7451c44f..41052863 100644 --- a/orchestra/contrib/websites/apps.py +++ b/orchestra/contrib/websites/apps.py @@ -1,17 +1,20 @@ from django.apps import AppConfig from django.contrib.contenttypes.fields import GenericRelation +from orchestra.core import services from orchestra.utils.db import database_ready -class WebsiteConfig(AppConfig): +class WebsitesConfig(AppConfig): name = 'orchestra.contrib.websites' def ready(self): if database_ready(): - from django.contrib.contenttypes.models import ContentType - from .models import Content - qset = Content.content_type.field.get_limit_choices_to() - for ct in ContentType.objects.filter(qset): - relation = GenericRelation('websites.Content') - ct.model_class().add_to_class('content_set', relation) +# from django.contrib.contenttypes.models import ContentType +# from .models import Content, Website +# qset = Content.content_type.field.get_limit_choices_to() +# for ct in ContentType.objects.filter(qset): +# relation = GenericRelation('websites.Content') +# ct.model_class().add_to_class('content_set', relation) + from .models import Website + services.register(Website, icon='Applications-internet.png') diff --git a/orchestra/contrib/websites/models.py b/orchestra/contrib/websites/models.py index 3ea12672..4dffd743 100644 --- a/orchestra/contrib/websites/models.py +++ b/orchestra/contrib/websites/models.py @@ -5,7 +5,7 @@ from django.db import models from django.utils.functional import cached_property from django.utils.translation import ugettext_lazy as _ -from orchestra.core import validators, services +from orchestra.core import validators from orchestra.utils.functional import cached from . import settings @@ -152,6 +152,3 @@ class Content(models.Model): domain = self.website.domains.first() if domain: return '%s://%s%s' % (self.website.get_protocol(), domain, self.path) - - -services.register(Website) diff --git a/orchestra/core/__init__.py b/orchestra/core/__init__.py index 0badee82..f29687bb 100644 --- a/orchestra/core/__init__.py +++ b/orchestra/core/__init__.py @@ -1,9 +1,12 @@ +from django.utils.translation import string_concat + from ..utils.python import AttrDict class Register(object): - def __init__(self): + def __init__(self, verbose_name=None): self._registry = {} + self.verbose_name = verbose_name def __contains__(self, key): return key in self._registry @@ -13,13 +16,21 @@ class Register(object): def register(self, model, **kwargs): if model in self._registry: - raise KeyError("%s already registered" % str(model)) - plural = kwargs.get('verbose_name_plural', model._meta.verbose_name_plural) - self._registry[model] = AttrDict(**{ - 'verbose_name': kwargs.get('verbose_name', model._meta.verbose_name), - 'verbose_name_plural': plural, - 'menu': kwargs.get('menu', True), - }) + raise KeyError("%s already registered" % model) + if 'verbose_name' not in kwargs: + kwargs['verbose_name'] = model._meta.verbose_name + if 'verbose_name_plural' not in kwargs: + kwargs['verbose_name_plural'] = model._meta.verbose_name_plural + self._registry[model] = AttrDict(**kwargs) + + def register_view(self, view_name, **kwargs): + if view_name in self._registry: + raise KeyError("%s already registered" % view_name) + if 'verbose_name' not in kwargs: + raise KeyError("%s verbose_name is required for views" % view_name) + if 'verbose_name_plural' not in kwargs: + kwargs['verbose_name_plural'] = string_concat(kwargs['verbose_name'], 's') + self._registry[view_name] = AttrDict(**kwargs) def get(self, *args): if args: @@ -27,7 +38,7 @@ class Register(object): return self._registry -services = Register() +services = Register(verbose_name='Services') # TODO rename to something else -accounts = Register() -administration = Register() +accounts = Register(verbose_name='Accounts') +administration = Register(verbose_name='Administration') diff --git a/orchestra/static/orchestra/icons/Appointment.png b/orchestra/static/orchestra/icons/Appointment.png new file mode 100644 index 00000000..06e1fe94 Binary files /dev/null and b/orchestra/static/orchestra/icons/Appointment.png differ diff --git a/orchestra/static/orchestra/icons/Appointment.svg b/orchestra/static/orchestra/icons/Appointment.svg new file mode 100644 index 00000000..772b37ac --- /dev/null +++ b/orchestra/static/orchestra/icons/Appointment.svg @@ -0,0 +1,413 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + Jakub Steiner + + + http://jimmac.musichall.cz + + New Appointment + + + appointment + new + meeting + rvsp + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/orchestra/static/orchestra/icons/Edit-check-sheet.png b/orchestra/static/orchestra/icons/Edit-check-sheet.png new file mode 100644 index 00000000..1fcec45a Binary files /dev/null and b/orchestra/static/orchestra/icons/Edit-check-sheet.png differ diff --git a/orchestra/static/orchestra/icons/Edit-check-sheet.svg b/orchestra/static/orchestra/icons/Edit-check-sheet.svg new file mode 100644 index 00000000..e98f8210 --- /dev/null +++ b/orchestra/static/orchestra/icons/Edit-check-sheet.svg @@ -0,0 +1,390 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/orchestra/static/orchestra/icons/Mail-send.png b/orchestra/static/orchestra/icons/Mail-send.png new file mode 100644 index 00000000..2f8fd1f9 Binary files /dev/null and b/orchestra/static/orchestra/icons/Mail-send.png differ diff --git a/orchestra/static/orchestra/icons/Mail-send.svg b/orchestra/static/orchestra/icons/Mail-send.svg new file mode 100644 index 00000000..b114286a --- /dev/null +++ b/orchestra/static/orchestra/icons/Mail-send.svg @@ -0,0 +1,999 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Send and Receive Mail + + + Jakub Steiner + + + + + Andreas Nilsson, Garrett LeSage + + + + + + mail + e-mail + send + receive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/orchestra/static/orchestra/icons/Multimedia-volume-control.png b/orchestra/static/orchestra/icons/Multimedia-volume-control.png new file mode 100644 index 00000000..9e841739 Binary files /dev/null and b/orchestra/static/orchestra/icons/Multimedia-volume-control.png differ diff --git a/orchestra/static/orchestra/icons/Multimedia-volume-control.svg b/orchestra/static/orchestra/icons/Multimedia-volume-control.svg new file mode 100644 index 00000000..ed02b310 --- /dev/null +++ b/orchestra/static/orchestra/icons/Multimedia-volume-control.svg @@ -0,0 +1,242 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + Volume Control + + + multimedia + sound + volume + knob + control + mixer + + + + + Jakub Steiner + + + http://jimmac.musichall.cz + + + + + + + + + + + + + + + + + + + +