diff --git a/TODO.md b/TODO.md index 47af81be..dfac45c0 100644 --- a/TODO.md +++ b/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? # 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 * 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 * *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 diff --git a/orchestra/contrib/accounts/migrations/0001_initial.py b/orchestra/contrib/accounts/migrations/0001_initial.py new file mode 100644 index 00000000..e98f0dec --- /dev/null +++ b/orchestra/contrib/accounts/migrations/0001_initial.py @@ -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()), + ], + ), + ] diff --git a/orchestra/contrib/accounts/migrations/__init__.py b/orchestra/contrib/accounts/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/orchestra/contrib/accounts/models.py b/orchestra/contrib/accounts/models.py index ec64884c..c3b4128c 100644 --- a/orchestra/contrib/accounts/models.py +++ b/orchestra/contrib/accounts/models.py @@ -91,7 +91,7 @@ class Account(auth.AbstractBaseUser): if source in services and hasattr(source, 'active'): for obj in getattr(self, rel.get_accessor_name()).all(): OperationsMiddleware.collect(Operation.SAVE, instance=obj, update_fields=[]) - + def send_email(self, template, context, contacts=[], attachments=[], html=None): contacts = self.contacts.filter(email_usages=contacts) email_to = contacts.values_list('email', flat=True) diff --git a/orchestra/contrib/contacts/migrations/0001_initial.py b/orchestra/contrib/contacts/migrations/0001_initial.py new file mode 100644 index 00000000..627f9834 --- /dev/null +++ b/orchestra/contrib/contacts/migrations/0001_initial.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import django.core.validators +import orchestra.models.fields +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Contact', + fields=[ + ('id', models.AutoField(auto_created=True, verbose_name='ID', primary_key=True, serialize=False)), + ('short_name', models.CharField(max_length=128, verbose_name='short name')), + ('full_name', models.CharField(max_length=256, verbose_name='full name', blank=True)), + ('email', models.EmailField(max_length=254)), + ('email_usage', orchestra.models.fields.MultiSelectField(max_length=256, verbose_name='email usage', choices=[('SUPPORT', 'Support tickets'), ('ADMIN', 'Administrative'), ('BILLING', 'Billing'), ('TECH', 'Technical'), ('ADDS', 'Announcements'), ('EMERGENCY', 'Emergency contact')], default=('SUPPORT', 'ADMIN', 'BILLING', 'TECH', 'ADDS', 'EMERGENCY'), blank=True)), + ('phone', models.CharField(max_length=32, verbose_name='phone', blank=True)), + ('phone2', models.CharField(max_length=32, verbose_name='alternative phone', blank=True)), + ('address', models.TextField(verbose_name='address', blank=True)), + ('city', models.CharField(max_length=128, verbose_name='city', blank=True)), + ('zipcode', models.CharField(max_length=10, verbose_name='zip code', validators=[django.core.validators.RegexValidator('^[0-9,A-Z]{3,10}$', 'Enter a valid zipcode.', 'invalid')], blank=True)), + ('country', models.CharField(max_length=20, verbose_name='country', choices=[('BI', 'Burundi'), ('MW', 'Malawi'), ('UZ', 'Uzbekistan'), ('UA', 'Ukraine'), ('CR', 'Costa Rica'), ('TG', 'Togo'), ('VA', 'Holy See'), ('SB', 'Solomon Islands'), ('UM', 'United States Minor Outlying Islands'), ('UY', 'Uruguay'), ('SV', 'El Salvador'), ('VI', 'Virgin Islands (U.S.)'), ('FJ', 'Fiji'), ('LS', 'Lesotho'), ('NG', 'Nigeria'), ('EC', 'Ecuador'), ('WS', 'Samoa'), ('BZ', 'Belize'), ('ZM', 'Zambia'), ('TL', 'Timor-Leste'), ('SO', 'Somalia'), ('VC', 'Saint Vincent and the Grenadines'), ('KM', 'Comoros'), ('JE', 'Jersey'), ('SC', 'Seychelles'), ('GG', 'Guernsey'), ('MC', 'Monaco'), ('SM', 'San Marino'), ('AE', 'United Arab Emirates'), ('MG', 'Madagascar'), ('PE', 'Peru'), ('NR', 'Nauru'), ('MA', 'Morocco'), ('MM', 'Myanmar'), ('GB', 'United Kingdom of Great Britain and Northern Ireland'), ('PN', 'Pitcairn'), ('AW', 'Aruba'), ('FI', 'Finland'), ('TT', 'Trinidad and Tobago'), ('BO', 'Bolivia (Plurinational State of)'), ('ET', 'Ethiopia'), ('PM', 'Saint Pierre and Miquelon'), ('PK', 'Pakistan'), ('TR', 'Turkey'), ('CV', 'Cabo Verde'), ('SZ', 'Swaziland'), ('GT', 'Guatemala'), ('RW', 'Rwanda'), ('AL', 'Albania'), ('TK', 'Tokelau'), ('AS', 'American Samoa'), ('SH', 'Saint Helena, Ascension and Tristan da Cunha'), ('CH', 'Switzerland'), ('ME', 'Montenegro'), ('KP', "Korea (the Democratic People's Republic of)"), ('HM', 'Heard Island and McDonald Islands'), ('EG', 'Egypt'), ('SR', 'Suriname'), ('IT', 'Italy'), ('RO', 'Romania'), ('CO', 'Colombia'), ('MN', 'Mongolia'), ('AD', 'Andorra'), ('PH', 'Philippines'), ('IS', 'Iceland'), ('MF', 'Saint Martin (French part)'), ('KE', 'Kenya'), ('BN', 'Brunei Darussalam'), ('SD', 'Sudan'), ('GI', 'Gibraltar'), ('WF', 'Wallis and Futuna'), ('KR', 'Korea (the Republic of)'), ('AT', 'Austria'), ('ES', 'Spain'), ('DJ', 'Djibouti'), ('TV', 'Tuvalu'), ('JO', 'Jordan'), ('YE', 'Yemen'), ('IO', 'British Indian Ocean Territory'), ('HU', 'Hungary'), ('JM', 'Jamaica'), ('KH', 'Cambodia'), ('LR', 'Liberia'), ('SG', 'Singapore'), ('NZ', 'New Zealand'), ('ID', 'Indonesia'), ('SI', 'Slovenia'), ('PF', 'French Polynesia'), ('SJ', 'Svalbard and Jan Mayen'), ('GW', 'Guinea-Bissau'), ('BE', 'Belgium'), ('MV', 'Maldives'), ('IQ', 'Iraq'), ('SS', 'South Sudan'), ('IR', 'Iran (Islamic Republic of)'), ('PR', 'Puerto Rico'), ('KY', 'Cayman Islands'), ('BY', 'Belarus'), ('AI', 'Anguilla'), ('EE', 'Estonia'), ('SL', 'Sierra Leone'), ('AG', 'Antigua and Barbuda'), ('CL', 'Chile'), ('VG', 'Virgin Islands (British)'), ('GY', 'Guyana'), ('NE', 'Niger'), ('TH', 'Thailand'), ('RE', 'Réunion'), ('MU', 'Mauritius'), ('BF', 'Burkina Faso'), ('NU', 'Niue'), ('NP', 'Nepal'), ('CK', 'Cook Islands'), ('TC', 'Turks and Caicos Islands'), ('MK', 'Macedonia (the former Yugoslav Republic of)'), ('GS', 'South Georgia and the South Sandwich Islands'), ('HT', 'Haiti'), ('CD', 'Congo (the Democratic Republic of the)'), ('DM', 'Dominica'), ('CW', 'Curaçao'), ('CG', 'Congo'), ('BR', 'Brazil'), ('GQ', 'Equatorial Guinea'), ('NA', 'Namibia'), ('AF', 'Afghanistan'), ('MH', 'Marshall Islands'), ('LV', 'Latvia'), ('DE', 'Germany'), ('ZW', 'Zimbabwe'), ('CN', 'China'), ('KI', 'Kiribati'), ('SN', 'Senegal'), ('NI', 'Nicaragua'), ('PT', 'Portugal'), ('FO', 'Faroe Islands'), ('NF', 'Norfolk Island'), ('HK', 'Hong Kong'), ('FM', 'Micronesia (Federated States of)'), ('TN', 'Tunisia'), ('BQ', 'Bonaire, Sint Eustatius and Saba'), ('KZ', 'Kazakhstan'), ('PS', 'Palestine, State of'), ('BH', 'Bahrain'), ('DZ', 'Algeria'), ('BM', 'Bermuda'), ('DK', 'Denmark'), ('TF', 'French Southern Territories'), ('KN', 'Saint Kitts and Nevis'), ('HN', 'Honduras'), ('MD', 'Moldova (the Republic of)'), ('BW', 'Botswana'), ('LU', 'Luxembourg'), ('QA', 'Qatar'), ('AZ', 'Azerbaijan'), ('AU', 'Australia'), ('ML', 'Mali'), ('MR', 'Mauritania'), ('MX', 'Mexico'), ('MS', 'Montserrat'), ('VN', 'Viet Nam'), ('IN', 'India'), ('SX', 'Sint Maarten (Dutch part)'), ('TZ', 'Tanzania, United Republic of'), ('SA', 'Saudi Arabia'), ('CY', 'Cyprus'), ('PA', 'Panama'), ('SY', 'Syrian Arab Republic'), ('VU', 'Vanuatu'), ('PL', 'Poland'), ('BA', 'Bosnia and Herzegovina'), ('LK', 'Sri Lanka'), ('CI', "Côte d'Ivoire"), ('AQ', 'Antarctica'), ('AO', 'Angola'), ('LT', 'Lithuania'), ('MT', 'Malta'), ('GU', 'Guam'), ('IL', 'Israel'), ('MP', 'Northern Mariana Islands'), ('TM', 'Turkmenistan'), ('FK', 'Falkland Islands [Malvinas]'), ('LC', 'Saint Lucia'), ('TO', 'Tonga'), ('UG', 'Uganda'), ('GF', 'French Guiana'), ('TD', 'Chad'), ('AX', 'Åland Islands'), ('CF', 'Central African Republic'), ('PW', 'Palau'), ('CU', 'Cuba'), ('EH', 'Western Sahara'), ('HR', 'Croatia'), ('GN', 'Guinea'), ('BV', 'Bouvet Island'), ('BL', 'Saint Barthélemy'), ('MO', 'Macao'), ('SE', 'Sweden'), ('IM', 'Isle of Man'), ('CM', 'Cameroon'), ('BG', 'Bulgaria'), ('BS', 'Bahamas'), ('NO', 'Norway'), ('AM', 'Armenia'), ('PY', 'Paraguay'), ('VE', 'Venezuela (Bolivarian Republic of)'), ('JP', 'Japan'), ('LI', 'Liechtenstein'), ('BJ', 'Benin'), ('SK', 'Slovakia'), ('GD', 'Grenada'), ('RU', 'Russian Federation'), ('ER', 'Eritrea'), ('RS', 'Serbia'), ('MQ', 'Martinique'), ('ZA', 'South Africa'), ('CA', 'Canada'), ('ST', 'Sao Tome and Principe'), ('LY', 'Libya'), ('CX', 'Christmas Island'), ('MZ', 'Mozambique'), ('GL', 'Greenland'), ('BT', 'Bhutan'), ('PG', 'Papua New Guinea'), ('BD', 'Bangladesh'), ('NL', 'Netherlands'), ('LB', 'Lebanon'), ('CC', 'Cocos (Keeling) Islands'), ('FR', 'France'), ('GH', 'Ghana'), ('KG', 'Kyrgyzstan'), ('GM', 'Gambia'), ('YT', 'Mayotte'), ('MY', 'Malaysia'), ('CZ', 'Czech Republic'), ('LA', "Lao People's Democratic Republic"), ('AR', 'Argentina'), ('IE', 'Ireland'), ('TW', 'Taiwan (Province of China)'), ('GR', 'Greece'), ('NC', 'New Caledonia'), ('OM', 'Oman'), ('GA', 'Gabon'), ('DO', 'Dominican Republic'), ('GE', 'Georgia'), ('KW', 'Kuwait'), ('BB', 'Barbados'), ('GP', 'Guadeloupe'), ('TJ', 'Tajikistan'), ('US', 'United States of America')], default='ES', blank=True)), + ('account', models.ForeignKey(to=settings.AUTH_USER_MODEL, null=True, verbose_name='Account', related_name='contacts')), + ], + ), + ] diff --git a/orchestra/contrib/contacts/migrations/0002_auto_20150408_1411.py b/orchestra/contrib/contacts/migrations/0002_auto_20150408_1411.py new file mode 100644 index 00000000..8c62826e --- /dev/null +++ b/orchestra/contrib/contacts/migrations/0002_auto_20150408_1411.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('contacts', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='contact', + name='country', + field=models.CharField(max_length=20, verbose_name='country', default='ES', blank=True, choices=[('EE', 'Estonia'), ('AI', 'Anguilla'), ('BQ', 'Bonaire, Sint Eustatius and Saba'), ('LB', 'Lebanon'), ('MO', 'Macao'), ('PF', 'French Polynesia'), ('CI', "Côte d'Ivoire"), ('VC', 'Saint Vincent and the Grenadines'), ('PR', 'Puerto Rico'), ('PW', 'Palau'), ('PA', 'Panama'), ('TL', 'Timor-Leste'), ('SD', 'Sudan'), ('TT', 'Trinidad and Tobago'), ('CA', 'Canada'), ('MS', 'Montserrat'), ('DE', 'Germany'), ('HR', 'Croatia'), ('KN', 'Saint Kitts and Nevis'), ('SC', 'Seychelles'), ('TC', 'Turks and Caicos Islands'), ('AR', 'Argentina'), ('VN', 'Viet Nam'), ('NO', 'Norway'), ('MY', 'Malaysia'), ('IO', 'British Indian Ocean Territory'), ('GB', 'United Kingdom of Great Britain and Northern Ireland'), ('PL', 'Poland'), ('BW', 'Botswana'), ('CZ', 'Czech Republic'), ('AG', 'Antigua and Barbuda'), ('TR', 'Turkey'), ('NC', 'New Caledonia'), ('ZM', 'Zambia'), ('MV', 'Maldives'), ('BO', 'Bolivia (Plurinational State of)'), ('CM', 'Cameroon'), ('BZ', 'Belize'), ('SO', 'Somalia'), ('AE', 'United Arab Emirates'), ('YE', 'Yemen'), ('UY', 'Uruguay'), ('GG', 'Guernsey'), ('QA', 'Qatar'), ('IN', 'India'), ('FJ', 'Fiji'), ('MZ', 'Mozambique'), ('CO', 'Colombia'), ('EC', 'Ecuador'), ('SK', 'Slovakia'), ('UM', 'United States Minor Outlying Islands'), ('CR', 'Costa Rica'), ('TV', 'Tuvalu'), ('AZ', 'Azerbaijan'), ('IE', 'Ireland'), ('EG', 'Egypt'), ('GL', 'Greenland'), ('US', 'United States of America'), ('BT', 'Bhutan'), ('ZA', 'South Africa'), ('NP', 'Nepal'), ('TO', 'Tonga'), ('TJ', 'Tajikistan'), ('VG', 'Virgin Islands (British)'), ('RE', 'Réunion'), ('NR', 'Nauru'), ('DK', 'Denmark'), ('LT', 'Lithuania'), ('ET', 'Ethiopia'), ('TZ', 'Tanzania, United Republic of'), ('SB', 'Solomon Islands'), ('VE', 'Venezuela (Bolivarian Republic of)'), ('SG', 'Singapore'), ('MF', 'Saint Martin (French part)'), ('FI', 'Finland'), ('LK', 'Sri Lanka'), ('NU', 'Niue'), ('TM', 'Turkmenistan'), ('SJ', 'Svalbard and Jan Mayen'), ('GN', 'Guinea'), ('BB', 'Barbados'), ('VU', 'Vanuatu'), ('AL', 'Albania'), ('JM', 'Jamaica'), ('ID', 'Indonesia'), ('CL', 'Chile'), ('IT', 'Italy'), ('PT', 'Portugal'), ('KP', "Korea (the Democratic People's Republic of)"), ('MU', 'Mauritius'), ('PY', 'Paraguay'), ('MH', 'Marshall Islands'), ('TH', 'Thailand'), ('IQ', 'Iraq'), ('LR', 'Liberia'), ('NA', 'Namibia'), ('NL', 'Netherlands'), ('AX', 'Åland Islands'), ('MX', 'Mexico'), ('KW', 'Kuwait'), ('ZW', 'Zimbabwe'), ('GQ', 'Equatorial Guinea'), ('UA', 'Ukraine'), ('OM', 'Oman'), ('TD', 'Chad'), ('SR', 'Suriname'), ('KM', 'Comoros'), ('CF', 'Central African Republic'), ('GP', 'Guadeloupe'), ('LU', 'Luxembourg'), ('LC', 'Saint Lucia'), ('SY', 'Syrian Arab Republic'), ('HU', 'Hungary'), ('VA', 'Holy See'), ('KY', 'Cayman Islands'), ('CN', 'China'), ('KR', 'Korea (the Republic of)'), ('DZ', 'Algeria'), ('GE', 'Georgia'), ('CY', 'Cyprus'), ('LA', "Lao People's Democratic Republic"), ('GH', 'Ghana'), ('AT', 'Austria'), ('DO', 'Dominican Republic'), ('MN', 'Mongolia'), ('SI', 'Slovenia'), ('NG', 'Nigeria'), ('GM', 'Gambia'), ('AF', 'Afghanistan'), ('NE', 'Niger'), ('AO', 'Angola'), ('MW', 'Malawi'), ('PN', 'Pitcairn'), ('NZ', 'New Zealand'), ('ST', 'Sao Tome and Principe'), ('SA', 'Saudi Arabia'), ('RW', 'Rwanda'), ('AU', 'Australia'), ('CK', 'Cook Islands'), ('BJ', 'Benin'), ('SS', 'South Sudan'), ('GU', 'Guam'), ('LI', 'Liechtenstein'), ('MR', 'Mauritania'), ('FM', 'Micronesia (Federated States of)'), ('BY', 'Belarus'), ('PH', 'Philippines'), ('ER', 'Eritrea'), ('RU', 'Russian Federation'), ('YT', 'Mayotte'), ('BI', 'Burundi'), ('AS', 'American Samoa'), ('MA', 'Morocco'), ('BD', 'Bangladesh'), ('TN', 'Tunisia'), ('NI', 'Nicaragua'), ('HT', 'Haiti'), ('AQ', 'Antarctica'), ('BR', 'Brazil'), ('FK', 'Falkland Islands [Malvinas]'), ('AM', 'Armenia'), ('MG', 'Madagascar'), ('MC', 'Monaco'), ('IM', 'Isle of Man'), ('KI', 'Kiribati'), ('FO', 'Faroe Islands'), ('SZ', 'Swaziland'), ('GR', 'Greece'), ('PS', 'Palestine, State of'), ('GY', 'Guyana'), ('MQ', 'Martinique'), ('ML', 'Mali'), ('BM', 'Bermuda'), ('CU', 'Cuba'), ('MD', 'Moldova (the Republic of)'), ('CX', 'Christmas Island'), ('TF', 'French Southern Territories'), ('KE', 'Kenya'), ('CH', 'Switzerland'), ('SM', 'San Marino'), ('TK', 'Tokelau'), ('EH', 'Western Sahara'), ('IS', 'Iceland'), ('DM', 'Dominica'), ('SE', 'Sweden'), ('NF', 'Norfolk Island'), ('MM', 'Myanmar'), ('CD', 'Congo (the Democratic Republic of the)'), ('JP', 'Japan'), ('BS', 'Bahamas'), ('ES', 'Spain'), ('GI', 'Gibraltar'), ('AD', 'Andorra'), ('RO', 'Romania'), ('CV', 'Cabo Verde'), ('HN', 'Honduras'), ('GT', 'Guatemala'), ('KG', 'Kyrgyzstan'), ('WS', 'Samoa'), ('PG', 'Papua New Guinea'), ('VI', 'Virgin Islands (U.S.)'), ('BG', 'Bulgaria'), ('RS', 'Serbia'), ('LS', 'Lesotho'), ('GA', 'Gabon'), ('SH', 'Saint Helena, Ascension and Tristan da Cunha'), ('BN', 'Brunei Darussalam'), ('CC', 'Cocos (Keeling) Islands'), ('SN', 'Senegal'), ('BE', 'Belgium'), ('BA', 'Bosnia and Herzegovina'), ('HK', 'Hong Kong'), ('DJ', 'Djibouti'), ('GF', 'French Guiana'), ('UZ', 'Uzbekistan'), ('HM', 'Heard Island and McDonald Islands'), ('IL', 'Israel'), ('MP', 'Northern Mariana Islands'), ('MK', 'Macedonia (the former Yugoslav Republic of)'), ('SX', 'Sint Maarten (Dutch part)'), ('CG', 'Congo'), ('SV', 'El Salvador'), ('AW', 'Aruba'), ('BL', 'Saint Barthélemy'), ('MT', 'Malta'), ('LV', 'Latvia'), ('UG', 'Uganda'), ('PE', 'Peru'), ('KZ', 'Kazakhstan'), ('PK', 'Pakistan'), ('TG', 'Togo'), ('WF', 'Wallis and Futuna'), ('GS', 'South Georgia and the South Sandwich Islands'), ('GD', 'Grenada'), ('PM', 'Saint Pierre and Miquelon'), ('BV', 'Bouvet Island'), ('KH', 'Cambodia'), ('BH', 'Bahrain'), ('GW', 'Guinea-Bissau'), ('ME', 'Montenegro'), ('SL', 'Sierra Leone'), ('IR', 'Iran (Islamic Republic of)'), ('JO', 'Jordan'), ('BF', 'Burkina Faso'), ('LY', 'Libya'), ('FR', 'France'), ('TW', 'Taiwan (Province of China)'), ('CW', 'Curaçao'), ('JE', 'Jersey')]), + ), + ] diff --git a/orchestra/contrib/contacts/migrations/0003_auto_20150408_1412.py b/orchestra/contrib/contacts/migrations/0003_auto_20150408_1412.py new file mode 100644 index 00000000..2d989100 --- /dev/null +++ b/orchestra/contrib/contacts/migrations/0003_auto_20150408_1412.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import orchestra.contrib.contacts.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('contacts', '0002_auto_20150408_1411'), + ] + + operations = [ + migrations.AlterField( + model_name='contact', + name='country', + field=models.CharField(max_length=20, choices=[('CH', 'Switzerland'), ('ES', 'Spain'), ('CV', 'Cabo Verde'), ('GN', 'Guinea'), ('NI', 'Nicaragua'), ('IO', 'British Indian Ocean Territory'), ('FR', 'France'), ('AG', 'Antigua and Barbuda'), ('TW', 'Taiwan (Province of China)'), ('IT', 'Italy'), ('GI', 'Gibraltar'), ('AX', 'Åland Islands'), ('PY', 'Paraguay'), ('AS', 'American Samoa'), ('TD', 'Chad'), ('OM', 'Oman'), ('MU', 'Mauritius'), ('NP', 'Nepal'), ('SG', 'Singapore'), ('US', 'United States of America'), ('NE', 'Niger'), ('HT', 'Haiti'), ('JE', 'Jersey'), ('MZ', 'Mozambique'), ('BT', 'Bhutan'), ('LB', 'Lebanon'), ('CY', 'Cyprus'), ('HM', 'Heard Island and McDonald Islands'), ('IS', 'Iceland'), ('UY', 'Uruguay'), ('MP', 'Northern Mariana Islands'), ('CR', 'Costa Rica'), ('HK', 'Hong Kong'), ('BD', 'Bangladesh'), ('CL', 'Chile'), ('NZ', 'New Zealand'), ('LA', "Lao People's Democratic Republic"), ('ZW', 'Zimbabwe'), ('UZ', 'Uzbekistan'), ('SH', 'Saint Helena, Ascension and Tristan da Cunha'), ('BO', 'Bolivia (Plurinational State of)'), ('ET', 'Ethiopia'), ('DM', 'Dominica'), ('ER', 'Eritrea'), ('TL', 'Timor-Leste'), ('TO', 'Tonga'), ('PK', 'Pakistan'), ('AM', 'Armenia'), ('AO', 'Angola'), ('WF', 'Wallis and Futuna'), ('CU', 'Cuba'), ('UA', 'Ukraine'), ('SX', 'Sint Maarten (Dutch part)'), ('EH', 'Western Sahara'), ('SA', 'Saudi Arabia'), ('MQ', 'Martinique'), ('TM', 'Turkmenistan'), ('SR', 'Suriname'), ('PG', 'Papua New Guinea'), ('BN', 'Brunei Darussalam'), ('KG', 'Kyrgyzstan'), ('BL', 'Saint Barthélemy'), ('HN', 'Honduras'), ('MN', 'Mongolia'), ('YE', 'Yemen'), ('TJ', 'Tajikistan'), ('PR', 'Puerto Rico'), ('GS', 'South Georgia and the South Sandwich Islands'), ('TR', 'Turkey'), ('SJ', 'Svalbard and Jan Mayen'), ('AW', 'Aruba'), ('KP', "Korea (the Democratic People's Republic of)"), ('PA', 'Panama'), ('PN', 'Pitcairn'), ('IR', 'Iran (Islamic Republic of)'), ('YT', 'Mayotte'), ('MM', 'Myanmar'), ('LY', 'Libya'), ('SE', 'Sweden'), ('BW', 'Botswana'), ('ME', 'Montenegro'), ('FO', 'Faroe Islands'), ('EC', 'Ecuador'), ('KW', 'Kuwait'), ('GY', 'Guyana'), ('KR', 'Korea (the Republic of)'), ('BE', 'Belgium'), ('SO', 'Somalia'), ('KY', 'Cayman Islands'), ('NC', 'New Caledonia'), ('AQ', 'Antarctica'), ('MK', 'Macedonia (the former Yugoslav Republic of)'), ('GG', 'Guernsey'), ('FJ', 'Fiji'), ('MR', 'Mauritania'), ('BG', 'Bulgaria'), ('DK', 'Denmark'), ('IN', 'India'), ('EE', 'Estonia'), ('TK', 'Tokelau'), ('TN', 'Tunisia'), ('MH', 'Marshall Islands'), ('BR', 'Brazil'), ('LT', 'Lithuania'), ('GA', 'Gabon'), ('MC', 'Monaco'), ('CZ', 'Czech Republic'), ('KZ', 'Kazakhstan'), ('BY', 'Belarus'), ('SC', 'Seychelles'), ('DZ', 'Algeria'), ('RW', 'Rwanda'), ('SY', 'Syrian Arab Republic'), ('AU', 'Australia'), ('MD', 'Moldova (the Republic of)'), ('ID', 'Indonesia'), ('SD', 'Sudan'), ('GR', 'Greece'), ('LS', 'Lesotho'), ('MA', 'Morocco'), ('SS', 'South Sudan'), ('IL', 'Israel'), ('LR', 'Liberia'), ('CI', "Côte d'Ivoire"), ('AL', 'Albania'), ('MV', 'Maldives'), ('NF', 'Norfolk Island'), ('PE', 'Peru'), ('RO', 'Romania'), ('AI', 'Anguilla'), ('CX', 'Christmas Island'), ('IQ', 'Iraq'), ('PM', 'Saint Pierre and Miquelon'), ('AF', 'Afghanistan'), ('LI', 'Liechtenstein'), ('BF', 'Burkina Faso'), ('EG', 'Egypt'), ('KH', 'Cambodia'), ('MW', 'Malawi'), ('ST', 'Sao Tome and Principe'), ('NG', 'Nigeria'), ('CO', 'Colombia'), ('CW', 'Curaçao'), ('PW', 'Palau'), ('BQ', 'Bonaire, Sint Eustatius and Saba'), ('HR', 'Croatia'), ('LC', 'Saint Lucia'), ('FI', 'Finland'), ('GL', 'Greenland'), ('SM', 'San Marino'), ('MG', 'Madagascar'), ('VI', 'Virgin Islands (U.S.)'), ('TF', 'French Southern Territories'), ('KI', 'Kiribati'), ('NR', 'Nauru'), ('GP', 'Guadeloupe'), ('DO', 'Dominican Republic'), ('SB', 'Solomon Islands'), ('SI', 'Slovenia'), ('MX', 'Mexico'), ('FK', 'Falkland Islands [Malvinas]'), ('BI', 'Burundi'), ('RU', 'Russian Federation'), ('AR', 'Argentina'), ('VC', 'Saint Vincent and the Grenadines'), ('VA', 'Holy See'), ('TV', 'Tuvalu'), ('MF', 'Saint Martin (French part)'), ('CA', 'Canada'), ('MS', 'Montserrat'), ('CG', 'Congo'), ('PS', 'Palestine, State of'), ('DE', 'Germany'), ('ZM', 'Zambia'), ('GE', 'Georgia'), ('QA', 'Qatar'), ('GW', 'Guinea-Bissau'), ('VG', 'Virgin Islands (British)'), ('PH', 'Philippines'), ('GF', 'French Guiana'), ('DJ', 'Djibouti'), ('SK', 'Slovakia'), ('TH', 'Thailand'), ('VE', 'Venezuela (Bolivarian Republic of)'), ('RE', 'Réunion'), ('SL', 'Sierra Leone'), ('TT', 'Trinidad and Tobago'), ('BH', 'Bahrain'), ('AT', 'Austria'), ('CF', 'Central African Republic'), ('VN', 'Viet Nam'), ('PF', 'French Polynesia'), ('UM', 'United States Minor Outlying Islands'), ('AE', 'United Arab Emirates'), ('MY', 'Malaysia'), ('BZ', 'Belize'), ('PT', 'Portugal'), ('BB', 'Barbados'), ('ML', 'Mali'), ('CM', 'Cameroon'), ('GT', 'Guatemala'), ('BS', 'Bahamas'), ('MO', 'Macao'), ('GM', 'Gambia'), ('WS', 'Samoa'), ('ZA', 'South Africa'), ('RS', 'Serbia'), ('TG', 'Togo'), ('NU', 'Niue'), ('VU', 'Vanuatu'), ('JP', 'Japan'), ('KM', 'Comoros'), ('GB', 'United Kingdom of Great Britain and Northern Ireland'), ('SN', 'Senegal'), ('JM', 'Jamaica'), ('KE', 'Kenya'), ('HU', 'Hungary'), ('NO', 'Norway'), ('LK', 'Sri Lanka'), ('KN', 'Saint Kitts and Nevis'), ('GQ', 'Equatorial Guinea'), ('GH', 'Ghana'), ('CN', 'China'), ('NA', 'Namibia'), ('TC', 'Turks and Caicos Islands'), ('JO', 'Jordan'), ('LU', 'Luxembourg'), ('FM', 'Micronesia (Federated States of)'), ('CD', 'Congo (the Democratic Republic of the)'), ('IE', 'Ireland'), ('SZ', 'Swaziland'), ('BJ', 'Benin'), ('BM', 'Bermuda'), ('CK', 'Cook Islands'), ('IM', 'Isle of Man'), ('BV', 'Bouvet Island'), ('PL', 'Poland'), ('SV', 'El Salvador'), ('BA', 'Bosnia and Herzegovina'), ('TZ', 'Tanzania, United Republic of'), ('MT', 'Malta'), ('NL', 'Netherlands'), ('AZ', 'Azerbaijan'), ('UG', 'Uganda'), ('CC', 'Cocos (Keeling) Islands'), ('LV', 'Latvia'), ('GD', 'Grenada'), ('AD', 'Andorra'), ('GU', 'Guam')], blank=True, default='ES', verbose_name='country'), + ), + migrations.AlterField( + model_name='contact', + name='phone', + field=models.CharField(validators=[orchestra.contrib.contacts.models.validate_phone], max_length=32, blank=True, verbose_name='phone'), + ), + migrations.AlterField( + model_name='contact', + name='phone2', + field=models.CharField(validators=[orchestra.contrib.contacts.models.validate_phone], max_length=32, blank=True, verbose_name='alternative phone'), + ), + ] diff --git a/orchestra/contrib/contacts/migrations/__init__.py b/orchestra/contrib/contacts/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/orchestra/contrib/contacts/models.py b/orchestra/contrib/contacts/models.py index 0f29547a..e981b761 100644 --- a/orchestra/contrib/contacts/models.py +++ b/orchestra/contrib/contacts/models.py @@ -7,9 +7,7 @@ from orchestra.core import accounts, validators from orchestra.models.fields import MultiSelectField from . import settings - - -validate_phone = lambda p: validators.validate_phone(p, settings.CONTACTS_DEFAULT_COUNTRY) +from .validators import validate_phone class ContactQuerySet(models.QuerySet): diff --git a/orchestra/contrib/contacts/settings.py b/orchestra/contrib/contacts/settings.py index 331a6f3a..adcfe84d 100644 --- a/orchestra/contrib/contacts/settings.py +++ b/orchestra/contrib/contacts/settings.py @@ -13,12 +13,12 @@ CONTACTS_DEFAULT_EMAIL_USAGES = getattr(settings, 'CONTACTS_DEFAULT_EMAIL_USAGES CONTACTS_DEFAULT_CITY = getattr(settings, 'CONTACTS_DEFAULT_CITY', -'Barcelona' + 'Barcelona' ) -CONTACTS_COUNTRIES = getattr(settings, 'CONTACTS_COUNTRIES', ( - (k,v) for k,v in data.COUNTRIES.items() +CONTACTS_COUNTRIES = getattr(settings, 'CONTACTS_COUNTRIES', tuple( + ((k,v) for k,v in data.COUNTRIES.items()) )) diff --git a/orchestra/contrib/contacts/validators.py b/orchestra/contrib/contacts/validators.py new file mode 100644 index 00000000..c97e4ca7 --- /dev/null +++ b/orchestra/contrib/contacts/validators.py @@ -0,0 +1,7 @@ +from orchestra.core import validators + +from . import settings + + +def validate_phone(phone): + validators.validate_phone(phone, settings.CONTACTS_DEFAULT_COUNTRY) diff --git a/orchestra/contrib/databases/migrations/0001_initial.py b/orchestra/contrib/databases/migrations/0001_initial.py new file mode 100644 index 00000000..24b3d879 --- /dev/null +++ b/orchestra/contrib/databases/migrations/0001_initial.py @@ -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')]), + ), + ] diff --git a/orchestra/contrib/databases/migrations/__init__.py b/orchestra/contrib/databases/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/orchestra/contrib/domains/migrations/0001_initial.py b/orchestra/contrib/domains/migrations/0001_initial.py new file mode 100644 index 00000000..2925a8b9 --- /dev/null +++ b/orchestra/contrib/domains/migrations/0001_initial.py @@ -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')), + ], + ), + ] diff --git a/orchestra/contrib/domains/migrations/__init__.py b/orchestra/contrib/domains/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/orchestra/contrib/issues/migrations/0001_initial.py b/orchestra/contrib/issues/migrations/0001_initial.py new file mode 100644 index 00000000..cd2842ec --- /dev/null +++ b/orchestra/contrib/issues/migrations/0001_initial.py @@ -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')]), + ), + ] diff --git a/orchestra/contrib/issues/migrations/__init__.py b/orchestra/contrib/issues/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/orchestra/contrib/lists/migrations/0001_initial.py b/orchestra/contrib/lists/migrations/0001_initial.py new file mode 100644 index 00000000..186ebe1b --- /dev/null +++ b/orchestra/contrib/lists/migrations/0001_initial.py @@ -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')]), + ), + ] diff --git a/orchestra/contrib/lists/migrations/__init__.py b/orchestra/contrib/lists/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/orchestra/contrib/lists/models.py b/orchestra/contrib/lists/models.py index 5f6f99e6..3f442814 100644 --- a/orchestra/contrib/lists/models.py +++ b/orchestra/contrib/lists/models.py @@ -22,9 +22,9 @@ class List(models.Model): account = models.ForeignKey('accounts.Account', verbose_name=_("Account"), related_name='lists') # TODO also admin - # TODO is_active = models.BooleanField(_("active"), default=True, -# help_text=_("Designates whether this account should be treated as active. " -# "Unselect this instead of deleting accounts.")) + is_active = models.BooleanField(_("active"), default=True, + help_text=_("Designates whether this account should be treated as active. " + "Unselect this instead of deleting accounts.")) password = None class Meta: diff --git a/orchestra/contrib/mailboxes/migrations/0001_initial.py b/orchestra/contrib/mailboxes/migrations/0001_initial.py new file mode 100644 index 00000000..cd339b3a --- /dev/null +++ b/orchestra/contrib/mailboxes/migrations/0001_initial.py @@ -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 catch-all 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')]), + ), + ] diff --git a/orchestra/contrib/mailboxes/migrations/__init__.py b/orchestra/contrib/mailboxes/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/orchestra/contrib/miscellaneous/migrations/0001_initial.py b/orchestra/contrib/miscellaneous/migrations/0001_initial.py index f3ed1690..bd61b3b6 100644 --- a/orchestra/contrib/miscellaneous/migrations/0001_initial.py +++ b/orchestra/contrib/miscellaneous/migrations/0001_initial.py @@ -1,8 +1,10 @@ # -*- coding: utf-8 -*- +from __future__ import unicode_literals from django.db import models, migrations import orchestra.core.validators from django.conf import settings +import orchestra.models.fields class Migration(migrations.Migration): @@ -15,35 +17,32 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Miscellaneous', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('description', models.TextField(verbose_name='description', blank=True)), + ('id', models.AutoField(primary_key=True, verbose_name='ID', auto_created=True, serialize=False)), + ('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')), ('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={ 'verbose_name_plural': 'miscellaneous', }, - bases=(models.Model,), ), migrations.CreateModel( name='MiscService', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('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])), - ('verbose_name', models.CharField(help_text='Human readable name', max_length=256, verbose_name='verbose name', blank=True)), - ('description', models.TextField(help_text='Optional description', verbose_name='description', blank=True)), + ('id', models.AutoField(primary_key=True, verbose_name='ID', auto_created=True, serialize=False)), + ('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(blank=True, max_length=256, verbose_name='verbose name', help_text='Human readable name')), + ('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 unique text field that identifies it or not.', verbose_name='has identifier')), ('has_amount', models.BooleanField(default=False, help_text='Designates whether this service has amount 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')), ], - options={ - }, - bases=(models.Model,), ), migrations.AddField( model_name='miscellaneous', name='service', - field=models.ForeignKey(related_name='instances', verbose_name='service', to='miscellaneous.MiscService'), - preserve_default=True, + field=models.ForeignKey(verbose_name='service', to='miscellaneous.MiscService', related_name='instances'), ), ] diff --git a/orchestra/contrib/miscellaneous/migrations/0002_auto_20141112_1853.py b/orchestra/contrib/miscellaneous/migrations/0002_auto_20141112_1853.py deleted file mode 100644 index d1fd7de1..00000000 --- a/orchestra/contrib/miscellaneous/migrations/0002_auto_20141112_1853.py +++ /dev/null @@ -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 unique text field that identifies it or not.', verbose_name='has identifier'), - preserve_default=True, - ), - ] diff --git a/orchestra/contrib/orchestration/migrations/0001_initial.py b/orchestra/contrib/orchestration/migrations/0001_initial.py new file mode 100644 index 00000000..afa42e94 --- /dev/null +++ b/orchestra/contrib/orchestration/migrations/0001_initial.py @@ -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, instance 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')]), + ), + ] diff --git a/orchestra/contrib/orchestration/migrations/__init__.py b/orchestra/contrib/orchestration/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/orchestra/contrib/orders/migrations/0001_initial.py b/orchestra/contrib/orders/migrations/0001_initial.py new file mode 100644 index 00000000..7d8ecefb --- /dev/null +++ b/orchestra/contrib/orders/migrations/0001_initial.py @@ -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'), + ), + ] diff --git a/orchestra/contrib/orders/migrations/__init__.py b/orchestra/contrib/orders/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/orchestra/contrib/orders/models.py b/orchestra/contrib/orders/models.py index 79489ddc..7b97c69c 100644 --- a/orchestra/contrib/orders/models.py +++ b/orchestra/contrib/orders/models.py @@ -112,7 +112,7 @@ class Order(models.Model): object_id = models.PositiveIntegerField(null=True) service = models.ForeignKey(settings.ORDERS_SERVICE_MODEL, verbose_name=_("service"), 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) billed_on = models.DateField(_("billed"), null=True, blank=True) billed_until = models.DateField(_("billed until"), null=True, blank=True) @@ -137,7 +137,7 @@ class Order(models.Model): else: services = [service] 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 not orders: account_id = getattr(instance, 'account_id', instance.pk) @@ -152,12 +152,16 @@ class Order(models.Model): updates.append((order, 'created')) logger.info("CREATED new order id: {id}".format(id=order.id)) else: - order = orders.get() + if len(orders) > 1: + raise ValueError("A single active order was expected.") + order = orders[0] updates.append((order, 'updated')) if commit: order.update() elif orders: - order = orders.get() + if len(orders) > 1: + raise ValueError("A single active order was expected.") + order = orders[0] order.cancel(commit=commit) logger.info("CANCELLED order id: {id}".format(id=order.id)) updates.append((order, 'cancelled')) @@ -178,7 +182,7 @@ class Order(models.Model): metric = ', metric:{}'.format(metric) description = handler.get_order_description(instance) 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: self.description = description @@ -268,39 +272,26 @@ class MetricStorage(models.Model): 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 - -# FIXME account deletion generates a integrity error +# TODO perhas use cache = caches.get_request_cache() to cache an account delete and don't processes get_related_objects() if the case +# FIXME https://code.djangoproject.com/ticket/24576 # TODO build a cache hash table {model: related, model: None} @receiver(post_delete, dispatch_uid="orders.cancel_orders") def cancel_orders(sender, **kwargs): if sender._meta.app_label not in settings.ORDERS_EXCLUDED_APPS: instance = kwargs['instance'] # Account delete will delete all related orders, no need to maintain order consistency -# if isinstance(instance, Order.account.field.rel.to): -# return - print('delete', sender, instance, instance.pk) + if isinstance(instance, Order.account.field.rel.to): + return if type(instance) in services: for order in Order.objects.by_object(instance).active(): order.cancel() elif not hasattr(instance, 'account'): + # FIXME Indeterminate behaviour related = helpers.get_related_object(instance) - # FIXME this shit returns objects that are already deleted - # Indeterminate behaviour 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) -# 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") def update_orders(sender, **kwargs): diff --git a/orchestra/contrib/plans/migrations/0001_initial.py b/orchestra/contrib/plans/migrations/0001_initial.py new file mode 100644 index 00000000..6b775e7e --- /dev/null +++ b/orchestra/contrib/plans/migrations/0001_initial.py @@ -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')]), + ), + ] diff --git a/orchestra/contrib/plans/migrations/__init__.py b/orchestra/contrib/plans/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/orchestra/contrib/plans/models.py b/orchestra/contrib/plans/models.py index 06fdd632..deb2c196 100644 --- a/orchestra/contrib/plans/models.py +++ b/orchestra/contrib/plans/models.py @@ -15,9 +15,9 @@ from . import rating class Plan(models.Model): name = models.CharField(_("name"), max_length=32, unique=True, validators=[validate_name]) verbose_name = models.CharField(_("verbose_name"), max_length=128, blank=True) - # TODO is_active = models.BooleanField(_("active"), default=True, -# help_text=_("Designates whether this account should be treated as active. " -# "Unselect this instead of deleting accounts.")) + is_active = models.BooleanField(_("active"), default=True, + help_text=_("Designates whether this account should be treated as active. " + "Unselect this instead of deleting accounts.")) is_default = models.BooleanField(_("default"), default=False, help_text=_("Designates whether this plan is used by default or not.")) is_combinable = models.BooleanField(_("combinable"), default=True, diff --git a/orchestra/contrib/resources/admin.py b/orchestra/contrib/resources/admin.py index 6db7fc9c..5479e2ee 100644 --- a/orchestra/contrib/resources/admin.py +++ b/orchestra/contrib/resources/admin.py @@ -21,15 +21,15 @@ from .models import Resource, ResourceData, MonitorData class ResourceAdmin(ExtendedModelAdmin): list_display = ( - 'id', 'verbose_name', 'content_type', 'period', 'on_demand', + 'id', 'verbose_name', 'content_type', 'aggregation', 'on_demand', 'default_allocation', 'unit', 'crontab', 'is_active' ) list_display_links = ('id', 'verbose_name') list_editable = ('default_allocation', 'crontab', 'is_active',) - list_filter = (UsedContentTypeFilter, 'period', 'on_demand', 'disable_trigger') + list_filter = (UsedContentTypeFilter, 'aggregation', 'on_demand', 'disable_trigger') fieldsets = ( (None, { - 'fields': ('verbose_name', 'name', 'content_type', 'period'), + 'fields': ('verbose_name', 'name', 'content_type', 'aggregation'), }), (_("Configuration"), { 'fields': ('unit', 'scale', 'on_demand', 'default_allocation', 'disable_trigger', diff --git a/orchestra/contrib/resources/methods.py b/orchestra/contrib/resources/aggregations.py similarity index 96% rename from orchestra/contrib/resources/methods.py rename to orchestra/contrib/resources/aggregations.py index d11d07a8..7b4c993c 100644 --- a/orchestra/contrib/resources/methods.py +++ b/orchestra/contrib/resources/aggregations.py @@ -7,7 +7,7 @@ from django.utils.translation import ugettext_lazy as _ from orchestra import plugins -class DataMethod(plugins.Plugin, metaclass=plugins.PluginMount): +class Aggregation(plugins.Plugin, metaclass=plugins.PluginMount): """ filters and computes dataset usage """ def filter(self, dataset): """ 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 -class Last(DataMethod): +class Last(Aggregation): name = 'last' verbose_name = _("Last value") diff --git a/orchestra/contrib/resources/migrations/0001_initial.py b/orchestra/contrib/resources/migrations/0001_initial.py index 28dee4d4..8e6677b3 100644 --- a/orchestra/contrib/resources/migrations/0001_initial.py +++ b/orchestra/contrib/resources/migrations/0001_initial.py @@ -1,27 +1,28 @@ # -*- coding: utf-8 -*- +from __future__ import unicode_literals from django.db import models, migrations import orchestra.core.validators import orchestra.contrib.resources.validators -import django.utils.timezone import orchestra.models.fields +import django.utils.timezone class Migration(migrations.Migration): dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), ('djcelery', '__first__'), - ('contenttypes', '0001_initial'), ] operations = [ migrations.CreateModel( name='MonitorData', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('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')])), + ('id', models.AutoField(primary_key=True, auto_created=True, verbose_name='ID', serialize=False)), + ('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')), - ('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)), ('content_type', models.ForeignKey(verbose_name='content type', to='contenttypes.ContentType')), ], @@ -29,44 +30,39 @@ class Migration(migrations.Migration): 'get_latest_by': 'id', 'verbose_name_plural': 'monitor data', }, - bases=(models.Model,), ), migrations.CreateModel( name='Resource', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('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])), - ('verbose_name', models.CharField(max_length=256, verbose_name='verbose name')), - ('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')])), - ('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')), - ('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)), - ('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')), - ('scale', models.CharField(help_text='Scale in which this resource monitoring resoults should be prorcessed to match with unit. e.g. 10**9', max_length=32, verbose_name='scale', validators=[orchestra.contrib.resources.validators.validate_scale])), - ('disable_trigger', models.BooleanField(default=False, help_text='Disables monitors exeeded and recovery triggers', verbose_name='disable trigger')), - ('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')])), - ('is_active', models.BooleanField(default=True, verbose_name='active')), - ('content_type', models.ForeignKey(help_text='Model where this resource will be hooked.', to='contenttypes.ContentType')), - ('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')), + ('id', models.AutoField(primary_key=True, auto_created=True, verbose_name='ID', serialize=False)), + ('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(verbose_name='verbose name', max_length=256)), + ('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(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(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(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(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. 10**9')), + ('disable_trigger', models.BooleanField(verbose_name='disable trigger', default=False, help_text='Disables monitors exeeded and recovery triggers')), + ('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(verbose_name='active', default=True)), + ('content_type', models.ForeignKey(to='contenttypes.ContentType', help_text='Model where this resource will be hooked.')), + ('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( name='ResourceData', 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')), - ('used', models.DecimalField(verbose_name='used', null=True, editable=False, max_digits=16, decimal_places=2)), - ('updated_at', models.DateTimeField(verbose_name='updated', null=True, editable=False)), - ('allocated', models.DecimalField(null=True, verbose_name='allocated', max_digits=8, decimal_places=2, blank=True)), + ('used', models.DecimalField(editable=False, verbose_name='used', max_digits=16, null=True, decimal_places=3)), + ('updated_at', models.DateTimeField(editable=False, verbose_name='updated', null=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')), - ('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={ 'verbose_name_plural': 'resource data', }, - bases=(models.Model,), ), migrations.AlterUniqueTogether( name='resourcedata', @@ -74,6 +70,6 @@ class Migration(migrations.Migration): ), migrations.AlterUniqueTogether( name='resource', - unique_together=set([('name', 'content_type'), ('verbose_name', 'content_type')]), + unique_together=set([('verbose_name', 'content_type'), ('name', 'content_type')]), ), ] diff --git a/orchestra/contrib/resources/migrations/0002_auto_20141117_1415.py b/orchestra/contrib/resources/migrations/0002_auto_20141117_1415.py deleted file mode 100644 index 4a056065..00000000 --- a/orchestra/contrib/resources/migrations/0002_auto_20141117_1415.py +++ /dev/null @@ -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, - ), - ] diff --git a/orchestra/contrib/resources/models.py b/orchestra/contrib/resources/models.py index a8c86ed5..cf09abee 100644 --- a/orchestra/contrib/resources/models.py +++ b/orchestra/contrib/resources/models.py @@ -16,7 +16,7 @@ from orchestra.utils.sys import run from . import tasks from .backends import ServiceMonitor -from .methods import DataMethod +from .aggregations import Aggregation from .validators import validate_scale @@ -47,9 +47,8 @@ class Resource(models.Model): verbose_name = models.CharField(_("verbose name"), max_length=256) content_type = models.ForeignKey(ContentType, help_text=_("Model where this resource will be hooked.")) - # TODO rename to aggregation - period = models.CharField(_("aggregation"), max_length=16, - choices=DataMethod.get_choices(), default=DataMethod.get_choices()[0][0], + aggregation = models.CharField(_("aggregation"), max_length=16, + choices=Aggregation.get_choices(), default=Aggregation.get_choices()[0][0], help_text=_("Method used for aggregating this resource monitored data.")) on_demand = models.BooleanField(_("on demand"), default=False, 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) @cached_property - def method_class(self): - return DataMethod.get(self.period) + def aggregation_class(self): + return Aggregation.get(self.aggregation) @cached_property - def method_instance(self): + def aggregation_instance(self): """ Per request lived type_instance """ - return self.method_class(self) + return self.aggregation_class(self) def clean(self): self.verbose_name = self.verbose_name.strip() @@ -216,7 +215,7 @@ class ResourceData(models.Model): total = 0 has_result = False 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: has_result = True total += usage @@ -258,7 +257,7 @@ class ResourceData(models.Model): object_id__in=pks ) datasets.append( - resource.method_instance.filter(dataset) + resource.aggregation_instance.filter(dataset) ) return datasets diff --git a/orchestra/contrib/saas/migrations/0001_initial.py b/orchestra/contrib/saas/migrations/0001_initial.py new file mode 100644 index 00000000..6df8e2eb --- /dev/null +++ b/orchestra/contrib/saas/migrations/0001_initial.py @@ -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')]), + ), + ] diff --git a/orchestra/contrib/saas/migrations/__init__.py b/orchestra/contrib/saas/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/orchestra/contrib/services/handlers.py b/orchestra/contrib/services/handlers.py index f49661e6..bb512a1f 100644 --- a/orchestra/contrib/services/handlers.py +++ b/orchestra/contrib/services/handlers.py @@ -6,8 +6,8 @@ import math from dateutil import relativedelta from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError -from django.utils import timezone -from django.utils.translation import ugettext_lazy as _ +from django.utils import timezone, translation +from django.utils.translation import ugettext, ugettext_lazy as _ from orchestra import plugins from orchestra.utils.humanize import text2int @@ -130,11 +130,14 @@ class ServiceHandler(plugins.Plugin, metaclass=plugins.PluginMount): safe_locals = { 'instance': instance, 'obj': instance, + 'ugettext': ugettext, instance._meta.model_name: instance, } - if not self.order_description: - return '%s: %s' % (self.description, instance) - return eval(self.order_description, safe_locals) + account = getattr(instance, 'account', instance) + with translation.override(account.language): + 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): not_cachable = self.billing_point == self.FIXED_DATE and options.get('fixed_point') diff --git a/orchestra/contrib/services/migrations/0001_initial.py b/orchestra/contrib/services/migrations/0001_initial.py new file mode 100644 index 00000000..ac35277f --- /dev/null +++ b/orchestra/contrib/services/migrations/0001_initial.py @@ -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 expression that designates wheter a content_type object is related to this service or not, always evaluates True when left blank. Related instance can be instantiated with instance keyword or content_type.model_name.
 databaseuser.type == 'MYSQL'
 miscellaneous.active and str(miscellaneous.identifier).endswith(('.org', '.net', '.com'))
 contractedplan.plan.name == 'association_fee''
 instance.active", 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 expression used for generating the description for the bill lines of this services.
