Fixes on task state
This commit is contained in:
parent
6fa29a15b8
commit
13cf3a41ed
2
TODO.md
2
TODO.md
|
@ -355,5 +355,3 @@ make django admin taskstate uncollapse fucking traceback, ( if exists ?)
|
|||
# backend.context and backned.instance provided when an action is called? like forms.cleaned_data: do it on manager.generation(backend.context = backend.get_context()) or in backend.__getattr__ ? also backend.head,tail,content switching on manager.generate()?
|
||||
|
||||
# replace return_code by exit_code everywhere
|
||||
|
||||
# plan.rate registry
|
||||
|
|
|
@ -105,7 +105,7 @@ def execute(scripts, serialize=False, async=None):
|
|||
async: do not join threads (overrides route.async)
|
||||
"""
|
||||
if settings.ORCHESTRATION_DISABLE_EXECUTION:
|
||||
logger.info('Orchestration execution is dissabled by ORCHESTRATION_DISABLE_EXECUTION settings.')
|
||||
logger.info('Orchestration execution is dissabled by ORCHESTRATION_DISABLE_EXECUTION.')
|
||||
return []
|
||||
# Execute scripts on each server
|
||||
executions = []
|
||||
|
@ -122,6 +122,7 @@ def execute(scripts, serialize=False, async=None):
|
|||
kwargs = {
|
||||
'async': async,
|
||||
}
|
||||
# we clone the connection just in case we are isolated inside a transaction
|
||||
with db.clone(model=BackendLog) as handle:
|
||||
log = backend.create_log(*args, using=handle.target)
|
||||
log._state.db = handle.origin
|
||||
|
|
|
@ -30,6 +30,7 @@ class ContractedPlanAdmin(AccountAdminMixin, admin.ModelAdmin):
|
|||
list_select_related = ('plan', 'account')
|
||||
search_fields = ('account__username', 'plan__name', 'id')
|
||||
|
||||
|
||||
admin.site.register(Plan, PlanAdmin)
|
||||
admin.site.register(ContractedPlan, ContractedPlanAdmin)
|
||||
|
||||
|
|
|
@ -6,8 +6,10 @@ from django.utils.translation import ugettext_lazy as _
|
|||
|
||||
from orchestra.core.validators import validate_name
|
||||
from orchestra.models import queryset
|
||||
from orchestra.utils.functional import cached
|
||||
from orchestra.utils.python import import_class
|
||||
|
||||
from . import rating
|
||||
from . import settings
|
||||
|
||||
|
||||
class Plan(models.Model):
|
||||
|
@ -66,15 +68,6 @@ class RateQuerySet(models.QuerySet):
|
|||
|
||||
|
||||
class Rate(models.Model):
|
||||
STEP_PRICE = 'STEP_PRICE'
|
||||
MATCH_PRICE = 'MATCH_PRICE'
|
||||
BEST_PRICE = 'BEST_PRICE'
|
||||
RATE_METHODS = {
|
||||
STEP_PRICE: rating.step_price,
|
||||
MATCH_PRICE: rating.match_price,
|
||||
BEST_PRICE: rating.best_price,
|
||||
}
|
||||
|
||||
service = models.ForeignKey('services.Service', verbose_name=_("service"),
|
||||
related_name='rates')
|
||||
plan = models.ForeignKey(Plan, verbose_name=_("plan"), related_name='rates')
|
||||
|
@ -91,12 +84,18 @@ class Rate(models.Model):
|
|||
return "{}-{}".format(str(self.price), self.quantity)
|
||||
|
||||
@classmethod
|
||||
@cached
|
||||
def get_methods(cls):
|
||||
return cls.RATE_METHODS
|
||||
return dict((method, import_class(method)) for method in settings.PLANS_RATE_METHODS)
|
||||
|
||||
@classmethod
|
||||
@cached
|
||||
def get_choices(cls):
|
||||
choices = []
|
||||
for name, method in cls.RATE_METHODS.items():
|
||||
for name, method in cls.get_methods().items():
|
||||
choices.append((name, method.verbose_name))
|
||||
return choices
|
||||
|
||||
@classmethod
|
||||
def get_default(cls):
|
||||
return settings.PLANS_DEFAULT_RATE_METHOD
|
||||
|
|
15
orchestra/contrib/plans/settings.py
Normal file
15
orchestra/contrib/plans/settings.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
from orchestra.contrib.settings import Setting
|
||||
|
||||
|
||||
PLANS_RATE_METHODS = Setting('PLANS_RATE_METHODS',
|
||||
(
|
||||
'orchestra.contrib.plans.rating.step_price',
|
||||
'orchestra.contrib.plans.rating.match_price',
|
||||
'orchestra.contrib.plans.rating.best_price',
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
PLANS_DEFAULT_RATE_METHOD = Setting('PLANS_DEFAULT_RATE_METHOD',
|
||||
'orchestra.contrib.plans.rating.step_price',
|
||||
)
|
|
@ -0,0 +1,24 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('services', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='service',
|
||||
name='rate_algorithm',
|
||||
field=models.CharField(choices=[('orchestra.contrib.plans.rating.best_price', 'Best price'), ('orchestra.contrib.plans.rating.step_price', 'Step price'), ('orchestra.contrib.plans.rating.match_price', 'Match price')], help_text='Algorithm used to interprete the rating table.<br> Best price: Produces the best possible price given all active rating lines.<br> Step price: All rates with a quantity lower than the metric are applied. Nominal price will be used when initial block is missing.<br> Match price: Only <b>the rate</b> with a) inmediate inferior metric and b) lower price is applied. Nominal price will be used when initial block is missing.', max_length=64, verbose_name='rate algorithm', default='orchestra.contrib.plans.rating.step_price'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='service',
|
||||
name='tax',
|
||||
field=models.PositiveIntegerField(choices=[(0, 'Duty free'), (21, '21%')], verbose_name='tax', default=21),
|
||||
),
|
||||
]
|
|
@ -127,11 +127,13 @@ class Service(models.Model):
|
|||
(ANUAL, _("Anual data")),
|
||||
),
|
||||
default=BILLING_PERIOD)
|
||||
rate_algorithm = models.CharField(_("rate algorithm"), max_length=16,
|
||||
rate_algorithm = models.CharField(_("rate algorithm"), max_length=64,
|
||||
choices=rate_class.get_choices(),
|
||||
default=rate_class.get_default(),
|
||||
help_text=string_concat(_("Algorithm used to interprete the rating table."), *[
|
||||
string_concat('<br> ', method.verbose_name, ': ', method.help_text)
|
||||
for name, method in rate_class.get_methods().items()
|
||||
]), choices=rate_class.get_choices(), default=rate_class.get_choices()[0][0])
|
||||
]))
|
||||
on_cancel = models.CharField(_("on cancel"), max_length=16,
|
||||
help_text=_("Defines the cancellation behaviour of this service."),
|
||||
choices=(
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import logging
|
||||
import traceback
|
||||
from functools import partial, wraps, update_wrapper
|
||||
from multiprocessing import Process
|
||||
|
@ -15,6 +16,9 @@ from orchestra.utils.python import AttrDict, OrderedSet
|
|||
from .utils import get_name, get_id
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def keep_state(fn):
|
||||
""" logs task on djcelery's TaskState model """
|
||||
@wraps(fn)
|
||||
|
@ -30,14 +34,14 @@ def keep_state(fn):
|
|||
try:
|
||||
result = fn(*args, **kwargs)
|
||||
except:
|
||||
trace = traceback.format_exc()
|
||||
subject = 'EXCEPTION executing task %s(args=%s, kwargs=%s)' % (_name, str(args), str(kwargs))
|
||||
logger.error(subject)
|
||||
logger.error(trace)
|
||||
state.state = states.FAILURE
|
||||
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)
|
||||
raise
|
||||
else:
|
||||
|
|
|
@ -6,6 +6,7 @@ import re
|
|||
import select
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
from django.core.management.base import CommandError
|
||||
|
||||
|
@ -165,6 +166,10 @@ def touch(fname, mode=0o666, dir_fd=None, **kwargs):
|
|||
dir_fd=None if os.supports_fd else dir_fd, **kwargs)
|
||||
|
||||
|
||||
class OperationLocked(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class LockFile(object):
|
||||
""" File-based lock mechanism used for preventing concurrency problems """
|
||||
def __init__(self, lockfile, expire=5*60, unlocked=False):
|
||||
|
@ -188,8 +193,8 @@ class LockFile(object):
|
|||
def __enter__(self):
|
||||
if not self.unlocked:
|
||||
if not self.acquire():
|
||||
raise OperationLocked('%s lock file exists and its mtime is less '
|
||||
'than %s seconds' % (self.lockfile, self.expire))
|
||||
raise OperationLocked("%s lock file exists and its mtime is less than %s seconds" %
|
||||
(self.lockfile, self.expire))
|
||||
return True
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
|
|
Loading…
Reference in a new issue