diff --git a/TODO.md b/TODO.md
index 7a3ab562..ea35ff7d 100644
--- a/TODO.md
+++ b/TODO.md
@@ -440,8 +440,24 @@ mkhomedir_helper or create ssh homes with bash.rc and such
# Multiple domains wordpress
-# TODO: separate ports for fpm version
-
# Reversion
-# implement re-enable account
-# Disable/enable saas and VPS
+# Disable/enable SaaS and VPS
+
+# AGO
+
+# Don't show lines with size 0?
+# pending orders with recharge do not show up
+# Traffic of disabled accounts doesn't get disabled
+
+# is_active list filter account dissabled filtering support
+
+# URL encode "Order description" on clone
+# Service CLONE METRIC doesn't work
+
+
+# Show warning when saving order and metricstorage date is inconistent with registered date!
+
+# Warn user if changes are not saved
+
+# exclude from change list action, support for multiple exclusion
+# support for better edditing bill lines and sublines
diff --git a/orchestra/contrib/accounts/models.py b/orchestra/contrib/accounts/models.py
index 3fe5327a..54e163a8 100644
--- a/orchestra/contrib/accounts/models.py
+++ b/orchestra/contrib/accounts/models.py
@@ -2,12 +2,13 @@ from django.contrib.auth import models as auth
from django.conf import settings as djsettings
from django.core import validators
from django.db import models
+from django.db.models import signals
from django.apps import apps
from django.utils import timezone, translation
from django.utils.translation import ugettext_lazy as _
-from orchestra.contrib.orchestration.middlewares import OperationsMiddleware
-from orchestra.contrib.orchestration import Operation
+#from orchestra.contrib.orchestration.middlewares import OperationsMiddleware
+#from orchestra.contrib.orchestration import Operation
from orchestra.core import services
from orchestra.utils.mail import send_email_template
@@ -98,7 +99,9 @@ class Account(auth.AbstractBaseUser):
def notify_related(self):
""" Trigger save() on related objects that depend on this account """
for obj in self.get_services_to_disable():
- OperationsMiddleware.collect(Operation.SAVE, instance=obj, update_fields=())
+ signals.pre_save.send(sender=type(obj), instance=obj)
+ signals.post_save.send(sender=type(obj), instance=obj)
+# OperationsMiddleware.collect(Operation.SAVE, instance=obj, update_fields=())
def send_email(self, template, context, email_from=None, contacts=[], attachments=[], html=None):
contacts = self.contacts.filter(email_usages=contacts)
diff --git a/orchestra/contrib/bills/actions.py b/orchestra/contrib/bills/actions.py
index 7692334a..e58baa72 100644
--- a/orchestra/contrib/bills/actions.py
+++ b/orchestra/contrib/bills/actions.py
@@ -8,7 +8,7 @@ from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
from django.db import transaction
from django.forms.models import modelformset_factory
-from django.http import HttpResponse
+from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render, redirect
from django.utils import translation, timezone
from django.utils.safestring import mark_safe
@@ -368,3 +368,8 @@ def service_report(modeladmin, request, queryset):
'totals': totals,
}
return render(request, 'admin/bills/billline/report.html', context)
+
+
+def get_ids(modeladmin, request, queryset):
+ ids = ','.join(map(str, queryset.values_list('id', flat=True)))
+ return HttpResponseRedirect('?id__in=%s' % ids)
diff --git a/orchestra/contrib/bills/admin.py b/orchestra/contrib/bills/admin.py
index 3f6f2217..a16953e4 100644
--- a/orchestra/contrib/bills/admin.py
+++ b/orchestra/contrib/bills/admin.py
@@ -12,7 +12,7 @@ from django.utils.translation import ugettext_lazy as _
from django.shortcuts import redirect
from orchestra.admin import ExtendedModelAdmin
-from orchestra.admin.utils import admin_date, insertattr, admin_link
+from orchestra.admin.utils import admin_date, insertattr, admin_link, change_url
from orchestra.contrib.accounts.actions import list_accounts
from orchestra.contrib.accounts.admin import AccountAdminMixin, AccountAdmin
from orchestra.forms.widgets import paddingCheckboxSelectMultiple
@@ -21,7 +21,7 @@ from . import settings, actions
from .filters import (BillTypeListFilter, HasBillContactListFilter, TotalListFilter,
PaymentStateListFilter, AmendedListFilter)
from .models import (Bill, Invoice, AmendmentInvoice, Fee, AmendmentFee, ProForma, BillLine,
- BillContact)
+ BillSubline, BillContact)
PAYMENT_STATE_COLORS = {
@@ -36,6 +36,27 @@ PAYMENT_STATE_COLORS = {
}
+class BillSublineInline(admin.TabularInline):
+ model = BillSubline
+ fields = ('description', 'total', 'type')
+
+ def get_readonly_fields(self, request, obj=None):
+ fields = super().get_readonly_fields(request, obj)
+ if obj and not obj.bill.is_open:
+ return self.get_fields(request)
+ return fields
+
+ def get_max_num(self, request, obj=None):
+ if obj and not obj.bill.is_open:
+ return 0
+ return super().get_max_num(request, obj)
+
+ def has_delete_permission(self, request, obj=None):
+ if obj and not obj.bill.is_open:
+ return False
+ return super().has_delete_permission(request, obj)
+
+
class BillLineInline(admin.TabularInline):
model = BillLine
fields = (
@@ -50,11 +71,12 @@ class BillLineInline(admin.TabularInline):
if line.pk:
total = line.compute_total()
sublines = line.sublines.all()
+ url = change_url(line)
if sublines:
content = '\n'.join(['%s: %s' % (sub.description, sub.total) for sub in sublines])
img = static('admin/img/icon_alert.gif')
- return '%s ' % (content, total, img)
- return total
+ return '%s ' % (url, content, total, img)
+ return '%s' % (url, total)
display_total.short_description = _("Total")
display_total.allow_tags = True
@@ -118,12 +140,30 @@ class BillLineAdmin(admin.ModelAdmin):
actions = (
actions.undo_billing, actions.move_lines, actions.copy_lines, actions.service_report
)
+ fieldsets = (
+ (None, {
+ 'fields': ('bill_link', 'description', 'tax', 'start_on', 'end_on', 'amended_line_link')
+ }),
+ (_("Totals"), {
+ 'fields': ('rate', ('quantity', 'verbose_quantity'), 'subtotal', 'display_sublinetotal',
+ 'display_total'),
+ }),
+ (_("Order"), {
+ 'fields': ('order_link', 'order_billed_on', 'order_billed_until',)
+ }),
+ )
+ readonly_fields = (
+ 'bill_link', 'order_link', 'amended_line_link', 'display_sublinetotal', 'display_total'
+ )
list_filter = ('tax', 'bill__is_open', 'order__service')
list_select_related = ('bill', 'bill__account')
search_fields = ('description', 'bill__number')
+ inlines = (BillSublineInline,)
account_link = admin_link('bill__account')
bill_link = admin_link('bill')
+ order_link = admin_link('order')
+ amended_line_link = admin_link('amended_line')
def display_is_open(self, instance):
return instance.bill.is_open
@@ -140,6 +180,15 @@ class BillLineAdmin(admin.ModelAdmin):
display_total.short_description = _("Total")
display_total.admin_order_field = 'computed_total'
+ def get_readonly_fields(self, request, obj=None):
+ fields = super().get_readonly_fields(request, obj)
+ if obj and not obj.bill.is_open:
+ return list(fields) + [
+ 'description', 'tax', 'start_on', 'end_on', 'rate', 'quantity', 'verbose_quantity',
+ 'subtotal', 'order_billed_on', 'order_billed_until'
+ ]
+ return fields
+
def get_queryset(self, request):
qs = super().get_queryset(request)
qs = qs.annotate(
@@ -221,7 +270,7 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
actions = [
actions.manage_lines, actions.download_bills, actions.close_bills, actions.send_bills,
actions.amend_bills, actions.bill_report, actions.service_report,
- actions.close_send_download_bills, list_accounts,
+ actions.close_send_download_bills, list_accounts, actions.get_ids,
]
change_readonly_fields = (
'account_link', 'type', 'is_open', 'amend_of_link', 'amend_links'
@@ -326,7 +375,7 @@ class BillAdmin(AccountAdminMixin, ExtendedModelAdmin):
else:
fieldsets[0][1]['fields'][2] = 'amend_links'
if obj.is_open:
- fieldsets = (fieldsets[0],)
+ fieldsets = fieldsets[0:-1]
return fieldsets
def get_change_view_actions(self, obj=None):
diff --git a/orchestra/contrib/bills/models.py b/orchestra/contrib/bills/models.py
index d0bc3d6d..1296e977 100644
--- a/orchestra/contrib/bills/models.py
+++ b/orchestra/contrib/bills/models.py
@@ -11,6 +11,7 @@ from django.utils.encoding import force_text
from django.utils.functional import cached_property
from django.utils.translation import ugettext_lazy as _
+from orchestra.admin.utils import change_url
from orchestra.contrib.accounts.models import Account
from orchestra.contrib.contacts.models import Contact
from orchestra.core import validators
@@ -398,7 +399,7 @@ class BillLine(models.Model):
rate = models.DecimalField(_("rate"), blank=True, null=True, max_digits=12, decimal_places=2)
quantity = models.DecimalField(_("quantity"), blank=True, null=True, max_digits=12,
decimal_places=2)
- verbose_quantity = models.CharField(_("Verbose quantity"), max_length=16)
+ verbose_quantity = models.CharField(_("Verbose quantity"), max_length=16, blank=True)
subtotal = models.DecimalField(_("subtotal"), max_digits=12, decimal_places=2)
tax = models.DecimalField(_("tax"), max_digits=4, decimal_places=2)
start_on = models.DateField(_("start"))
@@ -422,6 +423,13 @@ class BillLine(models.Model):
def get_verbose_quantity(self):
return self.verbose_quantity or self.quantity
+ def clean():
+ if not self.verbose_quantity:
+ quantity = str(self.quantity)
+ # Strip trailing zeros
+ if quantity.endswith('0'):
+ self.verbose_quantity = quantity.strip('0').strip('.')
+
def get_verbose_period(self):
from django.template.defaultfilters import date
date_format = "N 'y"
@@ -448,6 +456,9 @@ class BillLine(models.Model):
else:
total += self.sublines.aggregate(sub_total=Sum('total'))['sub_total'] or 0
return round(total, 2)
+
+ def get_absolute_url(self):
+ return change_url(self)
class BillSubline(models.Model):
diff --git a/orchestra/contrib/databases/models.py b/orchestra/contrib/databases/models.py
index 2270a1f8..2ea924fa 100644
--- a/orchestra/contrib/databases/models.py
+++ b/orchestra/contrib/databases/models.py
@@ -38,6 +38,10 @@ class Database(models.Model):
if user is not None:
return user.databaseuser
return None
+
+ @property
+ def active(self):
+ return self.account.is_active
Database.users.through._meta.unique_together = (
diff --git a/orchestra/contrib/mailer/engine.py b/orchestra/contrib/mailer/engine.py
index fad98a46..27a649ed 100644
--- a/orchestra/contrib/mailer/engine.py
+++ b/orchestra/contrib/mailer/engine.py
@@ -27,7 +27,7 @@ def send_message(message, connection=None, bulk=settings.MAILER_BULK_MESSAGES):
connection.open()
except Exception as err:
message.defer()
- message.log(error)
+ message.log(err)
return
error = None
try:
diff --git a/orchestra/contrib/orchestration/helpers.py b/orchestra/contrib/orchestration/helpers.py
index 430772f1..5300b73a 100644
--- a/orchestra/contrib/orchestration/helpers.py
+++ b/orchestra/contrib/orchestration/helpers.py
@@ -109,9 +109,11 @@ def message_user(request, logs):
async_ids = []
for log in logs:
total += 1
- if log.state != log.EXCEPTION:
- # EXCEPTION logs are not stored on the database
+ try:
+ # Some EXCEPTION logs are not stored on the database
ids.append(log.pk)
+ except AttributeError:
+ pass
if log.is_success:
successes += 1
elif not log.has_finished:
@@ -160,5 +162,5 @@ def message_user(request, logs):
)
messages.success(request, mark_safe(msg + '.'))
else:
- msg = async_msg.format(url=url, async_url=async_url, async=async)
+ msg = async_msg.format(url=url, async_url=async_url, async=async, name=log.backend)
messages.success(request, mark_safe(msg + '.'))
diff --git a/orchestra/contrib/orders/admin.py b/orchestra/contrib/orders/admin.py
index f2f3ac70..024cbc5f 100644
--- a/orchestra/contrib/orders/admin.py
+++ b/orchestra/contrib/orders/admin.py
@@ -136,7 +136,7 @@ class OrderAdmin(AccountAdminMixin, ExtendedModelAdmin):
make_link = admin_link()
for line in order.lines.select_related('bill').distinct('bill'):
bills.append(make_link(line.bill))
- return '
'.join(bills)
bills_links.short_description = _("Bills")
bills_links.allow_tags = True
@@ -200,6 +200,7 @@ class OrderAdmin(AccountAdminMixin, ExtendedModelAdmin):
class MetricStorageAdmin(admin.ModelAdmin):
list_display = ('order', 'value', 'created_on', 'updated_on')
list_filter = ('order__service',)
+ raw_id_fields = ('order',)
admin.site.register(Order, OrderAdmin)
diff --git a/orchestra/contrib/orders/billing.py b/orchestra/contrib/orders/billing.py
index f199bb44..650f8df5 100644
--- a/orchestra/contrib/orders/billing.py
+++ b/orchestra/contrib/orders/billing.py
@@ -77,20 +77,15 @@ class BillsBackend(object):
return description
def get_verbose_quantity(self, line):
-# service = line.order.service
-# if service.metric and service.billing_period != service.NEVER and service.pricing_period == service.NEVER:
metric = format(line.metric, '.2f').rstrip('0').rstrip('.')
- if metric.endswith('.00'):
- metric = metric.split('.')[0]
+ metric = metric.strip('0').strip('.')
size = format(line.size, '.2f').rstrip('0').rstrip('.')
- if size.endswith('.00'):
- size = metric.split('.')[0]
+ size = size.strip('0').strip('.')
if metric == '1':
return size
if size == '1':
return metric
return "%s×%s" % (metric, size)
-# return ''
def create_sublines(self, line, discounts):
for discount in discounts:
diff --git a/orchestra/contrib/orders/filters.py b/orchestra/contrib/orders/filters.py
index b11d237b..c0a54332 100644
--- a/orchestra/contrib/orders/filters.py
+++ b/orchestra/contrib/orders/filters.py
@@ -74,7 +74,7 @@ class BilledOrderListFilter(SimpleListFilter):
pending_qs = Q(
Q(pk__in=self.get_pending_metric_pks(ignore_qs)) |
Q(billed_until__isnull=True) | Q(~Q(service__billing_period=Service.NEVER) &
- Q(billed_until__lt=now))
+ Q(billed_until__lte=now))
)
if reverse:
return queryset.exclude(pending_qs)
diff --git a/orchestra/contrib/orders/models.py b/orchestra/contrib/orders/models.py
index d2c42613..e055194c 100644
--- a/orchestra/contrib/orders/models.py
+++ b/orchestra/contrib/orders/models.py
@@ -239,7 +239,7 @@ class Order(models.Model):
created = metric.created_on
if created > ini:
if prev is None:
- raise ValueError("Metric storage information is inconsistent.")
+ raise ValueError("Metric storage information for order %i is inconsistent." % self.id)
cini = prev.created_on
if not result:
cini = ini
@@ -297,6 +297,7 @@ class MetricStorage(models.Model):
order = models.ForeignKey(Order, verbose_name=_("order"), related_name='metrics')
value = models.DecimalField(_("value"), max_digits=16, decimal_places=2)
created_on = models.DateField(_("created"), auto_now_add=True)
+ created_on.editable = True
# TODO time field?
updated_on = models.DateTimeField(_("updated"))
diff --git a/orchestra/contrib/payments/admin.py b/orchestra/contrib/payments/admin.py
index 9fb2b722..1c770fc3 100644
--- a/orchestra/contrib/payments/admin.py
+++ b/orchestra/contrib/payments/admin.py
@@ -165,7 +165,7 @@ class TransactionProcessAdmin(ChangeViewActionsMixin, admin.ModelAdmin):
lines.append(','.join(ids))
ids = []
lines.append(','.join(ids))
- transactions = '
%s' % (url, transactions)
diff --git a/orchestra/contrib/saas/backends/owncloud.py b/orchestra/contrib/saas/backends/owncloud.py
index f4c6c6c2..aaf8c616 100644
--- a/orchestra/contrib/saas/backends/owncloud.py
+++ b/orchestra/contrib/saas/backends/owncloud.py
@@ -123,6 +123,7 @@ class OwnCloudController(OwnClouwAPIMixin, ServiceController):
self.api_delete('users/%s' % saas.name)
def save(self, saas):
+ # TODO disable user https://github.com/owncloud/core/issues/12601
self.append(self.update_or_create, saas)
def delete(self, saas):
diff --git a/orchestra/contrib/saas/backends/wordpressmu.py b/orchestra/contrib/saas/backends/wordpressmu.py
index 45c38b56..d6a3ef78 100644
--- a/orchestra/contrib/saas/backends/wordpressmu.py
+++ b/orchestra/contrib/saas/backends/wordpressmu.py
@@ -1,6 +1,7 @@
import re
import sys
import textwrap
+from functools import partial
from urllib.parse import urlparse
import requests
@@ -45,7 +46,8 @@ class WordpressMuController(ServiceController):
def validate_response(self, response):
if response.status_code != 200:
- errors = re.findall(r'
(.*)
', response.content.decode('utf8')) + content = response.content.decode('utf8') + errors = re.findall(r'\n\t(.*)
', content) raise RuntimeError(errors[0] if errors else 'Unknown %i error' % response.status_code) def get_id(self, session, saas): @@ -66,14 +68,7 @@ class WordpressMuController(ServiceController): ids = ids.groups() if len(ids) > 1 and not blog_id: raise ValueError("Multiple matches") - # Get wpnonce - try: - wpnonce = re.search(r'(.*)', content).groups()[0] - except TypeError: - # No search results, try some luck - wpnonce = content - wpnonce = re.search(r'_wpnonce=([^"]*)"', wpnonce).groups()[0] - return blog_id or int(ids[0]), wpnonce + return blog_id or int(ids[0]), content def create_blog(self, saas, server): if saas.data.get('blog_id'): @@ -84,7 +79,7 @@ class WordpressMuController(ServiceController): # Check if blog already exists try: - blog_id, wpnonce = self.get_id(session, saas) + blog_id, content = self.get_id(session, saas) except RuntimeError: url = self.get_main_url() url += '/wp-admin/network/site-new.php' @@ -111,42 +106,69 @@ class WordpressMuController(ServiceController): sys.stdout.write("Created blog ID: %s\n" % blog_id) saas.data['blog_id'] = int(blog_id) saas.save(update_fields=('data',)) + return True else: sys.stdout.write("Retrieved blog ID: %s\n" % blog_id) saas.data['blog_id'] = int(blog_id) saas.save(update_fields=('data',)) - def delete_blog(self, saas, server): + def do_action(self, action, session, id, content, saas): + url_regex = r"""]*)['"]>""" % action + action_url = re.search(url_regex, content).groups()[0].replace("&", '&') + sys.stdout.write("%s confirm URL: %s\n" % (action, action_url)) + + content = session.get(action_url, verify=self.VERIFY).content.decode('utf8') + wpnonce = re.compile('name="_wpnonce"\s+value="([^"]*)"') + try: + wpnonce = wpnonce.search(content).groups()[0] + except AttributeError: + raise RuntimeError(re.search(r'([^<]+)<', content).groups()[0]) + data = { + 'action': action, + 'id': id, + '_wpnonce': wpnonce, + '_wp_http_referer': '/wp-admin/network/sites.php', + } + action_url = self.get_main_url() + action_url += '/wp-admin/network/sites.php?action=%sblog' % action + sys.stdout.write("%s URL: %s\n" % (action, action_url)) + response = session.post(action_url, data=data, verify=self.VERIFY) + self.validate_response(response) + + def is_active(self, content): + return bool( + re.findall(r""" beyond: cend = comp.end + new_end = cend if only_beyond: cini = beyond elif only_beyond: @@ -334,8 +336,9 @@ class ServiceHandler(plugins.Plugin, metaclass=plugins.PluginMount): # Extend billing point a little bit to benefit from a substantial discount elif comp.end > beyond and (comp.end-comp.ini).days > 3*(comp.ini-beyond).days: cend = comp.end + new_end = cend dsize += self.get_price_size(comp.ini, cend) - return dsize, cend + return dsize, new_end def get_register_or_renew_events(self, porders, ini, end): counter = 0 @@ -394,8 +397,10 @@ class ServiceHandler(plugins.Plugin, metaclass=plugins.PluginMount): size = self.get_price_size(order.new_billed_until, new_end) price += price*size order.new_billed_until = new_end + ini = order.billed_until or order.registered_on + end = new_end or order.new_billed_until line = self.generate_line( - order, price, ini, new_end or end, discounts=discounts, computed=True) + order, price, ini, end, discounts=discounts, computed=True) lines.append(line) return lines @@ -462,7 +467,6 @@ class ServiceHandler(plugins.Plugin, metaclass=plugins.PluginMount): givers = sorted(givers, key=cmp_to_key(helpers.cmp_billed_until_or_registered_on)) orders = sorted(orders, key=cmp_to_key(helpers.cmp_billed_until_or_registered_on)) self.assign_compensations(givers, orders, **options) - rates = self.get_rates(account) has_billing_period = self.billing_period != self.NEVER has_pricing_period = self.get_pricing_period() != self.NEVER @@ -507,6 +511,8 @@ class ServiceHandler(plugins.Plugin, metaclass=plugins.PluginMount): for order in orders: prepay_discount = 0 bp = self.get_billing_point(order, bp=bp, **options) + recharged_until = datetime.date.min + if (self.billing_period != self.NEVER and self.get_pricing_period() == self.NEVER and self.payment_style == self.PREPAY and order.billed_on): @@ -543,11 +549,13 @@ class ServiceHandler(plugins.Plugin, metaclass=plugins.PluginMount): line = self.generate_line(order, price, cini, cend, metric=metric, computed=True, discounts=discounts) lines.append(line) + recharged_until = cend if order.billed_until and order.cancelled_on and order.cancelled_on >= order.billed_until: # Cancelled order continue if self.billing_period != self.NEVER: ini = order.billed_until or order.registered_on +# ini = max(order.billed_until or order.registered_on, recharged_until) # Periodic billing if bp <= ini: # Already billed @@ -556,6 +564,7 @@ class ServiceHandler(plugins.Plugin, metaclass=plugins.PluginMount): if self.get_pricing_period() == self.NEVER: # Changes (Mailbox disk-like) for cini, cend, metric in order.get_metric(ini, bp, changes=True): + cini = max(recharged_until, cini) price = self.get_price(account, metric) discounts = () # Since the current datamodel can't guarantee to retrieve the exact @@ -566,7 +575,7 @@ class ServiceHandler(plugins.Plugin, metaclass=plugins.PluginMount): # price -= discount # prepay_discount -= discount # discounts = ( -# (self._PREPAY', -discount), +# (self._PREPAY, -discount), # ) if metric > 0: line = self.generate_line(order, price, cini, cend, metric=metric, @@ -575,7 +584,8 @@ class ServiceHandler(plugins.Plugin, metaclass=plugins.PluginMount): elif self.get_pricing_period() == self.billing_period: # pricing_slots (Traffic-like) if self.payment_style == self.PREPAY: - raise NotImplementedError + raise NotImplementedError( + "Metric with prepay and pricing_period == billing_period") for cini, cend in self.get_pricing_slots(ini, bp): metric = order.get_metric(cini, cend) price = self.get_price(account, metric) @@ -601,7 +611,8 @@ class ServiceHandler(plugins.Plugin, metaclass=plugins.PluginMount): line = self.generate_line(order, price, cini, cend, metric=metric) lines.append(line) else: - raise NotImplementedError + raise NotImplementedError( + "Metric with postpay and pricing_period in (monthly, anual)") else: raise NotImplementedError else: diff --git a/orchestra/contrib/services/migrations/0004_auto_20160405_1133.py b/orchestra/contrib/services/migrations/0004_auto_20160405_1133.py new file mode 100644 index 00000000..5820f707 --- /dev/null +++ b/orchestra/contrib/services/migrations/0004_auto_20160405_1133.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('services', '0003_auto_20150917_0942'), + ] + + operations = [ + migrations.AddField( + model_name='service', + name='periodic_update', + field=models.BooleanField(default=False, verbose_name='periodic update', help_text='Whether a periodic update of this service orders should be performed or not. Needed for match definitions that depend on complex model interactions.'), + ), + migrations.AlterField( + model_name='service', + name='rate_algorithm', + field=models.CharField(choices=[('orchestra.contrib.plans.ratings.match_price', 'Match price'), ('orchestra.contrib.plans.ratings.best_price', 'Best price'), ('orchestra.contrib.plans.ratings.step_price', 'Step price')], verbose_name='rate algorithm', help_text='Algorithm used to interprete the rating table.