Added SOA settings for domains

This commit is contained in:
Marc Aymerich 2015-07-15 10:35:21 +00:00
parent 74f72ed8a1
commit caa087deb6
9 changed files with 117 additions and 83 deletions

38
TODO.md
View file

@ -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 <site_name>.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()

View file

@ -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):

View file

@ -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:

View file

@ -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()

View file

@ -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)

View file

@ -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)

View file

@ -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'),
),
]

View file

@ -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 "
"(<tt>%s</tt> 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 "
"(<tt>%s</tt> 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 (<tt>%s</tt> 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) "
"(<tt>%s</tt> 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,

View file

@ -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],
)