Defaults to '%s: %s' % (handler.description, instance)", 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 trial periods', 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 expression used for obtinging the metric value for the pricing rate computation. Number of orders is used when left blank. Related instance can be instantiated with instance keyword or content_type.model_name.
 max((mailbox.resources.disk.allocated or 0) -1, 0)
 miscellaneous.amount
 max((account.resources.traffic.used or 0) - getattr(account.miscellaneous.filter(is_active=True, service__name='traffic-prepay').last(), 'amount', 0), 0)", 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.
  Step price: All price rates with a lower metric are applied.
  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')), + ], + ), + ] diff --git a/orchestra/contrib/services/migrations/__init__.py b/orchestra/contrib/services/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/orchestra/contrib/services/models.py b/orchestra/contrib/services/models.py index 43a68eae..a816d9d7 100644 --- a/orchestra/contrib/services/models.py +++ b/orchestra/contrib/services/models.py @@ -236,7 +236,7 @@ class Service(models.Model): order_model = get_model(settings.SERVICES_ORDER_MODEL) related_model = self.content_type.model_class() 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) return updates diff --git a/orchestra/contrib/systemusers/migrations/0001_initial.py b/orchestra/contrib/systemusers/migrations/0001_initial.py index 7bb97b0a..2470371d 100644 --- a/orchestra/contrib/systemusers/migrations/0001_initial.py +++ b/orchestra/contrib/systemusers/migrations/0001_initial.py @@ -1,31 +1,30 @@ # -*- coding: utf-8 -*- +from __future__ import unicode_literals from django.db import models, migrations +import orchestra.core.validators from django.conf import settings -import django.core.validators class Migration(migrations.Migration): - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] +# dependencies = [ +# migrations.swappable_dependency(settings.AUTH_USER_MODEL), +# ] operations = [ migrations.CreateModel( name='SystemUser', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('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')])), + ('id', models.AutoField(primary_key=True, auto_created=True, serialize=False, verbose_name='ID')), + ('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')), - ('home', models.CharField(help_text="Home directory relative to account's ~main_user", max_length=256, verbose_name='home', blank=True)), - ('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')])), + ('home', models.CharField(max_length=256, help_text='Starting location when login with this no-shell user.', blank=True, verbose_name='home')), + ('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')), - ('account', models.ForeignKey(related_name='systemusers', verbose_name='Account', to=settings.AUTH_USER_MODEL)), - ('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)), +# ('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?', blank=True, to='systemusers.SystemUser')), ], - options={ - }, - bases=(models.Model,), ), ] diff --git a/orchestra/contrib/systemusers/migrations/0002_systemuser_account.py b/orchestra/contrib/systemusers/migrations/0002_systemuser_account.py new file mode 100644 index 00000000..cf7b9d2d --- /dev/null +++ b/orchestra/contrib/systemusers/migrations/0002_systemuser_account.py @@ -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, + ), + ] diff --git a/orchestra/contrib/systemusers/migrations/0002_systemuser_relative_to_main.py b/orchestra/contrib/systemusers/migrations/0002_systemuser_relative_to_main.py deleted file mode 100644 index 1cbba0f7..00000000 --- a/orchestra/contrib/systemusers/migrations/0002_systemuser_relative_to_main.py +++ /dev/null @@ -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, - ), - ] diff --git a/orchestra/contrib/systemusers/migrations/0003_auto_20141114_1340.py b/orchestra/contrib/systemusers/migrations/0003_auto_20141114_1340.py deleted file mode 100644 index bfb7f7fa..00000000 --- a/orchestra/contrib/systemusers/migrations/0003_auto_20141114_1340.py +++ /dev/null @@ -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, - ), - ] diff --git a/orchestra/contrib/systemusers/models.py b/orchestra/contrib/systemusers/models.py index 95b64ed8..59a8cc96 100644 --- a/orchestra/contrib/systemusers/models.py +++ b/orchestra/contrib/systemusers/models.py @@ -91,7 +91,7 @@ class SystemUser(models.Model): raise ValidationError({ '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({ 'home': _("Shell users should use their own home."), }) diff --git a/orchestra/contrib/vps/migrations/0001_initial.py b/orchestra/contrib/vps/migrations/0001_initial.py new file mode 100644 index 00000000..4ace7e44 --- /dev/null +++ b/orchestra/contrib/vps/migrations/0001_initial.py @@ -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='root 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', + }, + ), + ] diff --git a/orchestra/contrib/vps/migrations/__init__.py b/orchestra/contrib/vps/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/orchestra/contrib/webapps/backends/__init__.py b/orchestra/contrib/webapps/backends/__init__.py index 593f4cad..15b601ea 100644 --- a/orchestra/contrib/webapps/backends/__init__.py +++ b/orchestra/contrib/webapps/backends/__init__.py @@ -42,7 +42,7 @@ class WebAppServiceMixin(object): 'under_construction_path': settings.settings.WEBAPPS_UNDER_CONSTRUCTION_PATH, 'is_mounted': webapp.content_set.exists(), } - replace(context, "'", '"') + return replace(context, "'", '"') for __, module_name, __ in pkgutil.walk_packages(__path__): diff --git a/orchestra/contrib/webapps/backends/php.py b/orchestra/contrib/webapps/backends/php.py index 3d43575e..d57b62ac 100644 --- a/orchestra/contrib/webapps/backends/php.py +++ b/orchestra/contrib/webapps/backends/php.py @@ -179,6 +179,7 @@ class PHPBackend(WebAppServiceMixin, ServiceController): 'cmd_options': self.get_fcgid_cmd_options(webapp, context), 'cmd_options_path': settings.WEBAPPS_FCGID_CMD_OPTIONS_PATH % context, }) + return context def update_fpm_context(self, webapp, context): context.update({ diff --git a/orchestra/contrib/webapps/migrations/0001_initial.py b/orchestra/contrib/webapps/migrations/0001_initial.py index 5b0a2aab..69d09498 100644 --- a/orchestra/contrib/webapps/migrations/0001_initial.py +++ b/orchestra/contrib/webapps/migrations/0001_initial.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- +from __future__ import unicode_literals from django.db import models, migrations +import jsonfield.fields import orchestra.core.validators from django.conf import settings @@ -15,30 +17,29 @@ class Migration(migrations.Migration): migrations.CreateModel( name='WebApp', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('name', models.CharField(max_length=128, verbose_name='name', validators=[orchestra.core.validators.validate_name])), - ('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)')])), - ('account', models.ForeignKey(related_name='webapps', verbose_name='Account', to=settings.AUTH_USER_MODEL)), + ('id', models.AutoField(auto_created=True, primary_key=True, verbose_name='ID', serialize=False)), + ('name', models.CharField(validators=[orchestra.core.validators.validate_name], verbose_name='name', max_length=128)), + ('type', models.CharField(verbose_name='type', choices=[('php', 'PHP'), ('static', 'Static'), ('symbolic-link', 'Symbolic link'), ('webalizer', 'Webalizer'), ('wordpress-php', 'WordPress')], max_length=32)), + ('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={ - 'verbose_name': 'Web App', 'verbose_name_plural': 'Web Apps', + 'verbose_name': 'Web App', }, - bases=(models.Model,), ), migrations.CreateModel( name='WebAppOption', fields=[ - ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), - ('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')])), + ('id', models.AutoField(auto_created=True, primary_key=True, verbose_name='ID', serialize=False)), + ('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')), - ('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={ - 'verbose_name': 'option', 'verbose_name_plural': 'options', + 'verbose_name': 'option', }, - bases=(models.Model,), ), migrations.AlterUniqueTogether( name='webappoption', diff --git a/orchestra/contrib/webapps/migrations/0002_webapp_data.py b/orchestra/contrib/webapps/migrations/0002_webapp_data.py deleted file mode 100644 index 2158f75f..00000000 --- a/orchestra/contrib/webapps/migrations/0002_webapp_data.py +++ /dev/null @@ -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, - ), - ] diff --git a/orchestra/contrib/webapps/migrations/0003_auto_20150310_2103.py b/orchestra/contrib/webapps/migrations/0003_auto_20150310_2103.py deleted file mode 100644 index 31101602..00000000 --- a/orchestra/contrib/webapps/migrations/0003_auto_20150310_2103.py +++ /dev/null @@ -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, - ), - ] diff --git a/orchestra/contrib/websites/apps.py b/orchestra/contrib/websites/apps.py index 33a54ef4..bb4fcf97 100644 --- a/orchestra/contrib/websites/apps.py +++ b/orchestra/contrib/websites/apps.py @@ -1,6 +1,5 @@ from django.apps import AppConfig from django.contrib.contenttypes.fields import GenericRelation -from django.contrib.contenttypes.models import ContentType from orchestra.utils import database_ready @@ -10,6 +9,7 @@ class WebsiteConfig(AppConfig): def ready(self): if database_ready(): + from django.contrib.contenttypes.models import ContentType from .models import Content qset = Content.content_type.field.get_limit_choices_to() for ct in ContentType.objects.filter(qset): diff --git a/orchestra/contrib/websites/backends/apache.py b/orchestra/contrib/websites/backends/apache.py index d3128b4e..b42c3ec1 100644 --- a/orchestra/contrib/websites/backends/apache.py +++ b/orchestra/contrib/websites/backends/apache.py @@ -228,26 +228,6 @@ class Apache2Backend(ServiceController): directive = settings.WEBSITES_SAAS_DIRECTIVES[name] saas += self.get_directives(directive, context) 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(""" -# -# AllowOverride All -# #AuthPAM_Enabled off -# AuthType Basic -# AuthName %s -# AuthUserFile %s -# -# require valid-user -# -# """ % (path, name, passwd) -# ) -# return protections def enable_or_disable(self, site): context = self.get_context(site) @@ -311,6 +291,10 @@ class Apache2Backend(ServiceController): class Apache2Traffic(ServiceMonitor): + """ + Parses apache logs, + looking for the size of each request on the last word of the log line + """ model = 'websites.Website' resource = ServiceMonitor.TRAFFIC verbose_name = _("Apache 2 Traffic") diff --git a/orchestra/contrib/websites/migrations/0001_initial.py b/orchestra/contrib/websites/migrations/0001_initial.py new file mode 100644 index 00000000..5565f0e8 --- /dev/null +++ b/orchestra/contrib/websites/migrations/0001_initial.py @@ -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
