Random fixes
This commit is contained in:
parent
d0c7c760af
commit
c4e8c07311
3
TODO.md
3
TODO.md
|
@ -160,3 +160,6 @@ Remember that, as always with QuerySets, any subsequent chained methods which im
|
||||||
* prevent adding local email addresses on account.contacts account.email
|
* prevent adding local email addresses on account.contacts account.email
|
||||||
|
|
||||||
* Resource monitoring without ROUTE alert or explicit error
|
* Resource monitoring without ROUTE alert or explicit error
|
||||||
|
|
||||||
|
|
||||||
|
* Domain validation has to be done with injected records and subdomains
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
import django.utils.timezone
|
|
||||||
import django.core.validators
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('systemusers', '__first__'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Account',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
|
||||||
('password', models.CharField(max_length=128, verbose_name='password')),
|
|
||||||
('last_login', models.DateTimeField(default=django.utils.timezone.now, verbose_name='last login')),
|
|
||||||
('username', models.CharField(help_text='Required. 30 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')])),
|
|
||||||
('first_name', models.CharField(max_length=30, verbose_name='first name', blank=True)),
|
|
||||||
('last_name', models.CharField(max_length=30, verbose_name='last name', blank=True)),
|
|
||||||
('email', models.EmailField(help_text='Used for password recovery', max_length=75, verbose_name='email address')),
|
|
||||||
('type', models.CharField(default=b'INDIVIDUAL', max_length=32, verbose_name='type', choices=[(b'INDIVIDUAL', 'Individual'), (b'ASSOCIATION', 'Association'), (b'CUSTOMER', 'Customer'), (b'STAFF', 'Staff')])),
|
|
||||||
('language', models.CharField(default=b'ca', max_length=2, verbose_name='language', choices=[(b'ca', 'Catalan'), (b'es', 'Spanish'), (b'en', 'English')])),
|
|
||||||
('comments', models.TextField(max_length=256, verbose_name='comments', blank=True)),
|
|
||||||
('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', to='systemusers.SystemUser', null=True)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
bases=(models.Model,),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,34 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('accounts', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='account',
|
|
||||||
name='first_name',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='account',
|
|
||||||
name='last_name',
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='account',
|
|
||||||
name='full_name',
|
|
||||||
field=models.CharField(default='', max_length=30, verbose_name='full name'),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='account',
|
|
||||||
name='short_name',
|
|
||||||
field=models.CharField(default='', max_length=30, verbose_name='short name', blank=True),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -14,13 +14,13 @@ from . import settings
|
||||||
|
|
||||||
class Account(auth.AbstractBaseUser):
|
class Account(auth.AbstractBaseUser):
|
||||||
username = models.CharField(_("username"), max_length=64, unique=True,
|
username = models.CharField(_("username"), max_length=64, unique=True,
|
||||||
help_text=_("Required. 30 characters or fewer. Letters, digits and ./-/_ only."),
|
help_text=_("Required. 64 characters or fewer. Letters, digits and ./-/_ only."),
|
||||||
validators=[validators.RegexValidator(r'^[\w.-]+$',
|
validators=[validators.RegexValidator(r'^[\w.-]+$',
|
||||||
_("Enter a valid username."), 'invalid')])
|
_("Enter a valid username."), 'invalid')])
|
||||||
main_systemuser = models.ForeignKey(settings.ACCOUNTS_SYSTEMUSER_MODEL, null=True,
|
main_systemuser = models.ForeignKey(settings.ACCOUNTS_SYSTEMUSER_MODEL, null=True,
|
||||||
related_name='accounts_main')
|
related_name='accounts_main', editable=False)
|
||||||
short_name = models.CharField(_("short name"), max_length=30, blank=True)
|
short_name = models.CharField(_("short name"), max_length=64, blank=True)
|
||||||
full_name = models.CharField(_("full name"), max_length=30)
|
full_name = models.CharField(_("full name"), max_length=256)
|
||||||
email = models.EmailField(_('email address'), help_text=_("Used for password recovery"))
|
email = models.EmailField(_('email address'), help_text=_("Used for password recovery"))
|
||||||
type = models.CharField(_("type"), choices=settings.ACCOUNTS_TYPES,
|
type = models.CharField(_("type"), choices=settings.ACCOUNTS_TYPES,
|
||||||
max_length=32, default=settings.ACCOUNTS_DEFAULT_TYPE)
|
max_length=32, default=settings.ACCOUNTS_DEFAULT_TYPE)
|
||||||
|
|
|
@ -15,7 +15,7 @@ ACCOUNTS_DEFAULT_TYPE = getattr(settings, 'ACCOUNTS_DEFAULT_TYPE', 'INDIVIDUAL')
|
||||||
|
|
||||||
|
|
||||||
ACCOUNTS_LANGUAGES = getattr(settings, 'ACCOUNTS_LANGUAGES', (
|
ACCOUNTS_LANGUAGES = getattr(settings, 'ACCOUNTS_LANGUAGES', (
|
||||||
('en', _('English')),
|
('EN', _('English')),
|
||||||
))
|
))
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ ACCOUNTS_SYSTEMUSER_MODEL = getattr(settings, 'ACCOUNTS_SYSTEMUSER_MODEL',
|
||||||
'systemusers.SystemUser')
|
'systemusers.SystemUser')
|
||||||
|
|
||||||
|
|
||||||
ACCOUNTS_DEFAULT_LANGUAGE = getattr(settings, 'ACCOUNTS_DEFAULT_LANGUAGE', 'en')
|
ACCOUNTS_DEFAULT_LANGUAGE = getattr(settings, 'ACCOUNTS_DEFAULT_LANGUAGE', 'EN')
|
||||||
|
|
||||||
|
|
||||||
ACCOUNTS_MAIN_PK = getattr(settings, 'ACCOUNTS_MAIN_PK', 1)
|
ACCOUNTS_MAIN_PK = getattr(settings, 'ACCOUNTS_MAIN_PK', 1)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
|
|
||||||
from django.core.validators import ValidationError
|
from django.core.validators import ValidationError, RegexValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.template import loader, Context
|
from django.template import loader, Context
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
@ -10,7 +10,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.apps.accounts.models import Account
|
from orchestra.apps.accounts.models import Account
|
||||||
from orchestra.apps.contacts.models import Contact
|
from orchestra.apps.contacts.models import Contact
|
||||||
from orchestra.core import accounts
|
from orchestra.core import accounts, validators
|
||||||
from orchestra.utils.html import html_to_pdf
|
from orchestra.utils.html import html_to_pdf
|
||||||
|
|
||||||
from . import settings
|
from . import settings
|
||||||
|
@ -24,8 +24,11 @@ class BillContact(models.Model):
|
||||||
address = models.TextField(_("address"))
|
address = models.TextField(_("address"))
|
||||||
city = models.CharField(_("city"), max_length=128,
|
city = models.CharField(_("city"), max_length=128,
|
||||||
default=settings.BILLS_CONTACT_DEFAULT_CITY)
|
default=settings.BILLS_CONTACT_DEFAULT_CITY)
|
||||||
zipcode = models.PositiveIntegerField(_("zip code"))
|
zipcode = models.CharField(_("zip code"), max_length=10,
|
||||||
|
validators=[RegexValidator(r'^[0-9A-Z]{3,10}$',
|
||||||
|
_("Enter a valid zipcode."), 'invalid')])
|
||||||
country = models.CharField(_("country"), max_length=20,
|
country = models.CharField(_("country"), max_length=20,
|
||||||
|
choices=settings.BILLS_CONTACT_COUNTRIES,
|
||||||
default=settings.BILLS_CONTACT_DEFAULT_COUNTRY)
|
default=settings.BILLS_CONTACT_DEFAULT_COUNTRY)
|
||||||
vat = models.CharField(_("VAT number"), max_length=64)
|
vat = models.CharField(_("VAT number"), max_length=64)
|
||||||
|
|
||||||
|
@ -34,6 +37,12 @@ class BillContact(models.Model):
|
||||||
|
|
||||||
def get_name(self):
|
def get_name(self):
|
||||||
return self.name or self.account.get_full_name()
|
return self.name or self.account.get_full_name()
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
self.vat = self.vat.strip()
|
||||||
|
self.city = self.city.strip()
|
||||||
|
validators.validate_vat(self.vat, self.country)
|
||||||
|
validators.validate_zipcode(self.zipcode, self.country)
|
||||||
|
|
||||||
|
|
||||||
class BillManager(models.Manager):
|
class BillManager(models.Manager):
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django_countries import data
|
||||||
|
|
||||||
|
|
||||||
BILLS_NUMBER_LENGTH = getattr(settings, 'BILLS_NUMBER_LENGTH', 4)
|
BILLS_NUMBER_LENGTH = getattr(settings, 'BILLS_NUMBER_LENGTH', 4)
|
||||||
|
@ -57,4 +58,9 @@ BILLS_ORDER_MODEL = getattr(settings, 'BILLS_ORDER_MODEL', 'orders.Order')
|
||||||
BILLS_CONTACT_DEFAULT_CITY = getattr(settings, 'BILLS_CONTACT_DEFAULT_CITY', 'Barcelona')
|
BILLS_CONTACT_DEFAULT_CITY = getattr(settings, 'BILLS_CONTACT_DEFAULT_CITY', 'Barcelona')
|
||||||
|
|
||||||
|
|
||||||
BILLS_CONTACT_DEFAULT_COUNTRY = getattr(settings, 'BILLS_CONTACT_DEFAULT_COUNTRY', 'Spain')
|
BILLS_CONTACT_COUNTRIES = getattr(settings, 'BILLS_CONTACT_COUNTRIES', data.COUNTRIES)
|
||||||
|
|
||||||
|
|
||||||
|
BILLS_CONTACT_DEFAULT_COUNTRY = getattr(settings, 'BILLS_CONTACT_DEFAULT_COUNTRY', 'ES')
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.core.validators import RegexValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.core import accounts
|
from orchestra.core import accounts, validators
|
||||||
from orchestra.models.fields import MultiSelectField
|
from orchestra.models.fields import MultiSelectField
|
||||||
|
|
||||||
from . import settings
|
from . import settings
|
||||||
|
|
||||||
|
|
||||||
|
validate_phone = lambda p: validators.validate_phone(p, settings.CONTACTS_DEFAULT_COUNTRY)
|
||||||
|
|
||||||
|
|
||||||
class ContactQuerySet(models.QuerySet):
|
class ContactQuerySet(models.QuerySet):
|
||||||
def filter(self, *args, **kwargs):
|
def filter(self, *args, **kwargs):
|
||||||
usages = kwargs.pop('email_usages', [])
|
usages = kwargs.pop('email_usages', [])
|
||||||
|
@ -37,14 +42,17 @@ class Contact(models.Model):
|
||||||
email_usage = MultiSelectField(_("email usage"), max_length=256, blank=True,
|
email_usage = MultiSelectField(_("email usage"), max_length=256, blank=True,
|
||||||
choices=EMAIL_USAGES,
|
choices=EMAIL_USAGES,
|
||||||
default=settings.CONTACTS_DEFAULT_EMAIL_USAGES)
|
default=settings.CONTACTS_DEFAULT_EMAIL_USAGES)
|
||||||
phone = models.CharField(_("phone"), max_length=32, blank=True)
|
phone = models.CharField(_("phone"), max_length=32, blank=True,
|
||||||
phone2 = models.CharField(_("alternative phone"), max_length=32, blank=True)
|
validators=[validate_phone])
|
||||||
|
phone2 = models.CharField(_("alternative phone"), max_length=32, blank=True,
|
||||||
|
validators=[validate_phone])
|
||||||
address = models.TextField(_("address"), blank=True)
|
address = models.TextField(_("address"), blank=True)
|
||||||
city = models.CharField(_("city"), max_length=128, blank=True,
|
city = models.CharField(_("city"), max_length=128, blank=True)
|
||||||
default=settings.CONTACTS_DEFAULT_CITY)
|
zipcode = models.CharField(_("zip code"), max_length=10, blank=True,
|
||||||
zipcode = models.PositiveIntegerField(_("zip code"), null=True, blank=True)
|
validators=[RegexValidator(r'^[0-9,A-Z]{3,10}$',
|
||||||
|
_("Enter a valid zipcode."), 'invalid')])
|
||||||
country = models.CharField(_("country"), max_length=20, blank=True,
|
country = models.CharField(_("country"), max_length=20, blank=True,
|
||||||
default=settings.CONTACTS_DEFAULT_COUNTRY)
|
choices=settings.CONTACTS_COUNTRIES)
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return self.short_name
|
return self.short_name
|
||||||
|
@ -57,6 +65,12 @@ class Contact(models.Model):
|
||||||
self.address = self.address.strip()
|
self.address = self.address.strip()
|
||||||
self.city = self.city.strip()
|
self.city = self.city.strip()
|
||||||
self.country = self.country.strip()
|
self.country = self.country.strip()
|
||||||
|
if self.address and not (self.city and self.zipcode and self.country):
|
||||||
|
raise ValidationError(_("City, zipcode and country must be provided when address is provided."))
|
||||||
|
if self.zipcode and not self.country:
|
||||||
|
raise ValidationError(_("Country must be provided when zipcode is provided."))
|
||||||
|
elif self.zipcode and self.country:
|
||||||
|
validators.validate_zipcode(self.zipcode, self.country)
|
||||||
|
|
||||||
|
|
||||||
accounts.register(Contact)
|
accounts.register(Contact)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django_countries import data
|
||||||
|
|
||||||
|
|
||||||
CONTACTS_DEFAULT_EMAIL_USAGES = getattr(settings, 'CONTACTS_DEFAULT_EMAIL_USAGES',
|
CONTACTS_DEFAULT_EMAIL_USAGES = getattr(settings, 'CONTACTS_DEFAULT_EMAIL_USAGES',
|
||||||
|
@ -9,7 +10,7 @@ CONTACTS_DEFAULT_EMAIL_USAGES = getattr(settings, 'CONTACTS_DEFAULT_EMAIL_USAGES
|
||||||
CONTACTS_DEFAULT_CITY = getattr(settings, 'CONTACTS_DEFAULT_CITY', 'Barcelona')
|
CONTACTS_DEFAULT_CITY = getattr(settings, 'CONTACTS_DEFAULT_CITY', 'Barcelona')
|
||||||
|
|
||||||
|
|
||||||
CONTACTS_DEFAULT_PROVINCE = getattr(settings, 'CONTACTS_DEFAULT_PROVINCE', 'Barcelona')
|
CONTACTS_COUNTRIES = getattr(settings, 'CONTACTS_COUNTRIES', data.COUNTRIES)
|
||||||
|
|
||||||
|
|
||||||
CONTACTS_DEFAULT_COUNTRY = getattr(settings, 'CONTACTS_DEFAULT_COUNTRY', 'Spain')
|
CONTACTS_DEFAULT_COUNTRY = getattr(settings, 'CONTACTS_DEFAULT_COUNTRY', 'ES')
|
||||||
|
|
|
@ -2,8 +2,7 @@ from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from orchestra.core import services
|
from orchestra.core import services
|
||||||
from orchestra.core.validators import (validate_ipv4_address, validate_ipv6_address,
|
from orchestra.core.validators import validate_ipv4_address, validate_ipv6_address, validate_ascii
|
||||||
validate_hostname, validate_ascii)
|
|
||||||
from orchestra.utils.python import AttrDict
|
from orchestra.utils.python import AttrDict
|
||||||
|
|
||||||
from . import settings, validators, utils
|
from . import settings, validators, utils
|
||||||
|
@ -11,11 +10,11 @@ from . import settings, validators, utils
|
||||||
|
|
||||||
class Domain(models.Model):
|
class Domain(models.Model):
|
||||||
name = models.CharField(_("name"), max_length=256, unique=True,
|
name = models.CharField(_("name"), max_length=256, unique=True,
|
||||||
validators=[validate_hostname, validators.validate_allowed_domain],
|
validators=[validators.validate_domain_name, validators.validate_allowed_domain],
|
||||||
help_text=_("Domain or subdomain name."))
|
help_text=_("Domain or subdomain name."))
|
||||||
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
|
account = models.ForeignKey('accounts.Account', verbose_name=_("Account"),
|
||||||
related_name='domains', blank=True, help_text=_("Automatically selected for subdomains."))
|
related_name='domains', blank=True, help_text=_("Automatically selected for subdomains."))
|
||||||
top = models.ForeignKey('domains.Domain', null=True, related_name='subdomains')
|
top = models.ForeignKey('domains.Domain', null=True, related_name='subdomains', editable=False)
|
||||||
serial = models.IntegerField(_("serial"), default=utils.generate_zone_serial,
|
serial = models.IntegerField(_("serial"), default=utils.generate_zone_serial,
|
||||||
help_text=_("Serial number"))
|
help_text=_("Serial number"))
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import re
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from orchestra.core.validators import validate_hostname
|
||||||
from orchestra.utils import paths
|
from orchestra.utils import paths
|
||||||
from orchestra.utils.system import run
|
from orchestra.utils.system import run
|
||||||
|
|
||||||
|
@ -23,6 +24,15 @@ def validate_allowed_domain(value):
|
||||||
raise ValidationError(_("This domain name is not allowed"))
|
raise ValidationError(_("This domain name is not allowed"))
|
||||||
|
|
||||||
|
|
||||||
|
def validate_domain_name(value):
|
||||||
|
# SRV records may use '_' in the domain name
|
||||||
|
value = value.lstrip('*.').replace('_', '')
|
||||||
|
try:
|
||||||
|
validate_hostname(value)
|
||||||
|
except ValidationError:
|
||||||
|
raise ValidationError(_("Not a valid domain name."))
|
||||||
|
|
||||||
|
|
||||||
def validate_zone_interval(value):
|
def validate_zone_interval(value):
|
||||||
try:
|
try:
|
||||||
int(value)
|
int(value)
|
||||||
|
|
|
@ -24,6 +24,11 @@ class PaymentMethod(plugins.Plugin):
|
||||||
plugins.append(import_class(cls))
|
plugins.append(import_class(cls))
|
||||||
return plugins
|
return plugins
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def clean_data(cls, data):
|
||||||
|
""" model clean """
|
||||||
|
return data
|
||||||
|
|
||||||
def get_form(self):
|
def get_form(self):
|
||||||
self.form.plugin = self
|
self.form.plugin = self
|
||||||
self.form.plugin_field = 'method'
|
self.form.plugin_field = 'method'
|
||||||
|
|
|
@ -23,24 +23,12 @@ class SEPADirectDebitForm(PluginDataForm):
|
||||||
widget=forms.TextInput(attrs={'size': '50'}))
|
widget=forms.TextInput(attrs={'size': '50'}))
|
||||||
name = forms.CharField(max_length=128, label=_("Name"),
|
name = forms.CharField(max_length=128, label=_("Name"),
|
||||||
widget=forms.TextInput(attrs={'size': '50'}))
|
widget=forms.TextInput(attrs={'size': '50'}))
|
||||||
|
|
||||||
def clean_iban(self):
|
|
||||||
return self.cleaned_data['iban'].strip()
|
|
||||||
|
|
||||||
def clean_name(self):
|
|
||||||
return self.cleaned_data['name'].strip()
|
|
||||||
|
|
||||||
|
|
||||||
class SEPADirectDebitSerializer(serializers.Serializer):
|
class SEPADirectDebitSerializer(serializers.Serializer):
|
||||||
iban = serializers.CharField(label='IBAN', validators=[IBANValidator()],
|
iban = serializers.CharField(label='IBAN', validators=[IBANValidator()],
|
||||||
min_length=min(IBAN_COUNTRY_CODE_LENGTH.values()), max_length=34)
|
min_length=min(IBAN_COUNTRY_CODE_LENGTH.values()), max_length=34)
|
||||||
name = serializers.CharField(label=_("Name"), max_length=128)
|
name = serializers.CharField(label=_("Name"), max_length=128)
|
||||||
|
|
||||||
def clean_iban(self, attrs, source):
|
|
||||||
return attrs[source].strip()
|
|
||||||
|
|
||||||
def clean_name(self, attrs, source):
|
|
||||||
return attrs[source].strip()
|
|
||||||
|
|
||||||
|
|
||||||
class SEPADirectDebit(PaymentMethod):
|
class SEPADirectDebit(PaymentMethod):
|
||||||
|
@ -56,6 +44,13 @@ class SEPADirectDebit(PaymentMethod):
|
||||||
return _("This bill will been automatically charged to your bank account "
|
return _("This bill will been automatically charged to your bank account "
|
||||||
" with IBAN number<br><strong>%s</strong>.") % source.number
|
" with IBAN number<br><strong>%s</strong>.") % source.number
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def clean_data(cls, data):
|
||||||
|
data['iban'] = data['iban'].strip()
|
||||||
|
data['name'] = data['name'].strip()
|
||||||
|
IBANValidator()(data['iban'])
|
||||||
|
return data
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def process(cls, transactions):
|
def process(cls, transactions):
|
||||||
debts = []
|
debts = []
|
||||||
|
|
|
@ -49,6 +49,9 @@ class PaymentSource(models.Model):
|
||||||
|
|
||||||
def get_due_delta(self):
|
def get_due_delta(self):
|
||||||
return self.method_class().due_delta
|
return self.method_class().due_delta
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
self.data = self.method_class().clean_data(self.data)
|
||||||
|
|
||||||
|
|
||||||
class TransactionQuerySet(models.QuerySet):
|
class TransactionQuerySet(models.QuerySet):
|
||||||
|
|
|
@ -23,7 +23,7 @@ class SystemUserQuerySet(models.QuerySet):
|
||||||
class SystemUser(models.Model):
|
class SystemUser(models.Model):
|
||||||
""" System users """
|
""" System users """
|
||||||
username = models.CharField(_("username"), max_length=64, unique=True,
|
username = models.CharField(_("username"), max_length=64, unique=True,
|
||||||
help_text=_("Required. 30 characters or fewer. Letters, digits and ./-/_ only."),
|
help_text=_("Required. 64 characters or fewer. Letters, digits and ./-/_ only."),
|
||||||
validators=[validators.RegexValidator(r'^[\w.-]+$',
|
validators=[validators.RegexValidator(r'^[\w.-]+$',
|
||||||
_("Enter a valid username."), 'invalid')])
|
_("Enter a valid username."), 'invalid')])
|
||||||
password = models.CharField(_("password"), max_length=128)
|
password = models.CharField(_("password"), max_length=128)
|
||||||
|
|
|
@ -61,6 +61,14 @@ WEBAPPS_TYPES = getattr(settings, 'WEBAPPS_TYPES', {
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
WEBAPPS_TYPES_OVERRIDE = getattr(settings, 'WEBAPPS_TYPES_OVERRIDE', {})
|
||||||
|
for webapp_type, value in WEBAPPS_TYPES_OVERRIDE.iteritems():
|
||||||
|
if value is None:
|
||||||
|
WEBAPPS_TYPES.pop(webapp_type, None)
|
||||||
|
else:
|
||||||
|
WEBAPPS_TYPES[webapp_type] = value
|
||||||
|
|
||||||
|
|
||||||
WEBAPPS_DEFAULT_TYPE = getattr(settings, 'WEBAPPS_DEFAULT_TYPE', 'php5.5')
|
WEBAPPS_DEFAULT_TYPE = getattr(settings, 'WEBAPPS_DEFAULT_TYPE', 'php5.5')
|
||||||
|
|
||||||
|
|
||||||
|
@ -74,51 +82,91 @@ WEBAPPS_OPTIONS = getattr(settings, 'WEBAPPS_OPTIONS', {
|
||||||
# PHP
|
# PHP
|
||||||
'enabled_functions': (
|
'enabled_functions': (
|
||||||
_("PHP - Enabled functions"),
|
_("PHP - Enabled functions"),
|
||||||
r'^[\w.,-]+$'
|
r'^[\w\.,-]+$'
|
||||||
),
|
|
||||||
'PHP-register_globals': (
|
|
||||||
_("PHP - Register globals"),
|
|
||||||
r'^(On|Off|on|off)$'
|
|
||||||
),
|
),
|
||||||
'PHP-allow_url_include': (
|
'PHP-allow_url_include': (
|
||||||
_("PHP - Allow URL include"),
|
_("PHP - Allow URL include"),
|
||||||
r'^(On|Off|on|off)$'
|
r'^(On|Off|on|off)$'
|
||||||
),
|
),
|
||||||
|
'PHP-allow_url_fopen': (
|
||||||
|
_("PHP - allow_url_fopen"),
|
||||||
|
r'^(On|Off|on|off)$'
|
||||||
|
),
|
||||||
'PHP-auto_append_file': (
|
'PHP-auto_append_file': (
|
||||||
_("PHP - Auto append file"),
|
_("PHP - Auto append file"),
|
||||||
r'^none$'
|
r'^[\w\.,-/]+$'
|
||||||
|
),
|
||||||
|
'PHP-auto_prepend_file': (
|
||||||
|
_("PHP - Auto prepend file"),
|
||||||
|
r'^[\w\.,-/]+$'
|
||||||
|
),
|
||||||
|
'PHP-date.timezone': (
|
||||||
|
_("PHP - date.timezone"),
|
||||||
|
r'^\w+/\w+$'
|
||||||
),
|
),
|
||||||
'PHP-default_socket_timeout': (
|
'PHP-default_socket_timeout': (
|
||||||
_("PHP - Default socket timeout"),
|
_("PHP - Default socket timeout"),
|
||||||
r'P^[0-9][0-9]?[0-9]?$'
|
r'^[0-9]{1,3}$'
|
||||||
),
|
),
|
||||||
'PHP-display_errors': (
|
'PHP-display_errors': (
|
||||||
_("PHP - Display errors"),
|
_("PHP - Display errors"),
|
||||||
r'^(On|Off|on|off)$'
|
r'^(On|Off|on|off)$'
|
||||||
),
|
),
|
||||||
|
'PHP-extension': (
|
||||||
|
_("PHP - Extension"),
|
||||||
|
r'^[^ ]+$'
|
||||||
|
),
|
||||||
'PHP-magic_quotes_gpc': (
|
'PHP-magic_quotes_gpc': (
|
||||||
_("PHP - Magic quotes GPC"),
|
_("PHP - Magic quotes GPC"),
|
||||||
r'^(On|Off|on|off)$'
|
r'^(On|Off|on|off)$'
|
||||||
),
|
),
|
||||||
|
'PHP-magic_quotes_runtime': (
|
||||||
|
_("PHP - Magic quotes runtime"),
|
||||||
|
r'^(On|Off|on|off)$'
|
||||||
|
),
|
||||||
|
'PHP-magic_quotes_sybase': (
|
||||||
|
_("PHP - Magic quotes sybase"),
|
||||||
|
r'^(On|Off|on|off)$'
|
||||||
|
),
|
||||||
'PHP-max_execution_time': (
|
'PHP-max_execution_time': (
|
||||||
_("PHP - Max execution time"),
|
_("PHP - Max execution time"),
|
||||||
r'^[0-9][0-9]?[0-9]?$'
|
r'^[0-9]{1,3}$'
|
||||||
),
|
),
|
||||||
'PHP-max_input_time': (
|
'PHP-max_input_time': (
|
||||||
_("PHP - Max input time"),
|
_("PHP - Max input time"),
|
||||||
r'^[0-9][0-9]?[0-9]?$'
|
r'^[0-9]{1,3}$'
|
||||||
),
|
),
|
||||||
'PHP-memory_limit': (
|
'PHP-memory_limit': (
|
||||||
_("PHP - Memory limit"),
|
_("PHP - Memory limit"),
|
||||||
r'^[0-9][0-9]?[0-9]?M$'
|
r'^[0-9]{1,3}M$'
|
||||||
),
|
),
|
||||||
'PHP-mysql.connect_timeout': (
|
'PHP-mysql.connect_timeout': (
|
||||||
_("PHP - Mysql connect timeout"),
|
_("PHP - Mysql connect timeout"),
|
||||||
r'^[0-9][0-9]?[0-9]?$'
|
r'^([0-9]){1,3}$'
|
||||||
|
),
|
||||||
|
'PHP-output_buffering': (
|
||||||
|
_("PHP - output_buffering"),
|
||||||
|
r'^(On|Off|on|off)$'
|
||||||
|
),
|
||||||
|
'PHP-register_globals': (
|
||||||
|
_("PHP - Register globals"),
|
||||||
|
r'^(On|Off|on|off)$'
|
||||||
),
|
),
|
||||||
'PHP-post_max_size': (
|
'PHP-post_max_size': (
|
||||||
_("PHP - Post max size"),
|
_("PHP - Post max size"),
|
||||||
r'^[0-9][0-9]?M$'
|
r'^[0-9]{1,3}M$'
|
||||||
|
),
|
||||||
|
'PHP-sendmail_path': (
|
||||||
|
_("PHP - sendmail_path"),
|
||||||
|
r'^[^ ]+$'
|
||||||
|
),
|
||||||
|
'PHP-session.bug_compat_warn': (
|
||||||
|
_("PHP - session.bug_compat_warn"),
|
||||||
|
r'^(On|Off|on|off)$'
|
||||||
|
),
|
||||||
|
'PHP-session.auto_start': (
|
||||||
|
_("PHP - session.auto_start"),
|
||||||
|
r'^(On|Off|on|off)$'
|
||||||
),
|
),
|
||||||
'PHP-safe_mode': (
|
'PHP-safe_mode': (
|
||||||
_("PHP - Safe mode"),
|
_("PHP - Safe mode"),
|
||||||
|
@ -126,32 +174,48 @@ WEBAPPS_OPTIONS = getattr(settings, 'WEBAPPS_OPTIONS', {
|
||||||
),
|
),
|
||||||
'PHP-suhosin.post.max_vars': (
|
'PHP-suhosin.post.max_vars': (
|
||||||
_("PHP - Suhosin post max vars"),
|
_("PHP - Suhosin post max vars"),
|
||||||
r'^[0-9][0-9]?[0-9]?[0-9]?$'
|
r'^[0-9]{1,4}$'
|
||||||
),
|
),
|
||||||
'PHP-suhosin.request.max_vars': (
|
'PHP-suhosin.request.max_vars': (
|
||||||
_("PHP - Suhosin request max vars"),
|
_("PHP - Suhosin request max vars"),
|
||||||
r'^[0-9][0-9]?[0-9]?[0-9]?$'
|
r'^[0-9]{1,4}$'
|
||||||
|
),
|
||||||
|
'PHP-suhosin.session.encrypt': (
|
||||||
|
_("PHP - suhosin.session.encrypt"),
|
||||||
|
r'^(On|Off|on|off)$'
|
||||||
),
|
),
|
||||||
'PHP-suhosin.simulation': (
|
'PHP-suhosin.simulation': (
|
||||||
_("PHP - Suhosin simulation"),
|
_("PHP - Suhosin simulation"),
|
||||||
r'^(On|Off|on|off)$'
|
r'^(On|Off|on|off)$'
|
||||||
),
|
),
|
||||||
|
'PHP-suhosin.executor.include.whitelist': (
|
||||||
|
_("PHP - suhosin.executor.include.whitelist"),
|
||||||
|
r'^(upload|phar)$'
|
||||||
|
),
|
||||||
|
'PHP-upload_max_filesize': (
|
||||||
|
_("PHP - upload_max_filesize"),
|
||||||
|
r'^[0-9]{1,3}M$'
|
||||||
|
),
|
||||||
|
'PHP-zend_extension': (
|
||||||
|
_("PHP - zend_extension"),
|
||||||
|
r'^[^ ]+$'
|
||||||
|
),
|
||||||
# FCGID
|
# FCGID
|
||||||
'FcgidIdleTimeout': (
|
'FcgidIdleTimeout': (
|
||||||
_("FCGI - Idle timeout"),
|
_("FCGI - Idle timeout"),
|
||||||
r'^[0-9][0-9]?[0-9]?$'
|
r'^[0-9]{1,3}$'
|
||||||
),
|
),
|
||||||
'FcgidBusyTimeout': (
|
'FcgidBusyTimeout': (
|
||||||
_("FCGI - Busy timeout"),
|
_("FCGI - Busy timeout"),
|
||||||
r'^[0-9][0-9]?[0-9]?$'
|
r'^[0-9]{1,3}$'
|
||||||
),
|
),
|
||||||
'FcgidConnectTimeout': (
|
'FcgidConnectTimeout': (
|
||||||
_("FCGI - Connection timeout"),
|
_("FCGI - Connection timeout"),
|
||||||
r'^[0-9][0-9]?[0-9]?$'
|
r'^[0-9]{1,3}$'
|
||||||
),
|
),
|
||||||
'FcgidIOTimeout': (
|
'FcgidIOTimeout': (
|
||||||
_("FCGI - IO timeout"),
|
_("FCGI - IO timeout"),
|
||||||
r'^[0-9][0-9]?[0-9]?$'
|
r'^[0-9]{1,3}$'
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ class Apache2Backend(ServiceController):
|
||||||
if site.protocol is 'https':
|
if site.protocol is 'https':
|
||||||
extra_conf += self.get_ssl(site)
|
extra_conf += self.get_ssl(site)
|
||||||
extra_conf += self.get_security(site)
|
extra_conf += self.get_security(site)
|
||||||
|
extra_conf += self.get_redirect(site)
|
||||||
context['extra_conf'] = extra_conf
|
context['extra_conf'] = extra_conf
|
||||||
|
|
||||||
apache_conf = Template(textwrap.dedent("""\
|
apache_conf = Template(textwrap.dedent("""\
|
||||||
|
@ -89,7 +90,7 @@ class Apache2Backend(ServiceController):
|
||||||
<Directory %(app_path)s>
|
<Directory %(app_path)s>
|
||||||
Options +ExecCGI
|
Options +ExecCGI
|
||||||
AddHandler fcgid-script .php
|
AddHandler fcgid-script .php
|
||||||
FcgidWrapper %(fcgid_path)s
|
FcgidWrapper %(fcgid_path)s\
|
||||||
""" % context)
|
""" % context)
|
||||||
for option in content.webapp.options.filter(name__startswith='Fcgid'):
|
for option in content.webapp.options.filter(name__startswith='Fcgid'):
|
||||||
fcgid += " %s %s\n" % (option.name, option.value)
|
fcgid += " %s %s\n" % (option.name, option.value)
|
||||||
|
@ -101,10 +102,12 @@ class Apache2Backend(ServiceController):
|
||||||
custom_cert = site.options.filter(name='ssl')
|
custom_cert = site.options.filter(name='ssl')
|
||||||
if custom_cert:
|
if custom_cert:
|
||||||
cert = tuple(custom_cert[0].value.split())
|
cert = tuple(custom_cert[0].value.split())
|
||||||
|
# TODO separate directtives?
|
||||||
directives = textwrap.dedent("""\
|
directives = textwrap.dedent("""\
|
||||||
SSLEngine on
|
SSLEngine on
|
||||||
SSLCertificateFile %s
|
SSLCertificateFile %s
|
||||||
SSLCertificateKeyFile %s""" % cert
|
SSLCertificateKeyFile %s\
|
||||||
|
""" % cert
|
||||||
)
|
)
|
||||||
return directives
|
return directives
|
||||||
|
|
||||||
|
@ -112,14 +115,24 @@ class Apache2Backend(ServiceController):
|
||||||
directives = ''
|
directives = ''
|
||||||
for rules in site.options.filter(name='sec_rule_remove'):
|
for rules in site.options.filter(name='sec_rule_remove'):
|
||||||
for rule in rules.value.split():
|
for rule in rules.value.split():
|
||||||
directives += "SecRuleRemoveById %i" % int(rule)
|
directives += "SecRuleRemoveById %i\n" % int(rule)
|
||||||
|
|
||||||
for modsecurity in site.options.filter(name='sec_rule_off'):
|
for modsecurity in site.options.filter(name='sec_rule_off'):
|
||||||
directives += textwrap.dedent("""\
|
directives += textwrap.dedent("""\
|
||||||
<LocationMatch %s>
|
<LocationMatch %s>
|
||||||
SecRuleEngine Off
|
SecRuleEngine Off
|
||||||
</LocationMatch>
|
</LocationMatch>\
|
||||||
""" % modsecurity.value)
|
""" % modsecurity.value)
|
||||||
|
if directives:
|
||||||
|
directives = '<IfModule mod_security2.c>\n%s\n</IfModule>' % directives
|
||||||
|
return directives
|
||||||
|
|
||||||
|
def get_redirect(self, site):
|
||||||
|
directives = ''
|
||||||
|
for redirect in site.options.filter(name='redirect'):
|
||||||
|
if re.match(r'^.*[\^\*\$\?\)]+.*$', redirect.value):
|
||||||
|
directives += "RedirectMatch %s" % redirect.value
|
||||||
|
else:
|
||||||
|
directives += "Redirect %s" % redirect.value
|
||||||
return directives
|
return directives
|
||||||
|
|
||||||
def get_protections(self, site):
|
def get_protections(self, site):
|
||||||
|
|
|
@ -17,25 +17,34 @@ WEBSITES_DEFAULT_IP = getattr(settings, 'WEBSITES_DEFAULT_IP', '*')
|
||||||
WEBSITES_DOMAIN_MODEL = getattr(settings, 'WEBSITES_DOMAIN_MODEL', 'domains.Domain')
|
WEBSITES_DOMAIN_MODEL = getattr(settings, 'WEBSITES_DOMAIN_MODEL', 'domains.Domain')
|
||||||
|
|
||||||
|
|
||||||
|
# TODO ssl ca, ssl cert, ssl key
|
||||||
WEBSITES_OPTIONS = getattr(settings, 'WEBSITES_OPTIONS', {
|
WEBSITES_OPTIONS = getattr(settings, 'WEBSITES_OPTIONS', {
|
||||||
# { name: ( verbose_name, validation_regex ) }
|
# { name: ( verbose_name, validation_regex ) }
|
||||||
'directory_protection': (
|
'directory_protection': (
|
||||||
_("HTTPD - Directory protection"),
|
_("HTTPD - Directory protection"),
|
||||||
r'^([\w/_]+)\s+(\".*\")\s+([\w/_\.]+)$'
|
r'^([\w/_]+)\s+(\".*\")\s+([\w/_\.]+)$'
|
||||||
),
|
),
|
||||||
'redirection': (
|
'redirect': (
|
||||||
_("HTTPD - Redirection"),
|
_("HTTPD - Redirection"),
|
||||||
r'^.*\s+.*$'
|
r'^(permanent\s[^ ]+|[^ ]+)\s[^ ]+$'
|
||||||
),
|
),
|
||||||
'ssl': (
|
'ssl_ca': (
|
||||||
_("HTTPD - SSL"),
|
_("HTTPD - SSL CA"),
|
||||||
r'^.*\s+.*$'
|
r'^[^ ]+$'
|
||||||
|
),
|
||||||
|
'ssl_cert': (
|
||||||
|
_("HTTPD - SSL cert"),
|
||||||
|
r'^[^ ]+$'
|
||||||
|
),
|
||||||
|
'ssl_key': (
|
||||||
|
_("HTTPD - SSL key"),
|
||||||
|
r'^[^ ]+$'
|
||||||
),
|
),
|
||||||
'sec_rule_remove': (
|
'sec_rule_remove': (
|
||||||
_("HTTPD - SecRuleRemoveById"),
|
_("HTTPD - SecRuleRemoveById"),
|
||||||
r'^[0-9,\s]+$'
|
r'^[0-9\s]+$'
|
||||||
),
|
),
|
||||||
'sec_rule_off': (
|
'sec_engine': (
|
||||||
_("HTTPD - Disable Modsecurity"),
|
_("HTTPD - Disable Modsecurity"),
|
||||||
r'^[\w/_]+$'
|
r'^[\w/_]+$'
|
||||||
),
|
),
|
||||||
|
|
|
@ -155,7 +155,10 @@ function install_requirements () {
|
||||||
lxml==3.3.5 \
|
lxml==3.3.5 \
|
||||||
python-dateutil==2.2 \
|
python-dateutil==2.2 \
|
||||||
django-iban==0.3.0 \
|
django-iban==0.3.0 \
|
||||||
requests"
|
requests \
|
||||||
|
phonenumbers \
|
||||||
|
django-countries \
|
||||||
|
django-localflavor"
|
||||||
|
|
||||||
if $testing; then
|
if $testing; then
|
||||||
APT="${APT} \
|
APT="${APT} \
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import crack
|
import crack
|
||||||
|
import localflavor
|
||||||
|
import phonenumbers
|
||||||
|
|
||||||
from django.core import validators
|
from django.core import validators
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
from IPy import IP
|
from IPy import IP
|
||||||
|
|
||||||
|
from ..utils.python import import_class
|
||||||
|
|
||||||
|
|
||||||
def validate_ipv4_address(value):
|
def validate_ipv4_address(value):
|
||||||
msg = _("%s is not a valid IPv4 address") % value
|
msg = _("%s is not a valid IPv4 address") % value
|
||||||
|
@ -18,7 +22,6 @@ def validate_ipv4_address(value):
|
||||||
raise ValidationError(msg)
|
raise ValidationError(msg)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def validate_ipv6_address(value):
|
def validate_ipv6_address(value):
|
||||||
msg = _("%s is not a valid IPv6 address") % value
|
msg = _("%s is not a valid IPv6 address") % value
|
||||||
try:
|
try:
|
||||||
|
@ -61,11 +64,12 @@ def validate_hostname(hostname):
|
||||||
http://stackoverflow.com/a/2532344
|
http://stackoverflow.com/a/2532344
|
||||||
"""
|
"""
|
||||||
if len(hostname) > 255:
|
if len(hostname) > 255:
|
||||||
return False
|
raise ValidationError(_("Too long for a hostname."))
|
||||||
if hostname[-1] == ".":
|
hostname = hostname.rstrip('.')
|
||||||
hostname = hostname[:-1] # strip exactly one dot from the right, if present
|
allowed = re.compile('(?!-)[A-Z\d-]{1,63}(?<!-)$', re.IGNORECASE)
|
||||||
allowed = re.compile("(?!-)[A-Z\d-]{1,63}(?<!-)$", re.IGNORECASE)
|
for name in hostname.split('.'):
|
||||||
return all(allowed.match(x) for x in hostname.split("."))
|
if not allowed.match(name):
|
||||||
|
raise ValidationError(_("Not a valid hostname (%s).") % name)
|
||||||
|
|
||||||
|
|
||||||
def validate_password(value):
|
def validate_password(value):
|
||||||
|
@ -78,3 +82,40 @@ def validate_password(value):
|
||||||
def validate_url_path(value):
|
def validate_url_path(value):
|
||||||
if not re.match(r'^\/[/.a-zA-Z0-9-_]*$', value):
|
if not re.match(r'^\/[/.a-zA-Z0-9-_]*$', value):
|
||||||
raise ValidationError(_('"%s" is not a valid URL path.') % value)
|
raise ValidationError(_('"%s" is not a valid URL path.') % value)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_vat(vat, country):
|
||||||
|
field = 'localflavor.{lower}.forms.{upper}IdentityCardNumberField'.format(
|
||||||
|
lower=country.lower(),
|
||||||
|
upper=country.upper()
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
field = import_class(field)
|
||||||
|
except (ImportError, AttributeError, ValueError):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
field().clean(vat)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_zipcode(zipcode, country):
|
||||||
|
field = 'localflavor.{lower}.forms.{upper}PostalCodeField'.format(
|
||||||
|
lower=country.lower(),
|
||||||
|
upper=country.upper()
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
field = import_class(field)
|
||||||
|
except (ImportError, AttributeError, ValueError):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
field().clean(zipcode)
|
||||||
|
|
||||||
|
|
||||||
|
def validate_phone(value, country):
|
||||||
|
""" local phone number or international """
|
||||||
|
msg = _("Not a valid %s nor international phone number.") % country
|
||||||
|
try:
|
||||||
|
number = phonenumbers.parse(value, country)
|
||||||
|
except phonenumbers.phonenumberutil.NumberParseException:
|
||||||
|
raise ValidationError(msg)
|
||||||
|
if not phonenumbers.is_valid_number(number):
|
||||||
|
raise ValidationError(msg)
|
||||||
|
|
|
@ -29,6 +29,10 @@ class MultiSelectField(models.CharField):
|
||||||
|
|
||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
if value is not None:
|
if value is not None:
|
||||||
|
if isinstance(value, list) and value[0].startswith('('):
|
||||||
|
# Workaround unknown bug on default model values
|
||||||
|
# [u"('SUPPORT'", u" 'ADMIN'", u" 'BILLING'", u" 'TECH'", u" 'ADDS'", u" 'EMERGENCY')"]
|
||||||
|
value = list(eval(', '.join(value)))
|
||||||
return value if isinstance(value, list) else value.split(',')
|
return value if isinstance(value, list) else value.split(',')
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
|
@ -36,7 +40,7 @@ class MultiSelectField(models.CharField):
|
||||||
super(MultiSelectField, self).contribute_to_class(cls, name)
|
super(MultiSelectField, self).contribute_to_class(cls, name)
|
||||||
if self.choices:
|
if self.choices:
|
||||||
def func(self, field=name, choices=dict(self.choices)):
|
def func(self, field=name, choices=dict(self.choices)):
|
||||||
','.join([ choices.get(value, value) for value in getattr(self, field) ])
|
return ','.join([ choices.get(value, value) for value in getattr(self, field) ])
|
||||||
setattr(cls, 'get_%s_display' % self.name, func)
|
setattr(cls, 'get_%s_display' % self.name, func)
|
||||||
|
|
||||||
def validate(self, value, model_instance):
|
def validate(self, value, model_instance):
|
||||||
|
|
|
@ -21,11 +21,11 @@ def check_root(func):
|
||||||
return wrapped
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
class _AttributeString(str):
|
class _AttributeUnicode(unicode):
|
||||||
""" Simple string subclass to allow arbitrary attribute access. """
|
""" Simple string subclass to allow arbitrary attribute access. """
|
||||||
@property
|
@property
|
||||||
def stdout(self):
|
def stdout(self):
|
||||||
return str(self)
|
return unicode(self)
|
||||||
|
|
||||||
|
|
||||||
def make_async(fd):
|
def make_async(fd):
|
||||||
|
@ -43,7 +43,7 @@ def read_async(fd):
|
||||||
if e.errno != errno.EAGAIN:
|
if e.errno != errno.EAGAIN:
|
||||||
raise e
|
raise e
|
||||||
else:
|
else:
|
||||||
return ''
|
return u''
|
||||||
|
|
||||||
|
|
||||||
def run(command, display=False, error_codes=[0], silent=False, stdin=''):
|
def run(command, display=False, error_codes=[0], silent=False, stdin=''):
|
||||||
|
@ -60,8 +60,8 @@ def run(command, display=False, error_codes=[0], silent=False, stdin=''):
|
||||||
make_async(p.stdout)
|
make_async(p.stdout)
|
||||||
make_async(p.stderr)
|
make_async(p.stderr)
|
||||||
|
|
||||||
stdout = str()
|
stdout = unicode()
|
||||||
stderr = str()
|
stderr = unicode()
|
||||||
|
|
||||||
# Async reading of stdout and sterr
|
# Async reading of stdout and sterr
|
||||||
while True:
|
while True:
|
||||||
|
@ -77,15 +77,15 @@ def run(command, display=False, error_codes=[0], silent=False, stdin=''):
|
||||||
if display and stderrPiece:
|
if display and stderrPiece:
|
||||||
sys.stderr.write(stderrPiece)
|
sys.stderr.write(stderrPiece)
|
||||||
|
|
||||||
stdout += stdoutPiece
|
stdout += stdoutPiece.decode("utf8")
|
||||||
stderr += stderrPiece
|
stderr += stderrPiece.decode("utf8")
|
||||||
returnCode = p.poll()
|
returnCode = p.poll()
|
||||||
|
|
||||||
if returnCode != None:
|
if returnCode != None:
|
||||||
break
|
break
|
||||||
|
|
||||||
out = _AttributeString(stdout.strip())
|
out = _AttributeUnicode(stdout.strip())
|
||||||
err = _AttributeString(stderr.strip())
|
err = _AttributeUnicode(stderr.strip())
|
||||||
p.stdout.close()
|
p.stdout.close()
|
||||||
p.stderr.close()
|
p.stderr.close()
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,99 @@
|
||||||
import re
|
import re
|
||||||
import glob
|
import glob
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import errno
|
||||||
|
import fcntl
|
||||||
|
import getpass
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import select
|
||||||
|
import subprocess
|
||||||
|
import textwrap
|
||||||
|
|
||||||
|
class _AttributeString(str):
|
||||||
|
""" Simple string subclass to allow arbitrary attribute access. """
|
||||||
|
@property
|
||||||
|
def stdout(self):
|
||||||
|
return str(self)
|
||||||
|
|
||||||
|
|
||||||
|
def make_async(fd):
|
||||||
|
""" Helper function to add the O_NONBLOCK flag to a file descriptor """
|
||||||
|
fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK)
|
||||||
|
|
||||||
|
|
||||||
|
def read_async(fd):
|
||||||
|
"""
|
||||||
|
Helper function to read some data from a file descriptor, ignoring EAGAIN errors
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return fd.read()
|
||||||
|
except IOError, e:
|
||||||
|
if e.errno != errno.EAGAIN:
|
||||||
|
raise e
|
||||||
|
else:
|
||||||
|
return ''
|
||||||
|
|
||||||
|
def run(command, display=False, error_codes=[0], silent=False, stdin=''):
|
||||||
|
""" Subprocess wrapper for running commands """
|
||||||
|
if display:
|
||||||
|
sys.stderr.write("\n\033[1m $ %s\033[0m\n" % command)
|
||||||
|
|
||||||
|
p = subprocess.Popen(command, shell=True, executable='/bin/bash',
|
||||||
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
|
||||||
|
|
||||||
|
p.stdin.write(stdin)
|
||||||
|
p.stdin.close()
|
||||||
|
|
||||||
|
make_async(p.stdout)
|
||||||
|
make_async(p.stderr)
|
||||||
|
|
||||||
|
stdout = str()
|
||||||
|
stderr = str()
|
||||||
|
|
||||||
|
# Async reading of stdout and sterr
|
||||||
|
while True:
|
||||||
|
# Wait for data to become available
|
||||||
|
select.select([p.stdout, p.stderr], [], [])
|
||||||
|
|
||||||
|
# Try reading some data from each
|
||||||
|
stdoutPiece = read_async(p.stdout)
|
||||||
|
stderrPiece = read_async(p.stderr)
|
||||||
|
|
||||||
|
if display and stdoutPiece:
|
||||||
|
sys.stdout.write(stdoutPiece)
|
||||||
|
if display and stderrPiece:
|
||||||
|
sys.stderr.write(stderrPiece)
|
||||||
|
|
||||||
|
stdout += stdoutPiece
|
||||||
|
stderr += stderrPiece
|
||||||
|
returnCode = p.poll()
|
||||||
|
|
||||||
|
if returnCode != None:
|
||||||
|
break
|
||||||
|
|
||||||
|
out = _AttributeString(stdout.strip())
|
||||||
|
err = _AttributeString(stderr.strip())
|
||||||
|
p.stdout.close()
|
||||||
|
p.stderr.close()
|
||||||
|
|
||||||
|
out.failed = False
|
||||||
|
out.return_code = returnCode
|
||||||
|
out.stderr = err
|
||||||
|
if p.returncode not in error_codes:
|
||||||
|
out.failed = True
|
||||||
|
msg = "\nrun() encountered an error (return code %s) while executing '%s'\n"
|
||||||
|
msg = msg % (p.returncode, command)
|
||||||
|
if display:
|
||||||
|
sys.stderr.write("\n\033[1;31mCommandError: %s %s\033[m\n" % (msg, err))
|
||||||
|
if not silent:
|
||||||
|
raise AttributeError("%s %s %s" % (msg, err, out))
|
||||||
|
|
||||||
|
out.succeeded = not out.failed
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
print "from orchestra.apps.accounts.models import Account"
|
print "from orchestra.apps.accounts.models import Account"
|
||||||
print "from orchestra.apps.domains.models import Domain"
|
print "from orchestra.apps.domains.models import Domain"
|
||||||
|
@ -8,10 +101,24 @@ print "from orchestra.apps.webapps.models import WebApp"
|
||||||
print "from orchestra.apps.websites.models import Website, Content"
|
print "from orchestra.apps.websites.models import Website, Content"
|
||||||
|
|
||||||
|
|
||||||
|
def print_webapp(context):
|
||||||
|
print textwrap.dedent("""\
|
||||||
|
try:
|
||||||
|
webapp = WebApp.objects.get(account=account, name='%(name)s')
|
||||||
|
except:"
|
||||||
|
webapp = WebApp.objects.create(account=account, name='%(name)s', type='%(type)s')
|
||||||
|
else:
|
||||||
|
webapp.type = '%(type)s'
|
||||||
|
webapp.save()"
|
||||||
|
Content.objects.get_or_create(website=website, webapp=webapp, path='%(path)s')
|
||||||
|
""" % context
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
for conf in glob.glob('/etc/apache2/sites-enabled/*'):
|
for conf in glob.glob('/etc/apache2/sites-enabled/*'):
|
||||||
username = conf.split('/')[-1].split('.')[0]
|
username = conf.split('/')[-1].split('.')[0]
|
||||||
with open(conf, 'rb') as conf:
|
with open(conf, 'rb') as conf:
|
||||||
print "account = Account.objects.get(user__username='%s')" % username
|
print "account = Account.objects.get(username='%s')" % username
|
||||||
for line in conf.readlines():
|
for line in conf.readlines():
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
if line.startswith('<VirtualHost'):
|
if line.startswith('<VirtualHost'):
|
||||||
|
@ -20,43 +127,64 @@ for conf in glob.glob('/etc/apache2/sites-enabled/*'):
|
||||||
apps = []
|
apps = []
|
||||||
if line.endswith(':443>'):
|
if line.endswith(':443>'):
|
||||||
port = 443
|
port = 443
|
||||||
|
wrapper_root = None
|
||||||
|
webalizer = False
|
||||||
|
webappname = None
|
||||||
|
elif line.startswith("DocumentRoot"):
|
||||||
|
__, path = line.split()
|
||||||
|
webappname = path.rstrip('/').split('/')[-1]
|
||||||
|
if webappname == 'public_html':
|
||||||
|
webappname = ''
|
||||||
elif line.startswith("ServerName"):
|
elif line.startswith("ServerName"):
|
||||||
domain = line.split()[1]
|
__, domain = line.split()
|
||||||
name = domain
|
sitename = domain
|
||||||
domains.append("'%s'" % domain)
|
domains.append("'%s'" % domain)
|
||||||
elif line.startswith("ServerAlias"):
|
elif line.startswith("ServerAlias"):
|
||||||
for domain in line.split()[1:]:
|
for domain in line.split()[1:]:
|
||||||
domains.append("'%s'" % domain)
|
domains.append("'%s'" % domain)
|
||||||
elif line.startswith("Alias /fcgi-bin/"):
|
elif line.startswith("Alias /fcgi-bin/"):
|
||||||
fcgid = line.split('/')[-1] or line.split('/')[-2]
|
__, __, wrapper_root = line.split()
|
||||||
fcgid = fcgid.split('-')[0]
|
elif line.startswith('Action php-fcgi'):
|
||||||
apps.append((name, fcgid, '/'))
|
__, __, wrapper_name = line.split()
|
||||||
|
wrapper_name = wrapper_name.split('/')[-1]
|
||||||
elif line.startswith("Alias /webalizer"):
|
elif line.startswith("Alias /webalizer"):
|
||||||
apps.append(('webalizer', 'webalizer', '/webalizer'))
|
webalizer = True
|
||||||
elif line == '</VirtualHost>':
|
elif line == '</VirtualHost>':
|
||||||
if port == 443:
|
if port == 443:
|
||||||
name += '-ssl'
|
sitename += '-ssl'
|
||||||
print "# SITE"
|
context = {
|
||||||
print "website, __ = Website.objects.get_or_create(name='%s', account=account, port=%d)" % (name, port)
|
'sitename': sitename,
|
||||||
domains = ', '.join(domains)
|
'port': port,
|
||||||
print "for domain in [%s]:" % str(domains)
|
'domains': ', '.join(domains),
|
||||||
print " try:"
|
}
|
||||||
print " domain = Domain.objects.get(name=domain)"
|
print textwrap.dedent("""\
|
||||||
print " except:"
|
# SITE"
|
||||||
print " domain = Domain.objects.create(name=domain, account=account)"
|
website, __ = Website.objects.get_or_create(name='%(sitename)s', account=account, port=%(port)d)
|
||||||
print " else:"
|
for domain in [%(domains)s]:
|
||||||
print " domain.account = account"
|
try:
|
||||||
print " domain.save()"
|
domain = Domain.objects.get(name=domain)
|
||||||
print " website.domains.add(domain)"
|
except:
|
||||||
print ""
|
domain = Domain.objects.create(name=domain, account=account)
|
||||||
for name, type, path in apps:
|
else:
|
||||||
print "try:"
|
domain.account = account
|
||||||
print " webapp = WebApp.objects.get(account=account, name='%s')" % name
|
domain.save()
|
||||||
print "except:"
|
website.domains.add(domain)
|
||||||
print " webapp = WebApp.objects.create(account=account, name='%s', type='%s')" % (name, type)
|
""" % context)
|
||||||
print "else:"
|
if wrapper_root:
|
||||||
print " webapp.type = '%s'" % type
|
wrapper = os.join(wrapper_root, wrapper_name)
|
||||||
print " webapp.save()"
|
fcgid = run('grep "^\s*exec " %s' % wrapper).stdout
|
||||||
print ""
|
type = fcgid.split()[1].split('/')[-1].split('-')[0]
|
||||||
print "Content.objects.get_or_create(website=website, webapp=webapp, path='%s')" % path
|
for option in fcgid.split('-d'):
|
||||||
|
print option
|
||||||
|
print_webapp({
|
||||||
|
'name': webappname,
|
||||||
|
'path': '/',
|
||||||
|
'type': type,
|
||||||
|
})
|
||||||
|
if webalizer:
|
||||||
|
print_webapp({
|
||||||
|
'name': 'webalizer-%s' % sitename,
|
||||||
|
'path': '/webalizer',
|
||||||
|
'type': 'webalizer',
|
||||||
|
})
|
||||||
print '\n'
|
print '\n'
|
||||||
|
|
|
@ -1,34 +1,64 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
|
||||||
VIRTUALTABLE="/etc/postfix/virtusertable"
|
VIRTUALTABLE=${1-"/etc/postfix/virtusertable"}
|
||||||
|
|
||||||
|
|
||||||
echo "from orchestra.apps.users import User"
|
echo "from orchestra.apps.accounts.models import Account"
|
||||||
echo "from orchestra.apps.users.roles.mailbox import Address, Mailbox"
|
echo "from orchestra.apps.mailboxes.models import Address, Mailbox"
|
||||||
echo "from orchestra.apps.domains import Domain"
|
echo "from orchestra.apps.domains.models import Domain"
|
||||||
|
|
||||||
|
echo "main_account = Account.objects.get(id=1)"
|
||||||
cat "$VIRTUALTABLE"|grep -v "^\s*$"|while read line; do
|
cat "$VIRTUALTABLE"|grep -v "^\s*$"|while read line; do
|
||||||
NAME=$(echo "$line" | awk {'print $1'} | cut -d'@' -f1)
|
NAME=$(echo "$line" | awk {'print $1'} | cut -d'@' -f1)
|
||||||
DOMAIN=$(echo "$line" | awk {'print $1'} | cut -d'@' -f2)
|
DOMAIN=$(echo "$line" | awk {'print $1'} | cut -d'@' -f2)
|
||||||
DESTINATION=$(echo "$line" | awk '{$1=""; print $0}' | sed -e 's/^ *//' -e 's/ *$//')
|
DESTINATION=$(echo "$line" | awk '{$1=""; print $0}' | sed -e 's/^ *//' -e 's/ *$//')
|
||||||
echo "domain = Domain.objects.get(name='$DOMAIN')"
|
echo "domain = Domain.objects.get(name='$DOMAIN')"
|
||||||
|
echo "mailboxes = []"
|
||||||
|
echo "account = main_account"
|
||||||
|
NEW_DESTINATION=""
|
||||||
for PLACE in $DESTINATION; do
|
for PLACE in $DESTINATION; do
|
||||||
if [[ ! $(echo $PLACE | grep '@') ]]; then
|
if [[ ! $(echo $PLACE | grep '@') ]]; then
|
||||||
echo "try:"
|
if [[ $(grep "^${PLACE}:" /etc/shadow) ]]; then
|
||||||
echo " user = User.objects.get(username='$PLACE')"
|
PASSWORD=$(grep "^${PLACE}:" /etc/shadow | cut -d':' -f2)
|
||||||
echo "except:"
|
echo "if account == main_account and domain.account != main_account:"
|
||||||
echo " print 'User $PLACE does not exists'"
|
echo " account = domain.account"
|
||||||
echo "else:"
|
echo "else:"
|
||||||
echo " mailbox, __ = Mailbox.objects.get_or_create(user=user)"
|
echo " try:"
|
||||||
echo " if user.account_id != 1:"
|
echo " account = Account.objects.get(username='${PLACE}')"
|
||||||
echo " user.account=domain.account"
|
echo " except:"
|
||||||
echo " user.save()"
|
echo " pass"
|
||||||
echo ""
|
echo "mailboxes.append(('${PLACE}', '${PASSWORD}'))"
|
||||||
|
else
|
||||||
|
NEW_DESTINATION="${NEW_DESTINATION} ${PLACE}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
NEW_DESTINATION="${NEW_DESTINATION} ${PLACE}"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
echo "address, __ = Address.objects.get_or_create(name='$NAME', domain=domain)"
|
echo "for mailbox, password in mailboxes:"
|
||||||
echo "address.account=domain.account"
|
echo " mailbox = mailbox.strip()"
|
||||||
echo "address.destination='$DESTINATION'"
|
echo " try:"
|
||||||
echo "address.save()"
|
echo " mailbox = Mailbox.objects.get(username=mailbox)"
|
||||||
|
echo " except:"
|
||||||
|
echo " mailbox = Mailbox(username=mailbox, password=password, account=account)"
|
||||||
|
echo " try:"
|
||||||
|
echo " mailbox.full_clean()"
|
||||||
|
echo " except:"
|
||||||
|
echo " sys.stderr.write('cleaning')"
|
||||||
|
echo " else:"
|
||||||
|
echo " mailbox.save()"
|
||||||
|
echo " else:"
|
||||||
|
echo " if mailbox.account != account:"
|
||||||
|
echo " sys.stderr.write('%s != %s' % (mailbox.account, account))"
|
||||||
|
echo " if domain.account != account:"
|
||||||
|
echo " sys.stderr.write('%s != %s' % (domain.account, account))"
|
||||||
|
echo " address = Address(name='${NAME}', domain=domain, account=account, destination='${NEW_DESTINATION}')"
|
||||||
|
echo " try:"
|
||||||
|
echo " address.full_clean()"
|
||||||
|
echo " except:"
|
||||||
|
echo " sys.stderr.write('cleaning address')"
|
||||||
|
echo " else:"
|
||||||
|
echo " address.save()"
|
||||||
|
echo " domain = None"
|
||||||
done
|
done
|
||||||
|
|
Loading…
Reference in New Issue