diff --git a/TODO.md b/TODO.md index d35791e1..48edb8ae 100644 --- a/TODO.md +++ b/TODO.md @@ -313,16 +313,15 @@ celery max-tasks-per-child * webapp has_website list filter - -# FIXME account deletion generates a integrity error - - -apt-get install python3 python3-pip -cp /usr/local/lib/python2.7/dist-packages/orchestra.pth /usr/local/lib/python3.4/dist-packages/ glic3rinu's django-fluent-dashboard * gevent is not ported to python3 :'( * uwsgi python3 +https://github.com/django-nose/django-nose/archive/master.zip +django_debug_toolbar-1.3.0-py2.py3-none-any.whl +# FIXME account deletion generates a integrity error # FIXME what to do when deleting accounts? set fk null and fill a username charfield? issues, invoices.. we whant all this to go away? * implement delete All related services + +* address name change does not remove old one :P diff --git a/orchestra/admin/filters.py b/orchestra/admin/filters.py index e3c4bd34..27e4e276 100644 --- a/orchestra/admin/filters.py +++ b/orchestra/admin/filters.py @@ -9,7 +9,7 @@ class UsedContentTypeFilter(SimpleListFilter): def lookups(self, request, model_admin): qset = model_admin.model._default_manager.all().order_by() result = () - for pk, name in qset.values_list('content_type', 'content_type__name').distinct(): + for pk, name in qset.values_list('content_type', 'content_type__model').distinct(): result += ((str(pk), name.capitalize()),) return result diff --git a/orchestra/apps/bills/admin.py b/orchestra/apps/bills/admin.py index 79be1ef8..020e665c 100644 --- a/orchestra/apps/bills/admin.py +++ b/orchestra/apps/bills/admin.py @@ -165,9 +165,9 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin): transactions = bill.transactions.all() if len(transactions) == 1: args = (transactions[0].pk,) - url = reverse('admin:%s_%s_change' % (t_opts.app_label, t_opts.module_name), args=args) + url = reverse('admin:%s_%s_change' % (t_opts.app_label, t_opts.model_name), args=args) else: - url = reverse('admin:%s_%s_changelist' % (t_opts.app_label, t_opts.module_name)) + url = reverse('admin:%s_%s_changelist' % (t_opts.app_label, t_opts.model_name)) url += '?bill=%i' % bill.pk state = bill.get_payment_state_display().upper() color = PAYMENT_STATE_COLORS.get(bill.payment_state, 'grey') diff --git a/orchestra/apps/databases/forms.py b/orchestra/apps/databases/forms.py index b8717bf8..a3440e2d 100644 --- a/orchestra/apps/databases/forms.py +++ b/orchestra/apps/databases/forms.py @@ -92,7 +92,7 @@ class ReadOnlySQLPasswordHashField(ReadOnlyPasswordHashField): summary = mark_safe("%s" % _("No password set.")) else: size = len(value) - summary = value[:size/2] + '*'*(size-size/2) + summary = value[:int(size/2)] + '*'*int(size-size/2) summary = "hash: %s" % summary if value.startswith('*'): summary = "algorithm: sha1_bin_hex %s" % summary diff --git a/orchestra/apps/domains/admin.py b/orchestra/apps/domains/admin.py index e0ab1e01..0769ebb5 100644 --- a/orchestra/apps/domains/admin.py +++ b/orchestra/apps/domains/admin.py @@ -60,7 +60,7 @@ class DomainInline(admin.TabularInline): class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin): list_display = ( - 'structured_name', 'display_is_top', 'websites', 'account_link' + 'structured_name', 'display_is_top', 'display_websites', 'account_link' ) add_fields = ('name', 'account') fields = ('name', 'account_link') @@ -85,7 +85,7 @@ class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin): display_is_top.boolean = True display_is_top.admin_order_field = 'top' - def websites(self, domain): + def display_websites(self, domain): if apps.isinstalled('orchestra.apps.websites'): webs = domain.websites.all() if webs: @@ -95,9 +95,9 @@ class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin): links.append('%s' % (url, web.name)) return '
'.join(links) return _("No website") - websites.admin_order_field = 'websites__name' - websites.short_description = _("Websites") - websites.allow_tags = True + display_websites.admin_order_field = 'websites__name' + display_websites.short_description = _("Websites") + display_websites.allow_tags = True def get_queryset(self, request): """ Order by structured name and imporve performance """ diff --git a/orchestra/apps/orchestration/middlewares.py b/orchestra/apps/orchestration/middlewares.py index 062bee33..a9b08746 100644 --- a/orchestra/apps/orchestration/middlewares.py +++ b/orchestra/apps/orchestration/middlewares.py @@ -38,10 +38,7 @@ class OperationsMiddleware(object): Stores all the operations derived from save and delete signals and executes them at the end of the request/response cycle - It also works as a transaction middleware. Each view function will be run - with commit_on_response activated - that way a save() doesn't do a direct - commit, the commit is done when a successful response is created. If an - exception happens, the database is rolled back. + It also works as a transaction middleware, making requets to run within an atomic block. """ # Thread local is used because request object is not available on model signals thread_locals = local() @@ -77,55 +74,38 @@ class OperationsMiddleware(object): instance = kwargs.pop('instance') manager.collect(instance, action, **kwargs) - def commit_transaction(self): - if not transaction.get_autocommit(): - if transaction.is_dirty(): - # Note: it is possible that the commit fails. If the reason is - # closed connection or some similar reason, then there is - # little hope to proceed nicely. However, in some cases ( - # deferred foreign key checks for exampl) it is still possible - # to rollback(). - try: - transaction.commit() - except Exception: - # If the rollback fails, the transaction state will be - # messed up. It doesn't matter, the connection will be set - # to clean state after the request finishes. And, we can't - # clean the state here properly even if we wanted to, the - # connection is in transaction but we can't rollback... - transaction.rollback() - transaction.leave_transaction_management() - raise - transaction.leave_transaction_management() + def enter_transaction_management(self): + type(self).thread_locals.transaction = transaction.atomic() + type(self).thread_locals.transaction.__enter__() + + def leave_transaction_management(self, exception=None): + type(self).thread_locals.transaction.__exit__(exception, None, None) def process_request(self, request): """ Store request on a thread local variable """ type(self).thread_locals.request = request - # Enters transaction management - transaction.enter_transaction_management() + self.enter_transaction_management() def process_exception(self, request, exception): """Rolls back the database and leaves transaction management""" - if transaction.is_dirty(): - # This rollback might fail because of network failure for example. - # If rollback isn't possible it is impossible to clean the - # connection's state. So leave the connection in dirty state and - # let request_finished signal deal with cleaning the connection. - transaction.rollback() - transaction.leave_transaction_management() + self.leave_transaction_management(exception) def process_response(self, request, response): """ Processes pending backend operations """ if not isinstance(response, HttpResponseServerError): operations = type(self).get_pending_operations() if operations: - scripts, block = manager.generate(operations) + try: + scripts, block = 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.commit_transaction() + self.leave_transaction_management() logs = manager.execute(scripts, block=block) if logs and resolve(request.path).app_name == 'admin': message_user(request, logs) return response - self.commit_transaction() + self.leave_transaction_management() return response diff --git a/orchestra/apps/payments/admin.py b/orchestra/apps/payments/admin.py index 4496eb85..701cff41 100644 --- a/orchestra/apps/payments/admin.py +++ b/orchestra/apps/payments/admin.py @@ -94,7 +94,7 @@ class TransactionAdmin(SelectAccountAdminMixin, ExtendedModelAdmin): filter_by_account_fields = ('bill', 'source') change_readonly_fields = ('amount', 'currency') readonly_fields = ('bill_link', 'display_state', 'process_link', 'account_link', 'source_link') - list_select_related = ('account', 'source', 'bill__account') + list_select_related = ('source', 'bill__account') bill_link = admin_link('bill') source_link = admin_link('source') diff --git a/orchestra/bin/orchestra-admin b/orchestra/bin/orchestra-admin index 889595e3..dd0076c4 100755 --- a/orchestra/bin/orchestra-admin +++ b/orchestra/bin/orchestra-admin @@ -135,7 +135,7 @@ function install_requirements () { ca-certificates \ gettext" - PIP="django==1.7.7 \ + PIP="django==1.8 \ django-celery-email==1.0.4 \ django-fluent-dashboard==0.4 \ https://bitbucket.org/izi/django-admin-tools/get/a0abfffd76a0.zip \ @@ -174,8 +174,8 @@ function install_requirements () { freezegun \ coverage \ orchestra-orm==dev \ - django-debug-toolbar==1.2.1 \ - django-nose==1.2 \ + django-debug-toolbar==1.3.0 \ + https://github.com/django-nose/django-nose/archive/master.zip \ sqlparse \ pyinotify \ --allow-external orchestra-orm --allow-unverified orchestra-orm" diff --git a/orchestra/models/fields.py b/orchestra/models/fields.py index fb1992ec..0a00d4fb 100644 --- a/orchestra/models/fields.py +++ b/orchestra/models/fields.py @@ -15,7 +15,7 @@ class MultiSelectField(models.CharField, metaclass=models.SubfieldBase): 'choices': self.choices } if self.has_default(): - defaults['initial'] = eval(self.get_default()) + defaults['initial'] = self.get_default() defaults.update(kwargs) return MultiSelectFormField(**defaults) @@ -27,13 +27,13 @@ class MultiSelectField(models.CharField, metaclass=models.SubfieldBase): def to_python(self, value): if value: - if isinstance(value, list) and value[0].startswith('('): - # Workaround unknown bug on default model values - # [u"('SUPPORT'", u" 'ADMIN'", u" 'BILLING'", u" 'TECH'", u" 'ADDS'", u" 'EMERGENCY')"] - value = list(eval(', '.join(value))) - if isinstance(value, list): - return value - return value.split(',') +# if isinstance(value, tuple) and value[0].startswith('('): +# # Workaround unknown bug on default model values +# # [u"('SUPPORT'", u" 'ADMIN'", u" 'BILLING'", u" 'TECH'", u" 'ADDS'", u" 'EMERGENCY')"] +# value = list(eval(', '.join(value))) + if isinstance(value, str): + return value.split(',') + return value return [] def contribute_to_class(self, cls, name):