HTTPS only performs a redirection from http to https.', 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')]), + ), + ] diff --git a/orchestra/contrib/websites/migrations/__init__.py b/orchestra/contrib/websites/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/orchestra/models/fields.py b/orchestra/models/fields.py index 9a6deab7..3b12deb1 100644 --- a/orchestra/models/fields.py +++ b/orchestra/models/fields.py @@ -22,7 +22,7 @@ class MultiSelectField(models.CharField, metaclass=models.SubfieldBase): def get_db_prep_value(self, value, connection=None, prepared=False): if isinstance(value, str): return value - elif isinstance(value, list): + else: return ','.join(value) def to_python(self, value): @@ -49,7 +49,6 @@ class MultiSelectField(models.CharField, metaclass=models.SubfieldBase): if (opt_select not in arr_choices): msg = self.error_messages['invalid_choice'] % value raise exceptions.ValidationError(msg) - return def get_choices_selected(self, arr_choices=''): if not arr_choices: diff --git a/orchestra/utils/sys.py b/orchestra/utils/sys.py index c4b89030..430862b1 100644 --- a/orchestra/utils/sys.py +++ b/orchestra/utils/sys.py @@ -70,8 +70,8 @@ def runiterator(command, display=False, error_codes=[0], silent=False, stdin='', stdoutPiece = read_async(p.stdout) stderrPiece = read_async(p.stderr) - stdout += (stdoutPiece or b'').decode('utf8') - stderr += (stderrPiece or b'').decode('utf8') + stdout += (stdoutPiece or b'').decode('utf8', errors='replace') + stderr += (stderrPiece or b'').decode('utf8', errors='replace') if display and stdout: sys.stdout.write(stdout)