Added initial migrations
This commit is contained in:
parent
bac2b94d70
commit
e44b1ee6de
5
TODO.md
5
TODO.md
|
@ -176,6 +176,7 @@ require_once(‘/etc/moodles/’.$moodle_host.‘config.php’);``` moodle/drupl
|
||||||
# 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?
|
# 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?
|
||||||
|
|
||||||
# DOMINI REGISTRE MIGRATION SCRIPTS
|
# DOMINI REGISTRE MIGRATION SCRIPTS
|
||||||
|
# IMPORTANT delete domain xina: missing FROM-clause entry for table "t3" LINE 1: SELECT (CONCAT(T3.name, domains_domain.name)) AS "structured...
|
||||||
|
|
||||||
# lines too long on invoice, double lines or cut, and make margin wider
|
# lines too long on invoice, double lines or cut, and make margin wider
|
||||||
* PHP_TIMEOUT env variable in sync with fcgid idle timeout
|
* PHP_TIMEOUT env variable in sync with fcgid idle timeout
|
||||||
|
@ -276,3 +277,7 @@ https://code.djangoproject.com/ticket/24576
|
||||||
|
|
||||||
* move all tests to django-orchestra/tests
|
* move all tests to django-orchestra/tests
|
||||||
* *natural keys: those fields that uniquely identify a service, list.name, website.name, webapp.name+account, make sure rest api can not edit thos things
|
* *natural keys: those fields that uniquely identify a service, list.name, website.name, webapp.name+account, make sure rest api can not edit thos things
|
||||||
|
|
||||||
|
# migrations accounts, bill, orders, auth -> migrate the rest (contacts lambda error)
|
||||||
|
|
||||||
|
# MultiCHoiceField proper serialization
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
import django.core.validators
|
||||||
|
import django.utils.timezone
|
||||||
|
import django.contrib.auth.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('systemusers', '__first__'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Account',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
|
||||||
|
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||||
|
('last_login', models.DateTimeField(verbose_name='last login', blank=True, null=True)),
|
||||||
|
('username', models.CharField(validators=[django.core.validators.RegexValidator('^[\\w.-]+$', 'Enter a valid username.', 'invalid')], help_text='Required. 64 characters or fewer. Letters, digits and ./-/_ only.', unique=True, max_length=32, verbose_name='username')),
|
||||||
|
('short_name', models.CharField(blank=True, max_length=64, verbose_name='short name')),
|
||||||
|
('full_name', models.CharField(max_length=256, verbose_name='full name')),
|
||||||
|
('email', models.EmailField(help_text='Used for password recovery', verbose_name='email address', max_length=254)),
|
||||||
|
('type', models.CharField(choices=[('INDIVIDUAL', 'Individual'), ('ASSOCIATION', 'Association'), ('CUSTOMER', 'Customer'), ('STAFF', 'Staff'), ('FRIEND', 'Friend')], default='INDIVIDUAL', max_length=32, verbose_name='type')),
|
||||||
|
('language', models.CharField(choices=[('CA', 'Catalan'), ('ES', 'Spanish'), ('EN', 'English')], default='CA', max_length=2, verbose_name='language')),
|
||||||
|
('comments', models.TextField(blank=True, max_length=256, verbose_name='comments')),
|
||||||
|
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
|
||||||
|
('is_active', models.BooleanField(default=True, help_text='Designates whether this account should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
||||||
|
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
|
||||||
|
('main_systemuser', models.ForeignKey(related_name='accounts_main', null=True, editable=False, to='systemusers.SystemUser')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
managers=[
|
||||||
|
('objects', django.contrib.auth.models.UserManager()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -7,9 +7,7 @@ from orchestra.core import accounts, validators
|
||||||
from orchestra.models.fields import MultiSelectField
|
from orchestra.models.fields import MultiSelectField
|
||||||
|
|
||||||
from . import settings
|
from . import settings
|
||||||
|
from .validators import validate_phone
|
||||||
|
|
||||||
validate_phone = lambda p: validators.validate_phone(p, settings.CONTACTS_DEFAULT_COUNTRY)
|
|
||||||
|
|
||||||
|
|
||||||
class ContactQuerySet(models.QuerySet):
|
class ContactQuerySet(models.QuerySet):
|
||||||
|
|
|
@ -13,12 +13,12 @@ CONTACTS_DEFAULT_EMAIL_USAGES = getattr(settings, 'CONTACTS_DEFAULT_EMAIL_USAGES
|
||||||
|
|
||||||
|
|
||||||
CONTACTS_DEFAULT_CITY = getattr(settings, 'CONTACTS_DEFAULT_CITY',
|
CONTACTS_DEFAULT_CITY = getattr(settings, 'CONTACTS_DEFAULT_CITY',
|
||||||
'Barcelona'
|
'Barcelona'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
CONTACTS_COUNTRIES = getattr(settings, 'CONTACTS_COUNTRIES', (
|
CONTACTS_COUNTRIES = getattr(settings, 'CONTACTS_COUNTRIES', tuple(
|
||||||
(k,v) for k,v in data.COUNTRIES.items()
|
((k,v) for k,v in data.COUNTRIES.items())
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
from orchestra.core import validators
|
||||||
|
|
||||||
|
from . import settings
|
||||||
|
|
||||||
|
|
||||||
|
def validate_phone(phone):
|
||||||
|
validators.validate_phone(phone, settings.CONTACTS_DEFAULT_COUNTRY)
|
|
@ -0,0 +1,51 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
import orchestra.core.validators
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Database',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)),
|
||||||
|
('name', models.CharField(max_length=64, validators=[orchestra.core.validators.validate_name], verbose_name='name')),
|
||||||
|
('type', models.CharField(max_length=32, default='mysql', choices=[('mysql', 'MySQL')], verbose_name='type')),
|
||||||
|
('account', models.ForeignKey(verbose_name='Account', to=settings.AUTH_USER_MODEL, related_name='databases')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='DatabaseUser',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)),
|
||||||
|
('username', models.CharField(max_length=16, validators=[orchestra.core.validators.validate_name], verbose_name='username')),
|
||||||
|
('password', models.CharField(max_length=256, verbose_name='password')),
|
||||||
|
('type', models.CharField(max_length=32, default='mysql', choices=[('mysql', 'MySQL')], verbose_name='type')),
|
||||||
|
('account', models.ForeignKey(verbose_name='Account', to=settings.AUTH_USER_MODEL, related_name='databaseusers')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name_plural': 'DB users',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='database',
|
||||||
|
name='users',
|
||||||
|
field=models.ManyToManyField(verbose_name='users', related_name='databases', to='databases.DatabaseUser'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='databaseuser',
|
||||||
|
unique_together=set([('username', 'type')]),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='database',
|
||||||
|
unique_together=set([('name', 'type')]),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,37 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
import orchestra.contrib.domains.validators
|
||||||
|
from django.conf import settings
|
||||||
|
import orchestra.contrib.domains.utils
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Domain',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(serialize=False, primary_key=True, verbose_name='ID', auto_created=True)),
|
||||||
|
('name', models.CharField(validators=[orchestra.contrib.domains.validators.validate_domain_name, orchestra.contrib.domains.validators.validate_allowed_domain], help_text='Domain or subdomain name.', unique=True, max_length=256, verbose_name='name')),
|
||||||
|
('serial', models.IntegerField(help_text='Serial number', default=orchestra.contrib.domains.utils.generate_zone_serial, verbose_name='serial')),
|
||||||
|
('account', models.ForeignKey(help_text='Automatically selected for subdomains.', blank=True, verbose_name='Account', to=settings.AUTH_USER_MODEL, related_name='domains')),
|
||||||
|
('top', models.ForeignKey(related_name='subdomain_set', null=True, to='domains.Domain', editable=False)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Record',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(serialize=False, primary_key=True, verbose_name='ID', auto_created=True)),
|
||||||
|
('ttl', models.CharField(validators=[orchestra.contrib.domains.validators.validate_zone_interval], help_text='Record TTL, defaults to 1h', blank=True, max_length=8, verbose_name='TTL')),
|
||||||
|
('type', models.CharField(choices=[('MX', 'MX'), ('NS', 'NS'), ('CNAME', 'CNAME'), ('A', 'A (IPv4 address)'), ('AAAA', 'AAAA (IPv6 address)'), ('SRV', 'SRV'), ('TXT', 'TXT'), ('SOA', 'SOA')], max_length=32, verbose_name='type')),
|
||||||
|
('value', models.CharField(max_length=256, verbose_name='value')),
|
||||||
|
('domain', models.ForeignKey(verbose_name='domain', to='domains.Domain', related_name='records')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,76 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
from django.conf import settings
|
||||||
|
import orchestra.models.fields
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Message',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(primary_key=True, verbose_name='ID', auto_created=True, serialize=False)),
|
||||||
|
('author_name', models.CharField(verbose_name='author name', blank=True, max_length=256)),
|
||||||
|
('content', models.TextField(verbose_name='content')),
|
||||||
|
('created_on', models.DateTimeField(verbose_name='created on', auto_now_add=True)),
|
||||||
|
('author', models.ForeignKey(verbose_name='author', related_name='ticket_messages', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'get_latest_by': 'id',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Queue',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(primary_key=True, verbose_name='ID', auto_created=True, serialize=False)),
|
||||||
|
('name', models.CharField(verbose_name='name', unique=True, max_length=128)),
|
||||||
|
('verbose_name', models.CharField(verbose_name='verbose_name', blank=True, max_length=128)),
|
||||||
|
('default', models.BooleanField(verbose_name='default', default=False)),
|
||||||
|
('notify', orchestra.models.fields.MultiSelectField(blank=True, default=('SUPPORT', 'ADMIN', 'BILLING', 'TECH', 'ADDS', 'EMERGENCY'), choices=[('SUPPORT', 'Support tickets'), ('ADMIN', 'Administrative'), ('BILLING', 'Billing'), ('TECH', 'Technical'), ('ADDS', 'Announcements'), ('EMERGENCY', 'Emergency contact')], help_text='Contacts to notify by email', verbose_name='notify', max_length=256)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Ticket',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(primary_key=True, verbose_name='ID', auto_created=True, serialize=False)),
|
||||||
|
('creator_name', models.CharField(verbose_name='creator name', blank=True, max_length=256)),
|
||||||
|
('subject', models.CharField(verbose_name='subject', max_length=256)),
|
||||||
|
('description', models.TextField(verbose_name='description')),
|
||||||
|
('priority', models.CharField(choices=[('HIGH', 'High'), ('MEDIUM', 'Medium'), ('LOW', 'Low')], verbose_name='priority', default='MEDIUM', max_length=32)),
|
||||||
|
('state', models.CharField(choices=[('NEW', 'New'), ('IN_PROGRESS', 'In Progress'), ('RESOLVED', 'Resolved'), ('FEEDBACK', 'Feedback'), ('REJECTED', 'Rejected'), ('CLOSED', 'Closed')], verbose_name='state', default='NEW', max_length=32)),
|
||||||
|
('created_at', models.DateTimeField(verbose_name='created', auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(verbose_name='modified', auto_now=True)),
|
||||||
|
('cc', models.TextField(verbose_name='CC', blank=True, help_text='emails to send a carbon copy to')),
|
||||||
|
('creator', models.ForeignKey(to=settings.AUTH_USER_MODEL, verbose_name='created by', related_name='tickets_created', null=True)),
|
||||||
|
('owner', models.ForeignKey(blank=True, to=settings.AUTH_USER_MODEL, verbose_name='assigned to', related_name='tickets_owned', null=True)),
|
||||||
|
('queue', models.ForeignKey(blank=True, to='issues.Queue', related_name='tickets', null=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ['-updated_at'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='TicketTracker',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(primary_key=True, verbose_name='ID', auto_created=True, serialize=False)),
|
||||||
|
('ticket', models.ForeignKey(verbose_name='ticket', related_name='trackers', to='issues.Ticket')),
|
||||||
|
('user', models.ForeignKey(verbose_name='user', related_name='ticket_trackers', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='message',
|
||||||
|
name='ticket',
|
||||||
|
field=models.ForeignKey(verbose_name='ticket', related_name='messages', to='issues.Ticket'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='tickettracker',
|
||||||
|
unique_together=set([('ticket', 'user')]),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,33 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
from django.conf import settings
|
||||||
|
import orchestra.core.validators
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('domains', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='List',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(verbose_name='ID', serialize=False, primary_key=True, auto_created=True)),
|
||||||
|
('name', models.CharField(max_length=128, unique=True, verbose_name='name', validators=[orchestra.core.validators.validate_name], help_text='Default list address <name>@lists.pangea.org')),
|
||||||
|
('address_name', models.CharField(blank=True, verbose_name='address name', validators=[orchestra.core.validators.validate_name], max_length=128)),
|
||||||
|
('admin_email', models.EmailField(max_length=254, verbose_name='admin email', help_text='Administration email address')),
|
||||||
|
('is_active', models.BooleanField(verbose_name='active', default=True, help_text='Designates whether this account should be treated as active. Unselect this instead of deleting accounts.')),
|
||||||
|
('account', models.ForeignKey(related_name='lists', verbose_name='Account', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('address_domain', models.ForeignKey(blank=True, null=True, verbose_name='address domain', to='domains.Domain')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='list',
|
||||||
|
unique_together=set([('address_name', 'address_domain')]),
|
||||||
|
),
|
||||||
|
]
|
|
@ -22,9 +22,9 @@ class List(models.Model):
|
||||||
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
|
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
|
||||||
related_name='lists')
|
related_name='lists')
|
||||||
# TODO also admin
|
# TODO also admin
|
||||||
# TODO is_active = models.BooleanField(_("active"), default=True,
|
is_active = models.BooleanField(_("active"), default=True,
|
||||||
# help_text=_("Designates whether this account should be treated as active. "
|
help_text=_("Designates whether this account should be treated as active. "
|
||||||
# "Unselect this instead of deleting accounts."))
|
"Unselect this instead of deleting accounts."))
|
||||||
password = None
|
password = None
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
import orchestra.contrib.mailboxes.validators
|
||||||
|
from django.conf import settings
|
||||||
|
import django.core.validators
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('domains', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Address',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=64, validators=[orchestra.contrib.mailboxes.validators.validate_emailname], help_text='Address name, left blank for a <i>catch-all</i> address', verbose_name='name', blank=True)),
|
||||||
|
('forward', models.CharField(max_length=256, validators=[orchestra.contrib.mailboxes.validators.validate_forward], help_text='Space separated email addresses or mailboxes', verbose_name='forward', blank=True)),
|
||||||
|
('account', models.ForeignKey(related_name='addresses', verbose_name='Account', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('domain', models.ForeignKey(related_name='addresses', verbose_name='domain', to='domains.Domain')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name_plural': 'addresses',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Autoresponse',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')),
|
||||||
|
('subject', models.CharField(max_length=256, verbose_name='subject')),
|
||||||
|
('message', models.TextField(verbose_name='message')),
|
||||||
|
('enabled', models.BooleanField(default=False, verbose_name='enabled')),
|
||||||
|
('address', models.OneToOneField(related_name='autoresponse', verbose_name='address', to='mailboxes.Address')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Mailbox',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')),
|
||||||
|
('name', models.CharField(validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid mailbox name.')], max_length=64, unique=True, help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', verbose_name='name')),
|
||||||
|
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||||
|
('filtering', models.CharField(default='REDIRECT', max_length=16, choices=[('DISABLE', 'Disable'), ('CUSTOM', 'Custom filtering'), ('REDIRECT', 'Archive spam'), ('REJECT', 'Reject spam')])),
|
||||||
|
('custom_filtering', models.TextField(validators=[orchestra.contrib.mailboxes.validators.validate_sieve], help_text='Arbitrary email filtering in sieve language. This overrides any automatic junk email filtering', verbose_name='filtering', blank=True)),
|
||||||
|
('is_active', models.BooleanField(default=True, verbose_name='active')),
|
||||||
|
('account', models.ForeignKey(related_name='mailboxes', verbose_name='account', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name_plural': 'mailboxes',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='address',
|
||||||
|
name='mailboxes',
|
||||||
|
field=models.ManyToManyField(related_name='addresses', verbose_name='mailboxes', blank=True, to='mailboxes.Mailbox'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='address',
|
||||||
|
unique_together=set([('name', 'domain')]),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,8 +1,10 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import models, migrations
|
from django.db import models, migrations
|
||||||
import orchestra.core.validators
|
import orchestra.core.validators
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
import orchestra.models.fields
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -15,35 +17,32 @@ class Migration(migrations.Migration):
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Miscellaneous',
|
name='Miscellaneous',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
('id', models.AutoField(primary_key=True, verbose_name='ID', auto_created=True, serialize=False)),
|
||||||
('description', models.TextField(verbose_name='description', blank=True)),
|
('identifier', orchestra.models.fields.NullableCharField(max_length=256, unique=True, verbose_name='identifier', null=True, help_text='A unique identifier for this service.')),
|
||||||
|
('description', models.TextField(blank=True, verbose_name='description')),
|
||||||
('amount', models.PositiveIntegerField(default=1, verbose_name='amount')),
|
('amount', models.PositiveIntegerField(default=1, verbose_name='amount')),
|
||||||
('is_active', models.BooleanField(default=True, help_text='Designates whether this service should be treated as active. Unselect this instead of deleting services.', verbose_name='active')),
|
('is_active', models.BooleanField(default=True, help_text='Designates whether this service should be treated as active. Unselect this instead of deleting services.', verbose_name='active')),
|
||||||
('account', models.ForeignKey(related_name='miscellaneous', verbose_name='account', to=settings.AUTH_USER_MODEL)),
|
('account', models.ForeignKey(verbose_name='account', to=settings.AUTH_USER_MODEL, related_name='miscellaneous')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name_plural': 'miscellaneous',
|
'verbose_name_plural': 'miscellaneous',
|
||||||
},
|
},
|
||||||
bases=(models.Model,),
|
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='MiscService',
|
name='MiscService',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
('id', models.AutoField(primary_key=True, verbose_name='ID', auto_created=True, serialize=False)),
|
||||||
('name', models.CharField(help_text='Raw name used for internal referenciation, i.e. service match definition', unique=True, max_length=32, verbose_name='name', validators=[orchestra.core.validators.validate_name])),
|
('name', models.CharField(max_length=32, validators=[orchestra.core.validators.validate_name], unique=True, verbose_name='name', help_text='Raw name used for internal referenciation, i.e. service match definition')),
|
||||||
('verbose_name', models.CharField(help_text='Human readable name', max_length=256, verbose_name='verbose name', blank=True)),
|
('verbose_name', models.CharField(blank=True, max_length=256, verbose_name='verbose name', help_text='Human readable name')),
|
||||||
('description', models.TextField(help_text='Optional description', verbose_name='description', blank=True)),
|
('description', models.TextField(blank=True, help_text='Optional description', verbose_name='description')),
|
||||||
|
('has_identifier', models.BooleanField(default=True, help_text='Designates if this service has a <b>unique text</b> field that identifies it or not.', verbose_name='has identifier')),
|
||||||
('has_amount', models.BooleanField(default=False, help_text='Designates whether this service has <tt>amount</tt> property or not.', verbose_name='has amount')),
|
('has_amount', models.BooleanField(default=False, help_text='Designates whether this service has <tt>amount</tt> property or not.', verbose_name='has amount')),
|
||||||
('is_active', models.BooleanField(default=True, help_text='Whether new instances of this service can be created or not. Unselect this instead of deleting services.', verbose_name='active')),
|
('is_active', models.BooleanField(default=True, help_text='Whether new instances of this service can be created or not. Unselect this instead of deleting services.', verbose_name='active')),
|
||||||
],
|
],
|
||||||
options={
|
|
||||||
},
|
|
||||||
bases=(models.Model,),
|
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='miscellaneous',
|
model_name='miscellaneous',
|
||||||
name='service',
|
name='service',
|
||||||
field=models.ForeignKey(related_name='instances', verbose_name='service', to='miscellaneous.MiscService'),
|
field=models.ForeignKey(verbose_name='service', to='miscellaneous.MiscService', related_name='instances'),
|
||||||
preserve_default=True,
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
import orchestra.models.fields
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('miscellaneous', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='miscellaneous',
|
|
||||||
name='identifier',
|
|
||||||
field=orchestra.models.fields.NullableCharField(help_text='A unique identifier for this service.', max_length=256, unique=True, null=True, verbose_name='identifier'),
|
|
||||||
preserve_default=True,
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='miscservice',
|
|
||||||
name='has_identifier',
|
|
||||||
field=models.BooleanField(default=True, help_text='Designates if this service has a <b>unique text</b> field that identifies it or not.', verbose_name='has identifier'),
|
|
||||||
preserve_default=True,
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
import orchestra.models.fields
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('contenttypes', '0002_remove_content_type_name'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='BackendLog',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(primary_key=True, serialize=False, auto_created=True, verbose_name='ID')),
|
||||||
|
('backend', models.CharField(verbose_name='backend', max_length=256)),
|
||||||
|
('state', models.CharField(choices=[('RECEIVED', 'RECEIVED'), ('TIMEOUT', 'TIMEOUT'), ('STARTED', 'STARTED'), ('SUCCESS', 'SUCCESS'), ('FAILURE', 'FAILURE'), ('ERROR', 'ERROR'), ('REVOKED', 'REVOKED')], verbose_name='state', max_length=16, default='RECEIVED')),
|
||||||
|
('script', models.TextField(verbose_name='script')),
|
||||||
|
('stdout', models.TextField(verbose_name='stdout')),
|
||||||
|
('stderr', models.TextField(verbose_name='stdin')),
|
||||||
|
('traceback', models.TextField(verbose_name='traceback')),
|
||||||
|
('exit_code', models.IntegerField(null=True, verbose_name='exit code')),
|
||||||
|
('task_id', models.CharField(null=True, verbose_name='task ID', unique=True, max_length=36, help_text='Celery task ID when used as execution backend')),
|
||||||
|
('created_at', models.DateTimeField(verbose_name='created', auto_now_add=True)),
|
||||||
|
('updated_at', models.DateTimeField(verbose_name='updated', auto_now=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'get_latest_by': 'id',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='BackendOperation',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(primary_key=True, serialize=False, auto_created=True, verbose_name='ID')),
|
||||||
|
('backend', models.CharField(verbose_name='backend', max_length=256)),
|
||||||
|
('action', models.CharField(verbose_name='action', max_length=64)),
|
||||||
|
('object_id', models.PositiveIntegerField()),
|
||||||
|
('content_type', models.ForeignKey(to='contenttypes.ContentType')),
|
||||||
|
('log', models.ForeignKey(related_name='operations', to='orchestration.BackendLog')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Operation',
|
||||||
|
'verbose_name_plural': 'Operations',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Route',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(primary_key=True, serialize=False, auto_created=True, verbose_name='ID')),
|
||||||
|
('backend', models.CharField(choices=[('Apache2Traffic', '[M] Apache 2 Traffic'), ('DovecotMaildirDisk', '[M] Dovecot Maildir size'), ('Exim4Traffic', '[M] Exim4 traffic'), ('MailmanSubscribers', '[M] Mailman subscribers'), ('MailmanTraffic', '[M] Mailman traffic'), ('MailmanTrafficBash', '[M] Mailman traffic (Bash)'), ('MysqlDisk', '[M] MySQL disk'), ('OpenVZTraffic', '[M] OpenVZTraffic'), ('PostfixMailscannerTraffic', '[M] Postfix-Mailscanner traffic'), ('UNIXUserDisk', '[M] UNIX user disk'), ('VsFTPdTraffic', '[M] VsFTPd traffic'), ('Apache2Backend', '[S] Apache 2'), ('BSCWBackend', '[S] BSCW SaaS'), ('Bind9MasterDomainBackend', '[S] Bind9 master domain'), ('Bind9SlaveDomainBackend', '[S] Bind9 slave domain'), ('DokuWikiMuBackend', '[S] DokuWiki multisite'), ('DovecotPostfixPasswdVirtualUserBackend', '[S] Dovecot-Postfix virtualuser'), ('DrupalMuBackend', '[S] Drupal multisite'), ('GitLabSaaSBackend', '[S] GitLab SaaS'), ('AutoresponseBackend', '[S] Mail autoresponse'), ('MailmanBackend', '[S] Mailman'), ('MySQLBackend', '[S] MySQL database'), ('MySQLUserBackend', '[S] MySQL user'), ('PHPBackend', '[S] PHP FPM/FCGID'), ('PostfixAddressBackend', '[S] Postfix address'), ('StaticBackend', '[S] Static'), ('SymbolicLinkBackend', '[S] Symbolic link webapp'), ('UNIXUserMaildirBackend', '[S] UNIX maildir user'), ('UNIXUserBackend', '[S] UNIX user'), ('WebalizerAppBackend', '[S] Webalizer App'), ('WebalizerBackend', '[S] Webalizer Content'), ('WordPressBackend', '[S] Wordpress'), ('WordpressMuBackend', '[S] Wordpress multisite'), ('WordpressMuBackend', '[S] Wordpress multisite'), ('PhpListSaaSBackend', '[S] phpList SaaS')], verbose_name='backend', max_length=256)),
|
||||||
|
('match', models.CharField(blank=True, default='True', verbose_name='match', max_length=256, help_text='Python expression used for selecting the targe host, <em>instance</em> referes to the current object.')),
|
||||||
|
('is_active', models.BooleanField(verbose_name='active', default=True)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Server',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(primary_key=True, serialize=False, auto_created=True, verbose_name='ID')),
|
||||||
|
('name', models.CharField(verbose_name='name', unique=True, max_length=256)),
|
||||||
|
('address', orchestra.models.fields.NullableCharField(blank=True, unique=True, help_text='IP address or domain name', null=True, verbose_name='address', max_length=256)),
|
||||||
|
('description', models.TextField(blank=True, verbose_name='description')),
|
||||||
|
('os', models.CharField(choices=[('LINUX', 'Linux')], verbose_name='operative system', max_length=32, default='LINUX')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='route',
|
||||||
|
name='host',
|
||||||
|
field=models.ForeignKey(to='orchestration.Server', verbose_name='host'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='backendlog',
|
||||||
|
name='server',
|
||||||
|
field=models.ForeignKey(to='orchestration.Server', related_name='execution_logs', verbose_name='server'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='route',
|
||||||
|
unique_together=set([('backend', 'host')]),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,54 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
import django.utils.timezone
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('contenttypes', '0002_remove_content_type_name'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('services', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='MetricStorage',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
|
||||||
|
('value', models.DecimalField(verbose_name='value', decimal_places=2, max_digits=16)),
|
||||||
|
('created_on', models.DateField(verbose_name='created', auto_now_add=True)),
|
||||||
|
('updated_on', models.DateTimeField(verbose_name='updated')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'get_latest_by': 'id',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Order',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)),
|
||||||
|
('object_id', models.PositiveIntegerField(null=True)),
|
||||||
|
('registered_on', models.DateField(verbose_name='registered', default=django.utils.timezone.now)),
|
||||||
|
('cancelled_on', models.DateField(blank=True, verbose_name='cancelled', null=True)),
|
||||||
|
('billed_on', models.DateField(blank=True, verbose_name='billed', null=True)),
|
||||||
|
('billed_until', models.DateField(blank=True, verbose_name='billed until', null=True)),
|
||||||
|
('ignore', models.BooleanField(verbose_name='ignore', default=False)),
|
||||||
|
('description', models.TextField(blank=True, verbose_name='description')),
|
||||||
|
('account', models.ForeignKey(related_name='orders', verbose_name='account', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('content_type', models.ForeignKey(to='contenttypes.ContentType')),
|
||||||
|
('service', models.ForeignKey(related_name='orders', verbose_name='service', to='services.Service')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'get_latest_by': 'id',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='metricstorage',
|
||||||
|
name='order',
|
||||||
|
field=models.ForeignKey(related_name='metrics', verbose_name='order', to='orders.Order'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -112,7 +112,7 @@ class Order(models.Model):
|
||||||
object_id = models.PositiveIntegerField(null=True)
|
object_id = models.PositiveIntegerField(null=True)
|
||||||
service = models.ForeignKey(settings.ORDERS_SERVICE_MODEL, verbose_name=_("service"),
|
service = models.ForeignKey(settings.ORDERS_SERVICE_MODEL, verbose_name=_("service"),
|
||||||
related_name='orders')
|
related_name='orders')
|
||||||
registered_on = models.DateField(_("registered"), default=lambda: timezone.now())
|
registered_on = models.DateField(_("registered"), default=timezone.now)
|
||||||
cancelled_on = models.DateField(_("cancelled"), null=True, blank=True)
|
cancelled_on = models.DateField(_("cancelled"), null=True, blank=True)
|
||||||
billed_on = models.DateField(_("billed"), null=True, blank=True)
|
billed_on = models.DateField(_("billed"), null=True, blank=True)
|
||||||
billed_until = models.DateField(_("billed until"), null=True, blank=True)
|
billed_until = models.DateField(_("billed until"), null=True, blank=True)
|
||||||
|
@ -137,7 +137,7 @@ class Order(models.Model):
|
||||||
else:
|
else:
|
||||||
services = [service]
|
services = [service]
|
||||||
for service in services:
|
for service in services:
|
||||||
orders = Order.objects.by_object(instance, service=service).active()
|
orders = Order.objects.by_object(instance, service=service).select_related('service').active()
|
||||||
if service.handler.matches(instance):
|
if service.handler.matches(instance):
|
||||||
if not orders:
|
if not orders:
|
||||||
account_id = getattr(instance, 'account_id', instance.pk)
|
account_id = getattr(instance, 'account_id', instance.pk)
|
||||||
|
@ -152,12 +152,16 @@ class Order(models.Model):
|
||||||
updates.append((order, 'created'))
|
updates.append((order, 'created'))
|
||||||
logger.info("CREATED new order id: {id}".format(id=order.id))
|
logger.info("CREATED new order id: {id}".format(id=order.id))
|
||||||
else:
|
else:
|
||||||
order = orders.get()
|
if len(orders) > 1:
|
||||||
|
raise ValueError("A single active order was expected.")
|
||||||
|
order = orders[0]
|
||||||
updates.append((order, 'updated'))
|
updates.append((order, 'updated'))
|
||||||
if commit:
|
if commit:
|
||||||
order.update()
|
order.update()
|
||||||
elif orders:
|
elif orders:
|
||||||
order = orders.get()
|
if len(orders) > 1:
|
||||||
|
raise ValueError("A single active order was expected.")
|
||||||
|
order = orders[0]
|
||||||
order.cancel(commit=commit)
|
order.cancel(commit=commit)
|
||||||
logger.info("CANCELLED order id: {id}".format(id=order.id))
|
logger.info("CANCELLED order id: {id}".format(id=order.id))
|
||||||
updates.append((order, 'cancelled'))
|
updates.append((order, 'cancelled'))
|
||||||
|
@ -178,7 +182,7 @@ class Order(models.Model):
|
||||||
metric = ', metric:{}'.format(metric)
|
metric = ', metric:{}'.format(metric)
|
||||||
description = handler.get_order_description(instance)
|
description = handler.get_order_description(instance)
|
||||||
logger.info("UPDATED order id:{id}, description:{description}{metric}".format(
|
logger.info("UPDATED order id:{id}, description:{description}{metric}".format(
|
||||||
id=self.id, description=description, metric=metric).encode('ascii', 'replace')
|
id=self.id, description=description, metric=metric).encode('ascii', 'replace')
|
||||||
)
|
)
|
||||||
if self.description != description:
|
if self.description != description:
|
||||||
self.description = description
|
self.description = description
|
||||||
|
@ -268,39 +272,26 @@ class MetricStorage(models.Model):
|
||||||
|
|
||||||
accounts.register(Order)
|
accounts.register(Order)
|
||||||
|
|
||||||
#@receiver(pre_delete, dispatch_uid="orders.account_orders")
|
|
||||||
#def account_orders(sender, **kwargs):
|
|
||||||
# account = kwargs['instance']
|
|
||||||
# if isinstance(account, Order.account.field.rel.to):
|
|
||||||
# account._deleted = True
|
|
||||||
|
|
||||||
|
# TODO perhas use cache = caches.get_request_cache() to cache an account delete and don't processes get_related_objects() if the case
|
||||||
# FIXME account deletion generates a integrity error
|
# FIXME https://code.djangoproject.com/ticket/24576
|
||||||
# TODO build a cache hash table {model: related, model: None}
|
# TODO build a cache hash table {model: related, model: None}
|
||||||
@receiver(post_delete, dispatch_uid="orders.cancel_orders")
|
@receiver(post_delete, dispatch_uid="orders.cancel_orders")
|
||||||
def cancel_orders(sender, **kwargs):
|
def cancel_orders(sender, **kwargs):
|
||||||
if sender._meta.app_label not in settings.ORDERS_EXCLUDED_APPS:
|
if sender._meta.app_label not in settings.ORDERS_EXCLUDED_APPS:
|
||||||
instance = kwargs['instance']
|
instance = kwargs['instance']
|
||||||
# Account delete will delete all related orders, no need to maintain order consistency
|
# Account delete will delete all related orders, no need to maintain order consistency
|
||||||
# if isinstance(instance, Order.account.field.rel.to):
|
if isinstance(instance, Order.account.field.rel.to):
|
||||||
# return
|
return
|
||||||
print('delete', sender, instance, instance.pk)
|
|
||||||
if type(instance) in services:
|
if type(instance) in services:
|
||||||
for order in Order.objects.by_object(instance).active():
|
for order in Order.objects.by_object(instance).active():
|
||||||
order.cancel()
|
order.cancel()
|
||||||
elif not hasattr(instance, 'account'):
|
elif not hasattr(instance, 'account'):
|
||||||
|
# FIXME Indeterminate behaviour
|
||||||
related = helpers.get_related_object(instance)
|
related = helpers.get_related_object(instance)
|
||||||
# FIXME this shit returns objects that are already deleted
|
|
||||||
# Indeterminate behaviour
|
|
||||||
if related and related != instance:
|
if related and related != instance:
|
||||||
# if isinstance(related, Order.account.field.rel.to):
|
|
||||||
# return
|
|
||||||
print('related', type(related), related, related.pk)
|
|
||||||
# try:
|
|
||||||
type(related).objects.get(pk=related.pk)
|
type(related).objects.get(pk=related.pk)
|
||||||
# except related.DoesNotExist:
|
|
||||||
# print('not exists', type(related), related, related.pk)
|
|
||||||
print([(str(a).encode('utf8'), b) for a, b in Order.update_orders(related)])
|
|
||||||
|
|
||||||
@receiver(post_save, dispatch_uid="orders.update_orders")
|
@receiver(post_save, dispatch_uid="orders.update_orders")
|
||||||
def update_orders(sender, **kwargs):
|
def update_orders(sender, **kwargs):
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
import orchestra.core.validators
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('services', '__first__'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ContractedPlan',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)),
|
||||||
|
('account', models.ForeignKey(related_name='plans', verbose_name='account', to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name_plural': 'plans',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Plan',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)),
|
||||||
|
('name', models.CharField(verbose_name='name', unique=True, validators=[orchestra.core.validators.validate_name], max_length=32)),
|
||||||
|
('verbose_name', models.CharField(verbose_name='verbose_name', max_length=128, blank=True)),
|
||||||
|
('is_active', models.BooleanField(verbose_name='active', default=True, help_text='Designates whether this account should be treated as active. Unselect this instead of deleting accounts.')),
|
||||||
|
('is_default', models.BooleanField(verbose_name='default', default=False, help_text='Designates whether this plan is used by default or not.')),
|
||||||
|
('is_combinable', models.BooleanField(verbose_name='combinable', default=True, help_text='Designates whether this plan can be combined with other plans or not.')),
|
||||||
|
('allow_multiple', models.BooleanField(verbose_name='allow multiple', default=False, help_text='Designates whether this plan allow for multiple contractions.')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Rate',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)),
|
||||||
|
('quantity', models.PositiveIntegerField(verbose_name='quantity', null=True, blank=True)),
|
||||||
|
('price', models.DecimalField(verbose_name='price', decimal_places=2, max_digits=12)),
|
||||||
|
('plan', models.ForeignKey(related_name='rates', verbose_name='plan', to='plans.Plan')),
|
||||||
|
('service', models.ForeignKey(related_name='rates', verbose_name='service', to='services.Service')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='contractedplan',
|
||||||
|
name='plan',
|
||||||
|
field=models.ForeignKey(related_name='contracts', verbose_name='plan', to='plans.Plan'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='rate',
|
||||||
|
unique_together=set([('service', 'plan', 'quantity')]),
|
||||||
|
),
|
||||||
|
]
|
|
@ -15,9 +15,9 @@ from . import rating
|
||||||
class Plan(models.Model):
|
class Plan(models.Model):
|
||||||
name = models.CharField(_("name"), max_length=32, unique=True, validators=[validate_name])
|
name = models.CharField(_("name"), max_length=32, unique=True, validators=[validate_name])
|
||||||
verbose_name = models.CharField(_("verbose_name"), max_length=128, blank=True)
|
verbose_name = models.CharField(_("verbose_name"), max_length=128, blank=True)
|
||||||
# TODO is_active = models.BooleanField(_("active"), default=True,
|
is_active = models.BooleanField(_("active"), default=True,
|
||||||
# help_text=_("Designates whether this account should be treated as active. "
|
help_text=_("Designates whether this account should be treated as active. "
|
||||||
# "Unselect this instead of deleting accounts."))
|
"Unselect this instead of deleting accounts."))
|
||||||
is_default = models.BooleanField(_("default"), default=False,
|
is_default = models.BooleanField(_("default"), default=False,
|
||||||
help_text=_("Designates whether this plan is used by default or not."))
|
help_text=_("Designates whether this plan is used by default or not."))
|
||||||
is_combinable = models.BooleanField(_("combinable"), default=True,
|
is_combinable = models.BooleanField(_("combinable"), default=True,
|
||||||
|
|
|
@ -21,15 +21,15 @@ from .models import Resource, ResourceData, MonitorData
|
||||||
|
|
||||||
class ResourceAdmin(ExtendedModelAdmin):
|
class ResourceAdmin(ExtendedModelAdmin):
|
||||||
list_display = (
|
list_display = (
|
||||||
'id', 'verbose_name', 'content_type', 'period', 'on_demand',
|
'id', 'verbose_name', 'content_type', 'aggregation', 'on_demand',
|
||||||
'default_allocation', 'unit', 'crontab', 'is_active'
|
'default_allocation', 'unit', 'crontab', 'is_active'
|
||||||
)
|
)
|
||||||
list_display_links = ('id', 'verbose_name')
|
list_display_links = ('id', 'verbose_name')
|
||||||
list_editable = ('default_allocation', 'crontab', 'is_active',)
|
list_editable = ('default_allocation', 'crontab', 'is_active',)
|
||||||
list_filter = (UsedContentTypeFilter, 'period', 'on_demand', 'disable_trigger')
|
list_filter = (UsedContentTypeFilter, 'aggregation', 'on_demand', 'disable_trigger')
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, {
|
(None, {
|
||||||
'fields': ('verbose_name', 'name', 'content_type', 'period'),
|
'fields': ('verbose_name', 'name', 'content_type', 'aggregation'),
|
||||||
}),
|
}),
|
||||||
(_("Configuration"), {
|
(_("Configuration"), {
|
||||||
'fields': ('unit', 'scale', 'on_demand', 'default_allocation', 'disable_trigger',
|
'fields': ('unit', 'scale', 'on_demand', 'default_allocation', 'disable_trigger',
|
||||||
|
|
|
@ -7,7 +7,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
from orchestra import plugins
|
from orchestra import plugins
|
||||||
|
|
||||||
|
|
||||||
class DataMethod(plugins.Plugin, metaclass=plugins.PluginMount):
|
class Aggregation(plugins.Plugin, metaclass=plugins.PluginMount):
|
||||||
""" filters and computes dataset usage """
|
""" filters and computes dataset usage """
|
||||||
def filter(self, dataset):
|
def filter(self, dataset):
|
||||||
""" Filter the dataset to get the relevant data according to the period """
|
""" Filter the dataset to get the relevant data according to the period """
|
||||||
|
@ -18,7 +18,7 @@ class DataMethod(plugins.Plugin, metaclass=plugins.PluginMount):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
class Last(DataMethod):
|
class Last(Aggregation):
|
||||||
name = 'last'
|
name = 'last'
|
||||||
verbose_name = _("Last value")
|
verbose_name = _("Last value")
|
||||||
|
|
|
@ -1,27 +1,28 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import models, migrations
|
from django.db import models, migrations
|
||||||
import orchestra.core.validators
|
import orchestra.core.validators
|
||||||
import orchestra.contrib.resources.validators
|
import orchestra.contrib.resources.validators
|
||||||
import django.utils.timezone
|
|
||||||
import orchestra.models.fields
|
import orchestra.models.fields
|
||||||
|
import django.utils.timezone
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
('contenttypes', '0002_remove_content_type_name'),
|
||||||
('djcelery', '__first__'),
|
('djcelery', '__first__'),
|
||||||
('contenttypes', '0001_initial'),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='MonitorData',
|
name='MonitorData',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
('id', models.AutoField(primary_key=True, auto_created=True, verbose_name='ID', serialize=False)),
|
||||||
('monitor', models.CharField(max_length=256, verbose_name='monitor', choices=[(b'Apache2Traffic', '[M] Apache 2 Traffic'), (b'MaildirDisk', '[M] Maildir disk usage'), (b'MailmanSubscribers', '[M] Mailman subscribers'), (b'MailmanTraffic', '[M] Mailman traffic'), (b'FTPTraffic', '[M] Main FTP traffic'), (b'SystemUserDisk', '[M] Main user disk'), (b'MysqlDisk', '[M] MySQL disk'), (b'OpenVZTraffic', '[M] OpenVZTraffic')])),
|
('monitor', models.CharField(choices=[('Apache2Traffic', '[M] Apache 2 Traffic'), ('DovecotMaildirDisk', '[M] Dovecot Maildir size'), ('Exim4Traffic', '[M] Exim4 traffic'), ('MailmanSubscribers', '[M] Mailman subscribers'), ('MailmanTraffic', '[M] Mailman traffic'), ('MailmanTrafficBash', '[M] Mailman traffic (Bash)'), ('MysqlDisk', '[M] MySQL disk'), ('OpenVZTraffic', '[M] OpenVZTraffic'), ('PostfixMailscannerTraffic', '[M] Postfix-Mailscanner traffic'), ('UNIXUserDisk', '[M] UNIX user disk'), ('VsFTPdTraffic', '[M] VsFTPd traffic')], verbose_name='monitor', max_length=256)),
|
||||||
('object_id', models.PositiveIntegerField(verbose_name='object id')),
|
('object_id', models.PositiveIntegerField(verbose_name='object id')),
|
||||||
('created_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='created')),
|
('created_at', models.DateTimeField(verbose_name='created', default=django.utils.timezone.now)),
|
||||||
('value', models.DecimalField(verbose_name='value', max_digits=16, decimal_places=2)),
|
('value', models.DecimalField(verbose_name='value', max_digits=16, decimal_places=2)),
|
||||||
('content_type', models.ForeignKey(verbose_name='content type', to='contenttypes.ContentType')),
|
('content_type', models.ForeignKey(verbose_name='content type', to='contenttypes.ContentType')),
|
||||||
],
|
],
|
||||||
|
@ -29,44 +30,39 @@ class Migration(migrations.Migration):
|
||||||
'get_latest_by': 'id',
|
'get_latest_by': 'id',
|
||||||
'verbose_name_plural': 'monitor data',
|
'verbose_name_plural': 'monitor data',
|
||||||
},
|
},
|
||||||
bases=(models.Model,),
|
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Resource',
|
name='Resource',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
('id', models.AutoField(primary_key=True, auto_created=True, verbose_name='ID', serialize=False)),
|
||||||
('name', models.CharField(help_text='Required. 32 characters or fewer. Lowercase letters, digits and hyphen only.', max_length=32, verbose_name='name', validators=[orchestra.core.validators.validate_name])),
|
('name', models.CharField(validators=[orchestra.core.validators.validate_name], verbose_name='name', max_length=32, help_text='Required. 32 characters or fewer. Lowercase letters, digits and hyphen only.')),
|
||||||
('verbose_name', models.CharField(max_length=256, verbose_name='verbose name')),
|
('verbose_name', models.CharField(verbose_name='verbose name', max_length=256)),
|
||||||
('period', models.CharField(default=b'LAST', help_text='Operation used for aggregating this resource monitored data.', max_length=16, verbose_name='period', choices=[(b'LAST', 'Last'), (b'MONTHLY_SUM', 'Monthly Sum'), (b'MONTHLY_AVG', 'Monthly Average')])),
|
('aggregation', models.CharField(choices=[('last-10-days-avg', 'Last 10 days AVG'), ('last', 'Last value'), ('monthly-avg', 'Monthly AVG'), ('monthly-sum', 'Monthly Sum')], verbose_name='aggregation', max_length=16, help_text='Method used for aggregating this resource monitored data.', default='last-10-days-avg')),
|
||||||
('on_demand', models.BooleanField(default=False, help_text='If enabled the resource will not be pre-allocated, but allocated under the application demand', verbose_name='on demand')),
|
('on_demand', models.BooleanField(verbose_name='on demand', default=False, help_text='If enabled the resource will not be pre-allocated, but allocated under the application demand')),
|
||||||
('default_allocation', models.PositiveIntegerField(help_text='Default allocation value used when this is not an on demand resource', null=True, verbose_name='default allocation', blank=True)),
|
('default_allocation', models.PositiveIntegerField(verbose_name='default allocation', help_text='Default allocation value used when this is not an on demand resource', null=True, blank=True)),
|
||||||
('unit', models.CharField(help_text='The unit in which this resource is represented. For example GB, KB or subscribers', max_length=16, verbose_name='unit')),
|
('unit', models.CharField(verbose_name='unit', max_length=16, help_text='The unit in which this resource is represented. For example GB, KB or subscribers')),
|
||||||
('scale', models.CharField(help_text='Scale in which this resource monitoring resoults should be prorcessed to match with unit. e.g. <tt>10**9</tt>', max_length=32, verbose_name='scale', validators=[orchestra.contrib.resources.validators.validate_scale])),
|
('scale', models.CharField(validators=[orchestra.contrib.resources.validators.validate_scale], verbose_name='scale', max_length=32, help_text='Scale in which this resource monitoring resoults should be prorcessed to match with unit. e.g. <tt>10**9</tt>')),
|
||||||
('disable_trigger', models.BooleanField(default=False, help_text='Disables monitors exeeded and recovery triggers', verbose_name='disable trigger')),
|
('disable_trigger', models.BooleanField(verbose_name='disable trigger', default=False, help_text='Disables monitors exeeded and recovery triggers')),
|
||||||
('monitors', orchestra.models.fields.MultiSelectField(blank=True, help_text='Monitor backends used for monitoring this resource.', max_length=256, verbose_name='monitors', choices=[(b'Apache2Traffic', '[M] Apache 2 Traffic'), (b'MaildirDisk', '[M] Maildir disk usage'), (b'MailmanSubscribers', '[M] Mailman subscribers'), (b'MailmanTraffic', '[M] Mailman traffic'), (b'FTPTraffic', '[M] Main FTP traffic'), (b'SystemUserDisk', '[M] Main user disk'), (b'MysqlDisk', '[M] MySQL disk'), (b'OpenVZTraffic', '[M] OpenVZTraffic')])),
|
('monitors', orchestra.models.fields.MultiSelectField(choices=[('Apache2Traffic', '[M] Apache 2 Traffic'), ('DovecotMaildirDisk', '[M] Dovecot Maildir size'), ('Exim4Traffic', '[M] Exim4 traffic'), ('MailmanSubscribers', '[M] Mailman subscribers'), ('MailmanTraffic', '[M] Mailman traffic'), ('MailmanTrafficBash', '[M] Mailman traffic (Bash)'), ('MysqlDisk', '[M] MySQL disk'), ('OpenVZTraffic', '[M] OpenVZTraffic'), ('PostfixMailscannerTraffic', '[M] Postfix-Mailscanner traffic'), ('UNIXUserDisk', '[M] UNIX user disk'), ('VsFTPdTraffic', '[M] VsFTPd traffic')], verbose_name='monitors', max_length=256, help_text='Monitor backends used for monitoring this resource.', blank=True)),
|
||||||
('is_active', models.BooleanField(default=True, verbose_name='active')),
|
('is_active', models.BooleanField(verbose_name='active', default=True)),
|
||||||
('content_type', models.ForeignKey(help_text='Model where this resource will be hooked.', to='contenttypes.ContentType')),
|
('content_type', models.ForeignKey(to='contenttypes.ContentType', help_text='Model where this resource will be hooked.')),
|
||||||
('crontab', models.ForeignKey(blank=True, to='djcelery.CrontabSchedule', help_text='Crontab for periodic execution. Leave it empty to disable periodic monitoring', null=True, verbose_name='crontab')),
|
('crontab', models.ForeignKey(verbose_name='crontab', null=True, to='djcelery.CrontabSchedule', blank=True, help_text='Crontab for periodic execution. Leave it empty to disable periodic monitoring')),
|
||||||
],
|
],
|
||||||
options={
|
|
||||||
},
|
|
||||||
bases=(models.Model,),
|
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='ResourceData',
|
name='ResourceData',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
('id', models.AutoField(primary_key=True, auto_created=True, verbose_name='ID', serialize=False)),
|
||||||
('object_id', models.PositiveIntegerField(verbose_name='object id')),
|
('object_id', models.PositiveIntegerField(verbose_name='object id')),
|
||||||
('used', models.DecimalField(verbose_name='used', null=True, editable=False, max_digits=16, decimal_places=2)),
|
('used', models.DecimalField(editable=False, verbose_name='used', max_digits=16, null=True, decimal_places=3)),
|
||||||
('updated_at', models.DateTimeField(verbose_name='updated', null=True, editable=False)),
|
('updated_at', models.DateTimeField(editable=False, verbose_name='updated', null=True)),
|
||||||
('allocated', models.DecimalField(null=True, verbose_name='allocated', max_digits=8, decimal_places=2, blank=True)),
|
('allocated', models.DecimalField(decimal_places=2, verbose_name='allocated', max_digits=8, null=True, blank=True)),
|
||||||
('content_type', models.ForeignKey(verbose_name='content type', to='contenttypes.ContentType')),
|
('content_type', models.ForeignKey(verbose_name='content type', to='contenttypes.ContentType')),
|
||||||
('resource', models.ForeignKey(related_name='dataset', verbose_name='resource', to='resources.Resource')),
|
('resource', models.ForeignKey(verbose_name='resource', related_name='dataset', to='resources.Resource')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name_plural': 'resource data',
|
'verbose_name_plural': 'resource data',
|
||||||
},
|
},
|
||||||
bases=(models.Model,),
|
|
||||||
),
|
),
|
||||||
migrations.AlterUniqueTogether(
|
migrations.AlterUniqueTogether(
|
||||||
name='resourcedata',
|
name='resourcedata',
|
||||||
|
@ -74,6 +70,6 @@ class Migration(migrations.Migration):
|
||||||
),
|
),
|
||||||
migrations.AlterUniqueTogether(
|
migrations.AlterUniqueTogether(
|
||||||
name='resource',
|
name='resource',
|
||||||
unique_together=set([('name', 'content_type'), ('verbose_name', 'content_type')]),
|
unique_together=set([('verbose_name', 'content_type'), ('name', 'content_type')]),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('resources', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='resourcedata',
|
|
||||||
name='used',
|
|
||||||
field=models.DecimalField(verbose_name='used', null=True, editable=False, max_digits=16, decimal_places=3),
|
|
||||||
preserve_default=True,
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -16,7 +16,7 @@ from orchestra.utils.sys import run
|
||||||
|
|
||||||
from . import tasks
|
from . import tasks
|
||||||
from .backends import ServiceMonitor
|
from .backends import ServiceMonitor
|
||||||
from .methods import DataMethod
|
from .aggregations import Aggregation
|
||||||
from .validators import validate_scale
|
from .validators import validate_scale
|
||||||
|
|
||||||
|
|
||||||
|
@ -47,9 +47,8 @@ class Resource(models.Model):
|
||||||
verbose_name = models.CharField(_("verbose name"), max_length=256)
|
verbose_name = models.CharField(_("verbose name"), max_length=256)
|
||||||
content_type = models.ForeignKey(ContentType,
|
content_type = models.ForeignKey(ContentType,
|
||||||
help_text=_("Model where this resource will be hooked."))
|
help_text=_("Model where this resource will be hooked."))
|
||||||
# TODO rename to aggregation
|
aggregation = models.CharField(_("aggregation"), max_length=16,
|
||||||
period = models.CharField(_("aggregation"), max_length=16,
|
choices=Aggregation.get_choices(), default=Aggregation.get_choices()[0][0],
|
||||||
choices=DataMethod.get_choices(), default=DataMethod.get_choices()[0][0],
|
|
||||||
help_text=_("Method used for aggregating this resource monitored data."))
|
help_text=_("Method used for aggregating this resource monitored data."))
|
||||||
on_demand = models.BooleanField(_("on demand"), default=False,
|
on_demand = models.BooleanField(_("on demand"), default=False,
|
||||||
help_text=_("If enabled the resource will not be pre-allocated, "
|
help_text=_("If enabled the resource will not be pre-allocated, "
|
||||||
|
@ -87,13 +86,13 @@ class Resource(models.Model):
|
||||||
return "{}-{}".format(str(self.content_type), self.name)
|
return "{}-{}".format(str(self.content_type), self.name)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def method_class(self):
|
def aggregation_class(self):
|
||||||
return DataMethod.get(self.period)
|
return Aggregation.get(self.aggregation)
|
||||||
|
|
||||||
@cached_property
|
@cached_property
|
||||||
def method_instance(self):
|
def aggregation_instance(self):
|
||||||
""" Per request lived type_instance """
|
""" Per request lived type_instance """
|
||||||
return self.method_class(self)
|
return self.aggregation_class(self)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
self.verbose_name = self.verbose_name.strip()
|
self.verbose_name = self.verbose_name.strip()
|
||||||
|
@ -216,7 +215,7 @@ class ResourceData(models.Model):
|
||||||
total = 0
|
total = 0
|
||||||
has_result = False
|
has_result = False
|
||||||
for dataset in self.get_monitor_datasets():
|
for dataset in self.get_monitor_datasets():
|
||||||
usage = resource.method_instance.compute_usage(dataset)
|
usage = resource.aggregation_instance.compute_usage(dataset)
|
||||||
if usage is not None:
|
if usage is not None:
|
||||||
has_result = True
|
has_result = True
|
||||||
total += usage
|
total += usage
|
||||||
|
@ -258,7 +257,7 @@ class ResourceData(models.Model):
|
||||||
object_id__in=pks
|
object_id__in=pks
|
||||||
)
|
)
|
||||||
datasets.append(
|
datasets.append(
|
||||||
resource.method_instance.filter(dataset)
|
resource.aggregation_instance.filter(dataset)
|
||||||
)
|
)
|
||||||
return datasets
|
return datasets
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
import orchestra.core.validators
|
||||||
|
import jsonfield.fields
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('databases', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SaaS',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(verbose_name='ID', auto_created=True, serialize=False, primary_key=True)),
|
||||||
|
('service', models.CharField(verbose_name='service', max_length=32, choices=[('bscw', 'BSCW'), ('DokuWikiService', 'Dowkuwiki'), ('DrupalService', 'Drupal'), ('gitlab', 'GitLab'), ('MoodleService', 'Moodle'), ('seafile', 'SeaFile'), ('WordPressService', 'WordPress'), ('phplist', 'phpList')])),
|
||||||
|
('name', models.CharField(help_text='Required. 64 characters or fewer. Letters, digits and ./-/_ only.', max_length=64, validators=[orchestra.core.validators.validate_username], verbose_name='Name')),
|
||||||
|
('is_active', models.BooleanField(help_text='Designates whether this service should be treated as active. ', verbose_name='active', default=True)),
|
||||||
|
('data', jsonfield.fields.JSONField(help_text='Extra information dependent of each service.', verbose_name='data', default={})),
|
||||||
|
('account', models.ForeignKey(related_name='saas', to=settings.AUTH_USER_MODEL, verbose_name='account')),
|
||||||
|
('database', models.ForeignKey(to='databases.Database', null=True, blank=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'SaaS',
|
||||||
|
'verbose_name_plural': 'SaaS',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='saas',
|
||||||
|
unique_together=set([('name', 'service')]),
|
||||||
|
),
|
||||||
|
]
|
|
@ -6,8 +6,8 @@ import math
|
||||||
from dateutil import relativedelta
|
from dateutil import relativedelta
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.utils import timezone
|
from django.utils import timezone, translation
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext, ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra import plugins
|
from orchestra import plugins
|
||||||
from orchestra.utils.humanize import text2int
|
from orchestra.utils.humanize import text2int
|
||||||
|
@ -130,11 +130,14 @@ class ServiceHandler(plugins.Plugin, metaclass=plugins.PluginMount):
|
||||||
safe_locals = {
|
safe_locals = {
|
||||||
'instance': instance,
|
'instance': instance,
|
||||||
'obj': instance,
|
'obj': instance,
|
||||||
|
'ugettext': ugettext,
|
||||||
instance._meta.model_name: instance,
|
instance._meta.model_name: instance,
|
||||||
}
|
}
|
||||||
if not self.order_description:
|
account = getattr(instance, 'account', instance)
|
||||||
return '%s: %s' % (self.description, instance)
|
with translation.override(account.language):
|
||||||
return eval(self.order_description, safe_locals)
|
if not self.order_description:
|
||||||
|
return '%s: %s' % (ugettext(self.description), instance)
|
||||||
|
return eval(self.order_description, safe_locals)
|
||||||
|
|
||||||
def get_billing_point(self, order, bp=None, **options):
|
def get_billing_point(self, order, bp=None, **options):
|
||||||
not_cachable = self.billing_point == self.FIXED_DATE and options.get('fixed_point')
|
not_cachable = self.billing_point == self.FIXED_DATE and options.get('fixed_point')
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('contenttypes', '0002_remove_content_type_name'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Service',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(primary_key=True, verbose_name='ID', serialize=False, auto_created=True)),
|
||||||
|
('description', models.CharField(verbose_name='description', max_length=256, unique=True)),
|
||||||
|
('match', models.CharField(verbose_name='match', help_text="Python <a href='https://docs.python.org/2/library/functions.html#eval'>expression</a> that designates wheter a <tt>content_type</tt> object is related to this service or not, always evaluates <tt>True</tt> when left blank. Related instance can be instantiated with <tt>instance</tt> keyword or <tt>content_type.model_name</tt>.</br><tt> databaseuser.type == 'MYSQL'</tt><br><tt> miscellaneous.active and str(miscellaneous.identifier).endswith(('.org', '.net', '.com'))</tt><br><tt> contractedplan.plan.name == 'association_fee''</tt><br><tt> instance.active</tt>", max_length=256, blank=True)),
|
||||||
|
('handler_type', models.CharField(verbose_name='handler', help_text='Handler used for processing this Service. A handler enables customized behaviour far beyond what options here allow to.', max_length=256, blank=True, choices=[('', 'Default')])),
|
||||||
|
('is_active', models.BooleanField(verbose_name='active', default=True)),
|
||||||
|
('ignore_superusers', models.BooleanField(verbose_name='ignore superuser, staff and friend', help_text='Designates whether superuser, staff and friend orders are marked as ignored by default or not.', default=True)),
|
||||||
|
('billing_period', models.CharField(verbose_name='billing period', help_text='Renewal period for recurring invoicing.', blank=True, choices=[('', 'One time service'), ('MONTHLY', 'Monthly billing'), ('ANUAL', 'Anual billing')], max_length=16, default='ANUAL')),
|
||||||
|
('billing_point', models.CharField(verbose_name='billing point', help_text='Reference point for calculating the renewal date on recurring invoices', max_length=16, default='ON_FIXED_DATE', choices=[('ON_REGISTER', 'Registration date'), ('ON_FIXED_DATE', 'Fixed billing date')])),
|
||||||
|
('is_fee', models.BooleanField(verbose_name='fee', help_text='Designates whether this service should be billed as membership fee or not', default=False)),
|
||||||
|
('order_description', models.CharField(verbose_name='Order description', help_text="Python <a href='https://docs.python.org/2/library/functions.html#eval'>expression</a> used for generating the description for the bill lines of this services.<br>Defaults to <tt>'%s: %s' % (handler.description, instance)</tt>", max_length=128, blank=True)),
|
||||||
|
('ignore_period', models.CharField(verbose_name='ignore period', help_text='Period in which orders will be ignored if cancelled. Useful for designating <i>trial periods</i>', blank=True, choices=[('', 'Never'), ('ONE_DAY', 'One day'), ('TWO_DAYS', 'Two days'), ('TEN_DAYS', 'Ten days'), ('ONE_MONTH', 'One month')], max_length=16, default='TEN_DAYS')),
|
||||||
|
('metric', models.CharField(verbose_name='metric', help_text="Python <a href='https://docs.python.org/2/library/functions.html#eval'>expression</a> used for obtinging the <i>metric value</i> for the pricing rate computation. Number of orders is used when left blank. Related instance can be instantiated with <tt>instance</tt> keyword or <tt>content_type.model_name</tt>.<br><tt> max((mailbox.resources.disk.allocated or 0) -1, 0)</tt><br><tt> miscellaneous.amount</tt><br><tt> max((account.resources.traffic.used or 0) - getattr(account.miscellaneous.filter(is_active=True, service__name='traffic-prepay').last(), 'amount', 0), 0)</tt>", max_length=256, blank=True)),
|
||||||
|
('nominal_price', models.DecimalField(verbose_name='nominal price', max_digits=12, decimal_places=2)),
|
||||||
|
('tax', models.PositiveIntegerField(verbose_name='tax', choices=[(0, 'Duty free'), (21, '21%')], default=21)),
|
||||||
|
('pricing_period', models.CharField(verbose_name='pricing period', help_text='Time period that is used for computing the rate metric.', blank=True, choices=[('', 'Current value'), ('BILLING_PERIOD', 'Same as billing period'), ('MONTHLY', 'Monthly data'), ('ANUAL', 'Anual data')], max_length=16, default='BILLING_PERIOD')),
|
||||||
|
('rate_algorithm', models.CharField(verbose_name='rate algorithm', help_text='Algorithm used to interprete the rating table.<br> Step price: All price rates with a lower metric are applied.<br> Match price: Only the rate with inmediate inferior metric is applied.', max_length=16, default='STEP_PRICE', choices=[('STEP_PRICE', 'Step price'), ('MATCH_PRICE', 'Match price')])),
|
||||||
|
('on_cancel', models.CharField(verbose_name='on cancel', help_text='Defines the cancellation behaviour of this service.', max_length=16, default='DISCOUNT', choices=[('NOTHING', 'Nothing'), ('DISCOUNT', 'Discount'), ('COMPENSATE', 'Compensat'), ('REFUND', 'Refund')])),
|
||||||
|
('payment_style', models.CharField(verbose_name='payment style', help_text='Designates whether this service should be paid after consumtion (postpay/on demand) or prepaid.', max_length=16, default='PREPAY', choices=[('PREPAY', 'Prepay'), ('POSTPAY', 'Postpay (on demand)')])),
|
||||||
|
('content_type', models.ForeignKey(verbose_name='content type', help_text='Content type of the related service objects.', to='contenttypes.ContentType')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
|
@ -236,7 +236,7 @@ class Service(models.Model):
|
||||||
order_model = get_model(settings.SERVICES_ORDER_MODEL)
|
order_model = get_model(settings.SERVICES_ORDER_MODEL)
|
||||||
related_model = self.content_type.model_class()
|
related_model = self.content_type.model_class()
|
||||||
updates = []
|
updates = []
|
||||||
for instance in related_model.objects.all().select_related('account'):
|
for instance in related_model.objects.select_related('account').all():
|
||||||
updates += order_model.update_orders(instance, service=self, commit=commit)
|
updates += order_model.update_orders(instance, service=self, commit=commit)
|
||||||
return updates
|
return updates
|
||||||
|
|
||||||
|
|
|
@ -1,31 +1,30 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import models, migrations
|
from django.db import models, migrations
|
||||||
|
import orchestra.core.validators
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
import django.core.validators
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
dependencies = [
|
# dependencies = [
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
# migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
]
|
# ]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='SystemUser',
|
name='SystemUser',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')),
|
||||||
('username', models.CharField(help_text='Required. 64 characters or fewer. Letters, digits and ./-/_ only.', unique=True, max_length=64, verbose_name='username', validators=[django.core.validators.RegexValidator(b'^[\\w.-]+$', 'Enter a valid username.', b'invalid')])),
|
('username', models.CharField(validators=[orchestra.core.validators.validate_username], unique=True, help_text='Required. 64 characters or fewer. Letters, digits and ./-/_ only.', max_length=32, verbose_name='username')),
|
||||||
('password', models.CharField(max_length=128, verbose_name='password')),
|
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||||
('home', models.CharField(help_text="Home directory relative to account's ~main_user", max_length=256, verbose_name='home', blank=True)),
|
('home', models.CharField(max_length=256, help_text='Starting location when login with this no-shell user.', blank=True, verbose_name='home')),
|
||||||
('shell', models.CharField(default=b'/dev/null', max_length=32, verbose_name='shell', choices=[(b'/dev/null', 'No shell, FTP only'), (b'/bin/rssh', 'No shell, SFTP/RSYNC only'), (b'/bin/bash', b'/bin/bash'), (b'/bin/sh', b'/bin/sh')])),
|
('directory', models.CharField(max_length=256, help_text="Optional directory relative to user's home.", blank=True, verbose_name='directory')),
|
||||||
|
('shell', models.CharField(default='/dev/null', choices=[('/dev/null', 'No shell, FTP only'), ('/bin/rssh', 'No shell, SFTP/RSYNC only'), ('/bin/bash', '/bin/bash'), ('/bin/sh', '/bin/sh')], max_length=32, verbose_name='shell')),
|
||||||
('is_active', models.BooleanField(default=True, help_text='Designates whether this account should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
('is_active', models.BooleanField(default=True, help_text='Designates whether this account should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
|
||||||
('account', models.ForeignKey(related_name='systemusers', verbose_name='Account', to=settings.AUTH_USER_MODEL)),
|
# ('account', models.ForeignKey(related_name='systemusers', to=settings.AUTH_USER_MODEL, verbose_name='Account')),
|
||||||
('groups', models.ManyToManyField(help_text='A new group will be created for the user. Which additional groups would you like them to be a member of?', to='systemusers.SystemUser', blank=True)),
|
('groups', models.ManyToManyField(help_text='A new group will be created for the user. Which additional groups would you like them to be a member of?', blank=True, to='systemusers.SystemUser')),
|
||||||
],
|
],
|
||||||
options={
|
|
||||||
},
|
|
||||||
bases=(models.Model,),
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('systemusers', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='systemuser',
|
||||||
|
name='account',
|
||||||
|
field=models.ForeignKey(related_name='systemusers', to=settings.AUTH_USER_MODEL, default=1, verbose_name='Account'),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,19 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('systemusers', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='systemuser',
|
|
||||||
name='relative_to_main',
|
|
||||||
field=models.BooleanField(default=False, choices=[(True, b'Hola'), (False, b'adeu')]),
|
|
||||||
preserve_default=True,
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,29 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('systemusers', '0002_systemuser_relative_to_main'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='systemuser',
|
|
||||||
name='relative_to_main',
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='systemuser',
|
|
||||||
name='directory',
|
|
||||||
field=models.CharField(default='', max_length=256, verbose_name='directory', blank=True),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='systemuser',
|
|
||||||
name='home',
|
|
||||||
field=models.CharField(help_text='This will be your starting location when you login with this sftp user.', max_length=256, verbose_name='home'),
|
|
||||||
preserve_default=True,
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -91,7 +91,7 @@ class SystemUser(models.Model):
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'directory': directory_error,
|
'directory': directory_error,
|
||||||
})
|
})
|
||||||
if self.has_shell and self.home != self.get_base_home():
|
if self.has_shell and self.home and self.home != self.get_base_home():
|
||||||
raise ValidationError({
|
raise ValidationError({
|
||||||
'home': _("Shell users should use their own home."),
|
'home': _("Shell users should use their own home."),
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
import orchestra.core.validators
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='VPS',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(verbose_name='ID', serialize=False, primary_key=True, auto_created=True)),
|
||||||
|
('hostname', models.CharField(verbose_name='hostname', max_length=256, validators=[orchestra.core.validators.validate_hostname], unique=True)),
|
||||||
|
('type', models.CharField(default='openvz', verbose_name='type', max_length=64, choices=[('openvz', 'OpenVZ container')])),
|
||||||
|
('template', models.CharField(default='debian7', verbose_name='template', max_length=64, choices=[('debian7', 'Debian 7 - Wheezy')])),
|
||||||
|
('password', models.CharField(verbose_name='password', help_text='<TT>root</TT> password of this virtual machine', max_length=128)),
|
||||||
|
('account', models.ForeignKey(to=settings.AUTH_USER_MODEL, verbose_name='Account', related_name='vpss')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'VPS',
|
||||||
|
'verbose_name_plural': 'VPSs',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
|
@ -42,7 +42,7 @@ class WebAppServiceMixin(object):
|
||||||
'under_construction_path': settings.settings.WEBAPPS_UNDER_CONSTRUCTION_PATH,
|
'under_construction_path': settings.settings.WEBAPPS_UNDER_CONSTRUCTION_PATH,
|
||||||
'is_mounted': webapp.content_set.exists(),
|
'is_mounted': webapp.content_set.exists(),
|
||||||
}
|
}
|
||||||
replace(context, "'", '"')
|
return replace(context, "'", '"')
|
||||||
|
|
||||||
|
|
||||||
for __, module_name, __ in pkgutil.walk_packages(__path__):
|
for __, module_name, __ in pkgutil.walk_packages(__path__):
|
||||||
|
|
|
@ -179,6 +179,7 @@ class PHPBackend(WebAppServiceMixin, ServiceController):
|
||||||
'cmd_options': self.get_fcgid_cmd_options(webapp, context),
|
'cmd_options': self.get_fcgid_cmd_options(webapp, context),
|
||||||
'cmd_options_path': settings.WEBAPPS_FCGID_CMD_OPTIONS_PATH % context,
|
'cmd_options_path': settings.WEBAPPS_FCGID_CMD_OPTIONS_PATH % context,
|
||||||
})
|
})
|
||||||
|
return context
|
||||||
|
|
||||||
def update_fpm_context(self, webapp, context):
|
def update_fpm_context(self, webapp, context):
|
||||||
context.update({
|
context.update({
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import models, migrations
|
from django.db import models, migrations
|
||||||
|
import jsonfield.fields
|
||||||
import orchestra.core.validators
|
import orchestra.core.validators
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
|
||||||
|
@ -15,30 +17,29 @@ class Migration(migrations.Migration):
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='WebApp',
|
name='WebApp',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
('id', models.AutoField(auto_created=True, primary_key=True, verbose_name='ID', serialize=False)),
|
||||||
('name', models.CharField(max_length=128, verbose_name='name', validators=[orchestra.core.validators.validate_name])),
|
('name', models.CharField(validators=[orchestra.core.validators.validate_name], verbose_name='name', max_length=128)),
|
||||||
('type', models.CharField(max_length=32, verbose_name='type', choices=[(b'dokuwiki-mu', b'DokuWiki (SaaS)'), (b'drupal-mu', b'Drupdal (SaaS)'), (b'php4-fcgi', b'PHP 4 FCGI'), (b'php5.2-fcgi', b'PHP 5.2 FCGI'), (b'php5.5-fpm', b'PHP 5.5 FPM'), (b'static', b'Static'), (b'symlink', b'Symbolic link'), (b'webalizer', b'Webalizer'), (b'wordpress', b'WordPress'), (b'wordpress-mu', b'WordPress (SaaS)')])),
|
('type', models.CharField(verbose_name='type', choices=[('php', 'PHP'), ('static', 'Static'), ('symbolic-link', 'Symbolic link'), ('webalizer', 'Webalizer'), ('wordpress-php', 'WordPress')], max_length=32)),
|
||||||
('account', models.ForeignKey(related_name='webapps', verbose_name='Account', to=settings.AUTH_USER_MODEL)),
|
('data', jsonfield.fields.JSONField(blank=True, verbose_name='data', help_text='Extra information dependent of each service.', default={})),
|
||||||
|
('account', models.ForeignKey(to=settings.AUTH_USER_MODEL, verbose_name='Account', related_name='webapps')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Web App',
|
|
||||||
'verbose_name_plural': 'Web Apps',
|
'verbose_name_plural': 'Web Apps',
|
||||||
|
'verbose_name': 'Web App',
|
||||||
},
|
},
|
||||||
bases=(models.Model,),
|
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='WebAppOption',
|
name='WebAppOption',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
('id', models.AutoField(auto_created=True, primary_key=True, verbose_name='ID', serialize=False)),
|
||||||
('name', models.CharField(max_length=128, verbose_name='name', choices=[(b'PHP-allow_url_fopen', 'PHP - allow_url_fopen'), (b'PHP-allow_url_include', 'PHP - Allow URL include'), (b'PHP-auto_append_file', 'PHP - Auto append file'), (b'PHP-auto_prepend_file', 'PHP - Auto prepend file'), (b'PHP-date.timezone', 'PHP - date.timezone'), (b'PHP-default_socket_timeout', 'PHP - Default socket timeout'), (b'PHP-display_errors', 'PHP - Display errors'), (b'PHP-extension', 'PHP - Extension'), (b'PHP-magic_quotes_gpc', 'PHP - Magic quotes GPC'), (b'PHP-magic_quotes_runtime', 'PHP - Magic quotes runtime'), (b'PHP-magic_quotes_sybase', 'PHP - Magic quotes sybase'), (b'PHP-max_execution_time', 'PHP - Max execution time'), (b'PHP-max_input_time', 'PHP - Max input time'), (b'PHP-max_input_vars', 'PHP - Max input vars'), (b'PHP-memory_limit', 'PHP - Memory limit'), (b'PHP-mysql.connect_timeout', 'PHP - Mysql connect timeout'), (b'PHP-output_buffering', 'PHP - output_buffering'), (b'PHP-post_max_size', 'PHP - Post max size'), (b'PHP-register_globals', 'PHP - Register globals'), (b'PHP-safe_mode', 'PHP - Safe mode'), (b'PHP-sendmail_path', 'PHP - sendmail_path'), (b'PHP-session.auto_start', 'PHP - session.auto_start'), (b'PHP-session.bug_compat_warn', 'PHP - session.bug_compat_warn'), (b'PHP-suhosin.executor.include.whitelist', 'PHP - suhosin.executor.include.whitelist'), (b'PHP-suhosin.get.max_vars', 'PHP - Suhosin GET max vars'), (b'PHP-suhosin.post.max_vars', 'PHP - Suhosin POST max vars'), (b'PHP-suhosin.request.max_vars', 'PHP - Suhosin request max vars'), (b'PHP-suhosin.session.encrypt', 'PHP - suhosin.session.encrypt'), (b'PHP-suhosin.simulation', 'PHP - Suhosin simulation'), (b'PHP-upload_max_filesize', 'PHP - upload_max_filesize'), (b'PHP-zend_extension', 'PHP - zend_extension'), (b'php-enabled_functions', 'PHP - Enabled functions'), (b'processes', 'Number of processes'), (b'public-root', 'Public root'), (b'timeout', 'Process timeout')])),
|
('name', models.CharField(verbose_name='name', choices=[(None, '-------'), ('FileSystem', [('public-root', 'Public root')]), ('Process', [('timeout', 'Process timeout'), ('processes', 'Number of processes')]), ('PHP', [('enabled_functions', 'Enabled functions'), ('allow_url_include', 'Allow URL include'), ('allow_url_fopen', 'Allow URL fopen'), ('auto_append_file', 'Auto append file'), ('auto_prepend_file', 'Auto prepend file'), ('date.timezone', 'date.timezone'), ('default_socket_timeout', 'Default socket timeout'), ('display_errors', 'Display errors'), ('extension', 'Extension'), ('magic_quotes_gpc', 'Magic quotes GPC'), ('magic_quotes_runtime', 'Magic quotes runtime'), ('magic_quotes_sybase', 'Magic quotes sybase'), ('max_execution_time', 'Max execution time'), ('max_input_time', 'Max input time'), ('max_input_vars', 'Max input vars'), ('memory_limit', 'Memory limit'), ('mysql.connect_timeout', 'Mysql connect timeout'), ('output_buffering', 'Output buffering'), ('register_globals', 'Register globals'), ('post_max_size', 'Post max size'), ('sendmail_path', 'sendmail_path'), ('session.bug_compat_warn', 'session.bug_compat_warn'), ('session.auto_start', 'session.auto_start'), ('safe_mode', 'Safe mode'), ('suhosin.post.max_vars', 'Suhosin POST max vars'), ('suhosin.get.max_vars', 'Suhosin GET max vars'), ('suhosin.request.max_vars', 'Suhosin request max vars'), ('suhosin.session.encrypt', 'suhosin.session.encrypt'), ('suhosin.simulation', 'Suhosin simulation'), ('suhosin.executor.include.whitelist', 'suhosin.executor.include.whitelist'), ('upload_max_filesize', 'upload_max_filesize'), ('zend_extension', 'Zend extension')])], max_length=128)),
|
||||||
('value', models.CharField(max_length=256, verbose_name='value')),
|
('value', models.CharField(max_length=256, verbose_name='value')),
|
||||||
('webapp', models.ForeignKey(related_name='options', verbose_name='Web application', to='webapps.WebApp')),
|
('webapp', models.ForeignKey(to='webapps.WebApp', verbose_name='Web application', related_name='options')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'option',
|
|
||||||
'verbose_name_plural': 'options',
|
'verbose_name_plural': 'options',
|
||||||
|
'verbose_name': 'option',
|
||||||
},
|
},
|
||||||
bases=(models.Model,),
|
|
||||||
),
|
),
|
||||||
migrations.AlterUniqueTogether(
|
migrations.AlterUniqueTogether(
|
||||||
name='webappoption',
|
name='webappoption',
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
import jsonfield.fields
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('webapps', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='webapp',
|
|
||||||
name='data',
|
|
||||||
field=jsonfield.fields.JSONField(default={}, help_text='Extra information dependent of each service.', verbose_name='data'),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,25 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('webapps', '0002_webapp_data'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='webapp',
|
|
||||||
name='type',
|
|
||||||
field=models.CharField(max_length=32, verbose_name='type', choices=[(b'dokuwiki-mu', b'DokuWiki (SaaS)'), (b'drupal-mu', b'Drupdal (SaaS)'), (b'php4-fcgid', b'PHP 4 FCGID'), (b'php5.2-fcgid', b'PHP 5.2 FCGID'), (b'php5.3-fcgid', b'PHP 5.3 FCGID'), (b'php5.4-fpm', b'PHP 5.4 FPM'), (b'static', b'Static'), (b'symbolic-link', b'Symbolic link'), (b'webalizer', b'Webalizer'), (b'wordpress', b'WordPress'), (b'wordpress-mu', b'WordPress (SaaS)')]),
|
|
||||||
preserve_default=True,
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='webappoption',
|
|
||||||
name='name',
|
|
||||||
field=models.CharField(max_length=128, verbose_name='name', choices=[(None, b'-------'), (b'FileSystem', [(b'public-root', 'Public root')]), (b'Process', [(b'timeout', 'Process timeout'), (b'processes', 'Number of processes')]), (b'PHP', [(b'enabled_functions', 'Enabled functions'), (b'allow_url_include', 'Allow URL include'), (b'allow_url_fopen', 'Allow URL fopen'), (b'auto_append_file', 'Auto append file'), (b'auto_prepend_file', 'Auto prepend file'), (b'date.timezone', 'date.timezone'), (b'default_socket_timeout', 'Default socket timeout'), (b'display_errors', 'Display errors'), (b'extension', 'Extension'), (b'magic_quotes_gpc', 'Magic quotes GPC'), (b'magic_quotes_runtime', 'Magic quotes runtime'), (b'magic_quotes_sybase', 'Magic quotes sybase'), (b'max_execution_time', 'Max execution time'), (b'max_input_time', 'Max input time'), (b'max_input_vars', 'Max input vars'), (b'memory_limit', 'Memory limit'), (b'mysql.connect_timeout', 'Mysql connect timeout'), (b'output_buffering', 'Output buffering'), (b'register_globals', 'Register globals'), (b'post_max_size', 'zend_extension'), (b'sendmail_path', 'sendmail_path'), (b'session.bug_compat_warn', 'session.bug_compat_warn'), (b'session.auto_start', 'session.auto_start'), (b'safe_mode', 'Safe mode'), (b'suhosin.post.max_vars', 'Suhosin POST max vars'), (b'suhosin.get.max_vars', 'Suhosin GET max vars'), (b'suhosin.request.max_vars', 'Suhosin request max vars'), (b'suhosin.session.encrypt', 'suhosin.session.encrypt'), (b'suhosin.simulation', 'Suhosin simulation'), (b'suhosin.executor.include.whitelist', 'suhosin.executor.include.whitelist'), (b'upload_max_filesize', 'upload_max_filesize'), (b'post_max_size', 'zend_extension')])]),
|
|
||||||
preserve_default=True,
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,6 +1,5 @@
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
from django.contrib.contenttypes.fields import GenericRelation
|
from django.contrib.contenttypes.fields import GenericRelation
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
|
|
||||||
from orchestra.utils import database_ready
|
from orchestra.utils import database_ready
|
||||||
|
|
||||||
|
@ -10,6 +9,7 @@ class WebsiteConfig(AppConfig):
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
if database_ready():
|
if database_ready():
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from .models import Content
|
from .models import Content
|
||||||
qset = Content.content_type.field.get_limit_choices_to()
|
qset = Content.content_type.field.get_limit_choices_to()
|
||||||
for ct in ContentType.objects.filter(qset):
|
for ct in ContentType.objects.filter(qset):
|
||||||
|
|
|
@ -228,26 +228,6 @@ class Apache2Backend(ServiceController):
|
||||||
directive = settings.WEBSITES_SAAS_DIRECTIVES[name]
|
directive = settings.WEBSITES_SAAS_DIRECTIVES[name]
|
||||||
saas += self.get_directives(directive, context)
|
saas += self.get_directives(directive, context)
|
||||||
return saas
|
return saas
|
||||||
# def get_protections(self, site):
|
|
||||||
# protections = ''
|
|
||||||
# context = self.get_context(site)
|
|
||||||
# for protection in site.directives.filter(name='directory_protection'):
|
|
||||||
# path, name, passwd = protection.value.split()
|
|
||||||
# path = os.path.join(context['root'], path)
|
|
||||||
# passwd = os.path.join(self.USER_HOME % context, passwd)
|
|
||||||
# protections += textwrap.dedent("""
|
|
||||||
# <Directory %s>
|
|
||||||
# AllowOverride All
|
|
||||||
# #AuthPAM_Enabled off
|
|
||||||
# AuthType Basic
|
|
||||||
# AuthName %s
|
|
||||||
# AuthUserFile %s
|
|
||||||
# <Limit GET POST>
|
|
||||||
# require valid-user
|
|
||||||
# </Limit>
|
|
||||||
# </Directory>""" % (path, name, passwd)
|
|
||||||
# )
|
|
||||||
# return protections
|
|
||||||
|
|
||||||
def enable_or_disable(self, site):
|
def enable_or_disable(self, site):
|
||||||
context = self.get_context(site)
|
context = self.get_context(site)
|
||||||
|
@ -311,6 +291,10 @@ class Apache2Backend(ServiceController):
|
||||||
|
|
||||||
|
|
||||||
class Apache2Traffic(ServiceMonitor):
|
class Apache2Traffic(ServiceMonitor):
|
||||||
|
"""
|
||||||
|
Parses apache logs,
|
||||||
|
looking for the size of each request on the last word of the log line
|
||||||
|
"""
|
||||||
model = 'websites.Website'
|
model = 'websites.Website'
|
||||||
resource = ServiceMonitor.TRAFFIC
|
resource = ServiceMonitor.TRAFFIC
|
||||||
verbose_name = _("Apache 2 Traffic")
|
verbose_name = _("Apache 2 Traffic")
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import models, migrations
|
||||||
|
import orchestra.core.validators
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('webapps', '0001_initial'),
|
||||||
|
('domains', '0001_initial'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Content',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(verbose_name='ID', auto_created=True, serialize=False, primary_key=True)),
|
||||||
|
('path', models.CharField(verbose_name='path', blank=True, max_length=256, validators=[orchestra.core.validators.validate_url_path])),
|
||||||
|
('webapp', models.ForeignKey(to='webapps.WebApp', verbose_name='web application')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Website',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(verbose_name='ID', auto_created=True, serialize=False, primary_key=True)),
|
||||||
|
('name', models.CharField(verbose_name='name', max_length=128, validators=[orchestra.core.validators.validate_name])),
|
||||||
|
('protocol', models.CharField(default='http', verbose_name='protocol', max_length=16, help_text='Select the protocol(s) for this website<br><tt>HTTPS only</tt> performs a redirection from <tt>http</tt> to <tt>https</tt>.', choices=[('http', 'HTTP'), ('https', 'HTTPS'), ('http/https', 'HTTP and HTTPS'), ('https-only', 'HTTPS only')])),
|
||||||
|
('is_active', models.BooleanField(verbose_name='active', default=True)),
|
||||||
|
('account', models.ForeignKey(to=settings.AUTH_USER_MODEL, related_name='websites', verbose_name='Account')),
|
||||||
|
('contents', models.ManyToManyField(to='webapps.WebApp', through='websites.Content')),
|
||||||
|
('domains', models.ManyToManyField(to='domains.Domain', verbose_name='domains', related_name='websites')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='WebsiteDirective',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(verbose_name='ID', auto_created=True, serialize=False, primary_key=True)),
|
||||||
|
('name', models.CharField(verbose_name='name', max_length=128, choices=[(None, '-------'), ('SaaS', [('wordpress-saas', 'WordPress SaaS'), ('dokuwiki-saas', 'DokuWiki SaaS'), ('drupal-saas', 'Drupdal SaaS')]), ('ModSecurity', [('sec-rule-remove', 'SecRuleRemoveById'), ('sec-engine', 'SecRuleEngine Off')]), ('HTTPD', [('redirect', 'Redirection'), ('proxy', 'Proxy'), ('error-document', 'ErrorDocumentRoot')]), ('SSL', [('ssl-ca', 'SSL CA'), ('ssl-cert', 'SSL cert'), ('ssl-key', 'SSL key')])])),
|
||||||
|
('value', models.CharField(verbose_name='value', max_length=256)),
|
||||||
|
('website', models.ForeignKey(to='websites.Website', related_name='directives', verbose_name='web site')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='content',
|
||||||
|
name='website',
|
||||||
|
field=models.ForeignKey(to='websites.Website', verbose_name='web site'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='website',
|
||||||
|
unique_together=set([('name', 'account')]),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='content',
|
||||||
|
unique_together=set([('website', 'path')]),
|
||||||
|
),
|
||||||
|
]
|
|
@ -22,7 +22,7 @@ class MultiSelectField(models.CharField, metaclass=models.SubfieldBase):
|
||||||
def get_db_prep_value(self, value, connection=None, prepared=False):
|
def get_db_prep_value(self, value, connection=None, prepared=False):
|
||||||
if isinstance(value, str):
|
if isinstance(value, str):
|
||||||
return value
|
return value
|
||||||
elif isinstance(value, list):
|
else:
|
||||||
return ','.join(value)
|
return ','.join(value)
|
||||||
|
|
||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
|
@ -49,7 +49,6 @@ class MultiSelectField(models.CharField, metaclass=models.SubfieldBase):
|
||||||
if (opt_select not in arr_choices):
|
if (opt_select not in arr_choices):
|
||||||
msg = self.error_messages['invalid_choice'] % value
|
msg = self.error_messages['invalid_choice'] % value
|
||||||
raise exceptions.ValidationError(msg)
|
raise exceptions.ValidationError(msg)
|
||||||
return
|
|
||||||
|
|
||||||
def get_choices_selected(self, arr_choices=''):
|
def get_choices_selected(self, arr_choices=''):
|
||||||
if not arr_choices:
|
if not arr_choices:
|
||||||
|
|
|
@ -70,8 +70,8 @@ def runiterator(command, display=False, error_codes=[0], silent=False, stdin='',
|
||||||
stdoutPiece = read_async(p.stdout)
|
stdoutPiece = read_async(p.stdout)
|
||||||
stderrPiece = read_async(p.stderr)
|
stderrPiece = read_async(p.stderr)
|
||||||
|
|
||||||
stdout += (stdoutPiece or b'').decode('utf8')
|
stdout += (stdoutPiece or b'').decode('utf8', errors='replace')
|
||||||
stderr += (stderrPiece or b'').decode('utf8')
|
stderr += (stderrPiece or b'').decode('utf8', errors='replace')
|
||||||
|
|
||||||
if display and stdout:
|
if display and stdout:
|
||||||
sys.stdout.write(stdout)
|
sys.stdout.write(stdout)
|
||||||
|
|
Loading…
Reference in New Issue