diff --git a/TODO.md b/TODO.md index 38a64a31..653251de 100644 --- a/TODO.md +++ b/TODO.md @@ -13,11 +13,6 @@ * backend logs with hal logo -# LAST version of this shit http://wkhtmltopdf.org/downloads.h otml -#apt-get install xfonts-75dpi -#wget http://download.gna.org/wkhtmltopdf/0.12/0.12.2.1/wkhtmltox-0.12.2.1_linux-jessie-amd64.deb -#dpkg -i wkhtmltox-0.12.2.1_linux-jessie-amd64.deb - * help_text on readonly_fields specialy Bill.state. (eg. A bill is in OPEN state when bla bla ) * create log file at /var/log/orchestra.log and rotate @@ -175,9 +170,6 @@ require_once(‘/etc/moodles/’.$moodle_host.‘config.php’);``` moodle/drupl * Autocomplete admin fields like .phplist... with js * allow empty metric pack for default rates? changes on rating algo -# don't produce lines with cost == 0 or quantity 0 ? maybe minimal quantity for billing? like 0.1 ? or minimal price? per line or per bill? - -# lines too long on invoice, double lines or cut * payment methods icons * use server.name | server.address on python backends, like gitlab instead of settings? @@ -373,23 +365,10 @@ method( arg, arg, arg) -# dovecot sieve only allolws one fucking active script. refactor mailbox shit to replace active script symlink by orchestra. Create a generic wrapper that includes al filters (rc, imp and orchestra) -http://wiki2.dovecot.org/Pigeonhole/Sieve/Examples - - - -# orders ignorign default filter is not very effective, because of selecting all orders for billing will select ignored too - - -# mail system users group? which one is more convinient? if main group does not exists, backend will fail! Bash/Python/PHPBackend - -# bill action view on a separate process. check memory consumption without debug (236m) - # services.handler as generator in order to save memory? not swell like a balloon -# mailboxes group username instead of mainuser import uwsgi from uwsgidecorators import timer @@ -406,34 +385,23 @@ uwsgi --reload /tmp/project-master.pid # or if uwsgi was started with touch-reload=/tmp/somefile touch /tmp/somefile - # Change zone ttl # batch zone edditing -# inherit registers from parent? # datetime metric storage granularity: otherwise innacurate detection of billed metric on order.billed_on # Serializers.validation migration to DRF3: grep -r 'attrs, source' *|grep -v '~' serailzer self.instance on create. -# set_password serializer: "just-the-password" not {"password": "password"} - -# use namedtuples? - -# Negative transactionsx - - * check certificate: websites directive ssl + domains search on miscellaneous - -# Merge websites locations # ValueError: Unable to configure handler 'file': [Errno 13] Permission denied: '/home/orchestra/panel/orchestra.log' # billing invoice link on related invoices not overflow nginx GET vars * backendLog store method and language... and use it for display_script with correct lexer -# process monitor data to represent state, or maybe create new resource datas when period expires? +# Compute Resource Data history from Monitor Data. @register.filter def comma(value): @@ -444,12 +412,8 @@ def comma(value): return value - # payment/bill report allow to change template using a setting variable # Payment transaction stats, graps over time -# order stats: service, cost, top profit, etc -# TODO remove bill.total - reporter.stories_filed = F('stories_filed') + 1 reporter.save() diff --git a/orchestra/admin/options.py b/orchestra/admin/options.py index c3a132d4..1c1aebfb 100644 --- a/orchestra/admin/options.py +++ b/orchestra/admin/options.py @@ -1,3 +1,5 @@ +from urllib import parse + from django import forms from django.conf.urls import url from django.contrib import admin, messages @@ -34,30 +36,27 @@ class ChangeListDefaultFilter(object): default_changelist_filters = () def changelist_view(self, request, extra_context=None): - defaults = [] - for key, value in self.default_changelist_filters: - set_url_query(request, key, value) - defaults.append(key) - # hack response cl context in order to hook default filter awaearness - # into search_form.html template - response = super(ChangeListDefaultFilter, self).changelist_view(request, extra_context) - if hasattr(response, 'context_data') and 'cl' in response.context_data: - response.context_data['cl'].default_changelist_filters = defaults - return response # defaults = [] -# querystring = request.META['QUERY_STRING'] -# redirect = False -# for field, value in self.default_changelist_filters: -# if field not in queryseting: -# redirect = True -# querystring[field] = value -# if redirect: -# raise -# if not request.META.get('HTTP_REFERER', '').startswith(request.build_absolute_uri()): -# querystring = '&'.join('%s=%s' % filed, value in querystring.items()) -# from django.http import HttpResponseRedirect -# return HttpResponseRedirect(request.path + '?%s' % querystring) -# return super(ChangeListDefaultFilter, self).changelist_view(request, extra_context=extra_context) +# for key, value in self.default_changelist_filters: +# set_url_query(request, key, value) +# defaults.append(key) +# # hack response cl context in order to hook default filter awaearness +# # into search_form.html template +# response = super(ChangeListDefaultFilter, self).changelist_view(request, extra_context) +# if hasattr(response, 'context_data') and 'cl' in response.context_data: +# response.context_data['cl'].default_changelist_filters = defaults +# return response + querystring = request.META['QUERY_STRING'] + querydict = parse.parse_qs(querystring) + redirect = False + for field, value in self.default_changelist_filters: + if field not in querydict: + redirect = True + querydict[field] = value + if redirect: + querystring = parse.urlencode(querydict, doseq=True) + return HttpResponseRedirect(request.path + '?%s' % querystring) + return super(ChangeListDefaultFilter, self).changelist_view(request, extra_context=extra_context) class AtLeastOneRequiredInlineFormSet(BaseInlineFormSet): diff --git a/orchestra/contrib/accounts/forms.py b/orchestra/contrib/accounts/forms.py index df9a6803..a351f0d3 100644 --- a/orchestra/contrib/accounts/forms.py +++ b/orchestra/contrib/accounts/forms.py @@ -22,8 +22,8 @@ def create_account_creation_form(): model = apps.get_model(model) field_name = 'create_%s' % model._meta.model_name label = _("Create %s") % model._meta.verbose_name - fields[field_name] = forms.BooleanField(initial=True, required=False, label=label, - help_text=help_text) + fields[field_name] = forms.BooleanField( + initial=True, required=False, label=label, help_text=help_text) def clean(self): """ unique usernames between accounts and system users """ @@ -55,7 +55,8 @@ def create_account_creation_form(): raise ValidationError(errors) def save_model(self, account): - account.save(active_systemuser=self.cleaned_data['enable_systemuser']) + enable_systemuser=self.cleaned_data['enable_systemuser'] + account.save(active_systemuser=enable_systemuser) def save_related(self, account): for model, key, related_kwargs, __ in settings.ACCOUNTS_CREATE_RELATED: diff --git a/orchestra/contrib/accounts/models.py b/orchestra/contrib/accounts/models.py index bf6a8a32..7b44de32 100644 --- a/orchestra/contrib/accounts/models.py +++ b/orchestra/contrib/accounts/models.py @@ -65,8 +65,9 @@ class Account(auth.AbstractBaseUser): was_active = Account.objects.filter(pk=self.pk).values_list('is_active', flat=True)[0] super(Account, self).save(*args, **kwargs) if created: - self.main_systemuser = self.systemusers.create(account=self, username=self.username, - password=self.password, is_active=active_systemuser) + self.main_systemuser = self.systemusers.create( + account=self, username=self.username, password=self.password, + is_active=active_systemuser) self.save(update_fields=('main_systemuser',)) elif was_active != self.is_active: self.notify_related() diff --git a/orchestra/contrib/bills/filters.py b/orchestra/contrib/bills/filters.py index cd706656..cb1b38e3 100644 --- a/orchestra/contrib/bills/filters.py +++ b/orchestra/contrib/bills/filters.py @@ -55,11 +55,11 @@ class TotalListFilter(SimpleListFilter): def queryset(self, request, queryset): if self.value() == 'gt': - return queryset.filter(computed_total__gt=0) + return queryset.filter(approx_total__gt=0) elif self.value() == 'eq': - return queryset.filter(computed_total=0) + return queryset.filter(approx_total=0) elif self.value() == 'lt': - return queryset.filter(computed_total__lt=0) + return queryset.filter(approx_total__lt=0) return queryset @@ -94,16 +94,17 @@ class PaymentStateListFilter(SimpleListFilter): ) def queryset(self, request, queryset): + # FIXME use queryset.computed_total instead of approx_total, bills.admin.BillAdmin.get_queryset Transaction = queryset.model.transactions.related.related_model if self.value() == 'OPEN': return queryset.filter(Q(is_open=True)|Q(type=queryset.model.PROFORMA)) elif self.value() == 'PAID': - zeros = queryset.filter(computed_total=0, computed_total__isnull=True) + zeros = queryset.filter(approx_total=0, approx_total__isnull=True) zeros = zeros.values_list('id', flat=True) ammounts = Transaction.objects.exclude(bill_id__in=zeros).secured().group_by('bill_id') paid = [] - relevant = queryset.exclude(computed_total=0, computed_total__isnull=True, is_open=True) - for bill_id, total in relevant.values_list('id', 'computed_total'): + relevant = queryset.exclude(approx_total=0, approx_total__isnull=True, is_open=True) + for bill_id, total in relevant.values_list('id', 'approx_total'): try: ammount = sum([t.ammount for t in ammounts[bill_id]]) except KeyError: @@ -112,8 +113,8 @@ class PaymentStateListFilter(SimpleListFilter): if abs(total) <= abs(ammount): paid.append(bill_id) return queryset.filter( - Q(computed_total=0) | - Q(computed_total__isnull=True) | + Q(approx_total=0) | + Q(approx_total__isnull=True) | Q(id__in=paid) ).exclude(is_open=True) elif self.value() == 'PENDING': @@ -122,7 +123,7 @@ class PaymentStateListFilter(SimpleListFilter): non_rejected = non_rejected.values_list('id', flat=True).distinct() return queryset.filter(pk__in=non_rejected) elif self.value() == 'BAD_DEBT': - closed = queryset.filter(is_open=False).exclude(computed_total=0) + closed = queryset.filter(is_open=False).exclude(approx_total=0) return closed.filter( Q(transactions__state=Transaction.REJECTED) | Q(transactions__isnull=True) diff --git a/orchestra/contrib/domains/admin.py b/orchestra/contrib/domains/admin.py index f2ce1a53..f3ff71fc 100644 --- a/orchestra/contrib/domains/admin.py +++ b/orchestra/contrib/domains/admin.py @@ -60,7 +60,7 @@ class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin): fields = ('name', 'account_link') inlines = [RecordInline, DomainInline] list_filter = [TopDomainListFilter] - change_readonly_fields = ('name',) + change_readonly_fields = ('name', 'serial') search_fields = ('name', 'account__username') add_form = BatchDomainCreationAdminForm change_view_actions = [view_zone] @@ -93,6 +93,18 @@ class DomainAdmin(AccountAdminMixin, ExtendedModelAdmin): display_websites.short_description = _("Websites") display_websites.allow_tags = True + def get_fieldsets(self, request, obj=None): + """ Add SOA fields when domain is top """ + fieldsets = super(DomainAdmin, self).get_fieldsets(request, obj) + if obj and obj.is_top: + fieldsets += ( + (_("SOA"), { + 'classes': ('collapse',), + 'fields': ('serial', 'refresh', 'retry', 'expire', 'min_ttl'), + }), + ) + return fieldsets + def get_queryset(self, request): """ Order by structured name and imporve performance """ qs = super(DomainAdmin, self).get_queryset(request) diff --git a/orchestra/contrib/domains/migrations/0002_auto_20150715_1017.py b/orchestra/contrib/domains/migrations/0002_auto_20150715_1017.py new file mode 100644 index 00000000..7b6d9ad1 --- /dev/null +++ b/orchestra/contrib/domains/migrations/0002_auto_20150715_1017.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('domains', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='domain', + name='expire', + field=models.IntegerField(null=True, blank=True, help_text='The upper limit in seconds before a zone is considered no longer authoritative (4w by default).', verbose_name='expire'), + ), + migrations.AddField( + model_name='domain', + name='min_ttl', + field=models.IntegerField(null=True, blank=True, help_text='The negative result TTL (for example, how long a resolver should consider a negative result for a subdomain to be valid before retrying) (1h by default).', verbose_name='refresh'), + ), + migrations.AddField( + model_name='domain', + name='refresh', + field=models.IntegerField(null=True, blank=True, help_text='The number of seconds before the zone should be refreshed (1d by default).', verbose_name='refresh'), + ), + migrations.AddField( + model_name='domain', + name='retry', + field=models.IntegerField(null=True, blank=True, help_text='The number of seconds before a failed refresh should be retried (2h by default).', verbose_name='retry'), + ), + migrations.AlterField( + model_name='record', + name='value', + field=models.CharField(max_length=256, help_text='MX, NS and CNAME records sould end with a dot.', verbose_name='value'), + ), + ] diff --git a/orchestra/contrib/domains/models.py b/orchestra/contrib/domains/models.py index 3e0bebd3..050d6d53 100644 --- a/orchestra/contrib/domains/models.py +++ b/orchestra/contrib/domains/models.py @@ -19,8 +19,25 @@ class Domain(models.Model): related_name='domains', help_text=_("Automatically selected for subdomains.")) top = models.ForeignKey('domains.Domain', null=True, related_name='subdomain_set', editable=False) - serial = models.IntegerField(_("serial"), default=utils.generate_zone_serial, - help_text=_("Serial number")) + serial = models.IntegerField(_("serial"), default=utils.generate_zone_serial, editable=False, + help_text=_("A timestamp that changes whenever you update your domain.")) + refresh = models.IntegerField(_("refresh"), null=True, blank=True, + validators=[validators.validate_zone_interval], + help_text=_("The number of seconds before the zone should be refreshed " + "(%s by default).") % settings.DOMAINS_DEFAULT_REFRESH) + retry = models.IntegerField(_("retry"), null=True, blank=True, + validators=[validators.validate_zone_interval], + help_text=_("The number of seconds before a failed refresh should be retried " + "(%s by default).") % settings.DOMAINS_DEFAULT_RETRY) + expire = models.IntegerField(_("expire"), null=True, blank=True, + validators=[validators.validate_zone_interval], + help_text=_("The upper limit in seconds before a zone is considered no longer " + "authoritative (%s by default).") % settings.DOMAINS_DEFAULT_EXPIRE) + min_ttl = models.IntegerField(_("refresh"), null=True, blank=True, + validators=[validators.validate_zone_interval], + help_text=_("The negative result TTL (for example, how long a resolver should " + "consider a negative result for a subdomain to be valid before retrying) " + "(%s by default).") % settings.DOMAINS_DEFAULT_MIN_TTL) def __str__(self): return self.name @@ -153,10 +170,10 @@ class Domain(models.Model): "%s." % settings.DOMAINS_DEFAULT_NAME_SERVER, utils.format_hostmaster(settings.DOMAINS_DEFAULT_HOSTMASTER), str(self.serial), - settings.DOMAINS_DEFAULT_REFRESH, - settings.DOMAINS_DEFAULT_RETRY, - settings.DOMAINS_DEFAULT_EXPIRATION, - settings.DOMAINS_DEFAULT_MIN_CACHING_TIME + settings.DOMAINS_DEFAULT_REFRESH if self.refresh is None else self.refresh, + settings.DOMAINS_DEFAULT_RETRY if self.retry is None else self.retry, + settings.DOMAINS_DEFAULT_EXPIRE if self.expire is None else self.expire, + settings.DOMAINS_DEFAULT_MIN_TTL if self.min_ttl is None else self.min_ttl, ] records.insert(0, AttrDict( type=Record.SOA, diff --git a/orchestra/contrib/domains/settings.py b/orchestra/contrib/domains/settings.py index e5808172..eab0e8fe 100644 --- a/orchestra/contrib/domains/settings.py +++ b/orchestra/contrib/domains/settings.py @@ -36,13 +36,13 @@ DOMAINS_DEFAULT_RETRY = Setting('DOMAINS_DEFAULT_RETRY', ) -DOMAINS_DEFAULT_EXPIRATION = Setting('DOMAINS_DEFAULT_EXPIRATION', +DOMAINS_DEFAULT_EXPIRE = Setting('DOMAINS_DEFAULT_EXPIRE', '4w', validators=[validate_zone_interval], ) -DOMAINS_DEFAULT_MIN_CACHING_TIME = Setting('DOMAINS_DEFAULT_MIN_CACHING_TIME', +DOMAINS_DEFAULT_MIN_TTL = Setting('DOMAINS_DEFAULT_MIN_TTL', '1h', validators=[validate_zone_interval], )