Compare commits
No commits in common. "master" and "musician" have entirely different histories.
|
@ -0,0 +1,48 @@
|
||||||
|
# Generated by Django 2.2.28 on 2023-07-22 08:01
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import orchestra.core.validators
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('orchestration', '__first__'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='SystemUser',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('username', models.CharField(help_text='Required. 32 characters or fewer. Letters, digits and ./-/_ only.', max_length=32, unique=True, validators=[orchestra.core.validators.validate_username], verbose_name='username')),
|
||||||
|
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||||
|
('home', models.CharField(blank=True, help_text='Starting location when login with this no-shell user.', max_length=256, verbose_name='home')),
|
||||||
|
('directory', models.CharField(blank=True, help_text="Optional directory relative to user's home.", max_length=256, verbose_name='directory')),
|
||||||
|
('shell', models.CharField(choices=[('/dev/null', 'No shell, FTP only'), ('/bin/rssh', 'No shell, SFTP/RSYNC only'), ('/usr/bin/git-shell', 'No shell, GIT only'), ('/bin/bash', '/bin/bash')], default='/dev/null', 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(on_delete=django.db.models.deletion.CASCADE, related_name='systemusers', to=settings.AUTH_USER_MODEL, verbose_name='Account')),
|
||||||
|
('groups', models.ManyToManyField(blank=True, 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')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='WebappUsers',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('username', models.CharField(help_text='Required. 32 characters or fewer. Letters, digits and ./-/_ only.', max_length=32, unique=True, validators=[orchestra.core.validators.validate_username], verbose_name='username')),
|
||||||
|
('password', models.CharField(max_length=128, verbose_name='password')),
|
||||||
|
('home', models.CharField(blank=True, help_text='Starting location when login with this no-shell user.', max_length=256, verbose_name='home')),
|
||||||
|
('shell', models.CharField(choices=[('/dev/null', 'No shell, FTP only'), ('/bin/rssh', 'No shell, SFTP/RSYNC only'), ('/usr/bin/git-shell', 'No shell, GIT only'), ('/bin/bash', '/bin/bash')], default='/dev/null', max_length=32, verbose_name='shell')),
|
||||||
|
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='accounts', to=settings.AUTH_USER_MODEL, verbose_name='Account')),
|
||||||
|
('target_server', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='orchestration.Server', verbose_name='Server')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'unique_together': {('username', 'target_server')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,43 @@
|
||||||
|
from django import forms
|
||||||
|
from django.contrib.auth.forms import ReadOnlyPasswordHashField
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.utils.html import format_html
|
||||||
|
from django.utils.safestring import mark_safe
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from orchestra.core import validators
|
||||||
|
from orchestra.contrib.systemusers.models import WebappUsers
|
||||||
|
from .models import WebApp
|
||||||
|
|
||||||
|
|
||||||
|
class WebappCreationForm(forms.ModelForm):
|
||||||
|
username = forms.CharField(label=_("Username"), max_length=16,
|
||||||
|
required=False, validators=[validators.validate_name],
|
||||||
|
help_text=_("Required. 16 characters or fewer. Letters, digits and "
|
||||||
|
"@/./+/-/_ only."),
|
||||||
|
error_messages={
|
||||||
|
'invalid': _("This value may contain 16 characters or fewer, only letters, numbers and "
|
||||||
|
"@/./+/-/_ characters.")})
|
||||||
|
user = forms.ModelChoiceField(required=False, queryset=WebappUsers.objects)
|
||||||
|
password1 = forms.CharField(label=_("Password"), required=False,
|
||||||
|
widget=forms.PasswordInput(attrs={'autocomplete': 'off'}),
|
||||||
|
validators=[validators.validate_password])
|
||||||
|
password2 = forms.CharField(label=_("Password confirmation"), required=False,
|
||||||
|
widget=forms.PasswordInput,
|
||||||
|
help_text=_("Enter the same password as above, for verification."))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = WebApp
|
||||||
|
fields = ('username', 'account', 'type')
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(WebappCreationForm, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def clean_password2(self):
|
||||||
|
password1 = self.cleaned_data.get("password1")
|
||||||
|
password2 = self.cleaned_data.get("password2")
|
||||||
|
if password1 and password2 and password1 != password2:
|
||||||
|
msg = _("The two password fields didn't match.")
|
||||||
|
raise ValidationError(msg)
|
||||||
|
return password2
|
|
@ -1,6 +1,4 @@
|
||||||
# -*- coding: utf-8 -*-
|
# Generated by Django 2.2.28 on 2023-08-17 09:14
|
||||||
# Generated by Django 1.10.5 on 2021-04-22 11:25
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
@ -10,15 +8,13 @@ import orchestra.core.validators
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
replaces = [('websites', '0001_initial'), ('websites', '0002_auto_20160219_1036'), ('websites', '0003_auto_20170528_2011'), ('websites', '0004_auto_20170625_1813'), ('websites', '0005_auto_20170625_1840'), ('websites', '0006_auto_20170625_1840'), ('websites', '0007_auto_20170625_1840'), ('websites', '0008_auto_20170625_1841'), ('websites', '0009_auto_20170625_2206'), ('websites', '0010_auto_20170625_2214'), ('websites', '0011_auto_20170704_1117'), ('websites', '0012_auto_20190805_1134'), ('websites', '0013_auto_20200204_1217'), ('websites', '0014_auto_20200204_1218'), ('websites', '0015_auto_20200204_1219'), ('websites', '0016_auto_20200204_1221'), ('websites', '0017_auto_20210330_1049')]
|
|
||||||
|
|
||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('domains', '0001_initial'),
|
|
||||||
('webapps', '0001_initial'),
|
|
||||||
('orchestration', '0007_auto_20170528_2011'),
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('orchestration', '__first__'),
|
||||||
|
('domains', '__first__'),
|
||||||
|
('webapps', '0004_auto_20230817_1108'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
@ -37,10 +33,15 @@ class Migration(migrations.Migration):
|
||||||
('name', models.CharField(max_length=128, validators=[orchestra.core.validators.validate_name], verbose_name='name')),
|
('name', models.CharField(max_length=128, validators=[orchestra.core.validators.validate_name], verbose_name='name')),
|
||||||
('protocol', models.CharField(choices=[('http', 'HTTP'), ('https', 'HTTPS'), ('http/https', 'HTTP and HTTPS'), ('https-only', 'HTTPS only')], default='http', help_text='Select the protocol(s) for this website<br><tt>HTTPS only</tt> performs a redirection from <tt>http</tt> to <tt>https</tt>.', max_length=16, verbose_name='protocol')),
|
('protocol', models.CharField(choices=[('http', 'HTTP'), ('https', 'HTTPS'), ('http/https', 'HTTP and HTTPS'), ('https-only', 'HTTPS only')], default='http', help_text='Select the protocol(s) for this website<br><tt>HTTPS only</tt> performs a redirection from <tt>http</tt> to <tt>https</tt>.', max_length=16, verbose_name='protocol')),
|
||||||
('is_active', models.BooleanField(default=True, verbose_name='active')),
|
('is_active', models.BooleanField(default=True, verbose_name='active')),
|
||||||
|
('comments', models.TextField(blank=True, default='')),
|
||||||
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='websites', to=settings.AUTH_USER_MODEL, verbose_name='Account')),
|
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='websites', to=settings.AUTH_USER_MODEL, verbose_name='Account')),
|
||||||
('contents', models.ManyToManyField(through='websites.Content', to='webapps.WebApp')),
|
('contents', models.ManyToManyField(through='websites.Content', to='webapps.WebApp')),
|
||||||
('domains', models.ManyToManyField(related_name='websites', to='domains.Domain', verbose_name='domains')),
|
('domains', models.ManyToManyField(blank=True, related_name='websites', to='domains.Domain', verbose_name='domains')),
|
||||||
|
('target_server', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='websites', to='orchestration.Server', verbose_name='Target Server')),
|
||||||
],
|
],
|
||||||
|
options={
|
||||||
|
'unique_together': {('name', 'account', 'target_server')},
|
||||||
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='WebsiteDirective',
|
name='WebsiteDirective',
|
||||||
|
@ -56,28 +57,8 @@ class Migration(migrations.Migration):
|
||||||
name='website',
|
name='website',
|
||||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='websites.Website', verbose_name='web site'),
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='websites.Website', verbose_name='web site'),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
|
||||||
model_name='website',
|
|
||||||
name='domains',
|
|
||||||
field=models.ManyToManyField(blank=True, related_name='websites', to='domains.Domain', verbose_name='domains'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='website',
|
|
||||||
name='target_server',
|
|
||||||
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='websites', to='orchestration.Server', verbose_name='Target Server'),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='website',
|
|
||||||
name='comments',
|
|
||||||
field=models.TextField(blank=True, default=''),
|
|
||||||
),
|
|
||||||
migrations.AlterUniqueTogether(
|
|
||||||
name='website',
|
|
||||||
unique_together=set([('name', 'account')]),
|
|
||||||
),
|
|
||||||
migrations.AlterUniqueTogether(
|
migrations.AlterUniqueTogether(
|
||||||
name='content',
|
name='content',
|
||||||
unique_together=set([('website', 'path')]),
|
unique_together={('website', 'path')},
|
||||||
),
|
),
|
||||||
]
|
]
|
|
@ -0,0 +1,64 @@
|
||||||
|
# Generated by Django 2.2.28 on 2023-08-17 09:42
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import orchestra.core.validators
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('orchestration', '__first__'),
|
||||||
|
('domains', '__first__'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
('webapps', '0004_auto_20230817_1108'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Content',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('path', models.CharField(blank=True, max_length=256, validators=[orchestra.core.validators.validate_url_path], verbose_name='path')),
|
||||||
|
('webapp', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='webapps.WebApp', verbose_name='web application')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Website',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=128, validators=[orchestra.core.validators.validate_name], verbose_name='name')),
|
||||||
|
('protocol', models.CharField(choices=[('http', 'HTTP'), ('https', 'HTTPS'), ('http/https', 'HTTP and HTTPS'), ('https-only', 'HTTPS only')], default='http', help_text='Select the protocol(s) for this website<br><tt>HTTPS only</tt> performs a redirection from <tt>http</tt> to <tt>https</tt>.', max_length=16, verbose_name='protocol')),
|
||||||
|
('is_active', models.BooleanField(default=True, verbose_name='active')),
|
||||||
|
('comments', models.TextField(blank=True, default='')),
|
||||||
|
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='websites', to=settings.AUTH_USER_MODEL, verbose_name='Account')),
|
||||||
|
('contents', models.ManyToManyField(through='websites.Content', to='webapps.WebApp')),
|
||||||
|
('domains', models.ManyToManyField(blank=True, related_name='websites', to='domains.Domain', verbose_name='domains')),
|
||||||
|
('target_server', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='websites', to='orchestration.Server', verbose_name='Target Server')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'unique_together': {('name', 'account')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='WebsiteDirective',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(choices=[(None, '-------'), ('HTTPD', [('redirect', 'Redirection'), ('proxy', 'Proxy'), ('error-document', 'ErrorDocumentRoot')]), ('SSL', [('ssl-ca', 'SSL CA'), ('ssl-cert', 'SSL cert'), ('ssl-key', 'SSL key')]), ('ModSecurity', [('sec-rule-remove', 'SecRuleRemoveById'), ('sec-engine', 'SecRuleEngine Off')]), ('SaaS', [('wordpress-saas', 'WordPress SaaS'), ('dokuwiki-saas', 'DokuWiki SaaS'), ('drupal-saas', 'Drupdal SaaS'), ('moodle-saas', 'Moodle SaaS')])], db_index=True, max_length=128, verbose_name='name')),
|
||||||
|
('value', models.CharField(blank=True, max_length=256, verbose_name='value')),
|
||||||
|
('website', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='directives', to='websites.Website', verbose_name='web site')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='content',
|
||||||
|
name='website',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='websites.Website', verbose_name='web site'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='content',
|
||||||
|
unique_together={('website', 'path')},
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,64 @@
|
||||||
|
# Generated by Django 2.2.28 on 2023-08-17 09:08
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import orchestra.core.validators
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('domains', '__first__'),
|
||||||
|
('orchestration', '__first__'),
|
||||||
|
('webapps', '0004_auto_20230817_1108'),
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Content',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('path', models.CharField(blank=True, max_length=256, validators=[orchestra.core.validators.validate_url_path], verbose_name='path')),
|
||||||
|
('webapp', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='webapps.WebApp', verbose_name='web application')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Website',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=128, validators=[orchestra.core.validators.validate_name], verbose_name='name')),
|
||||||
|
('protocol', models.CharField(choices=[('http', 'HTTP'), ('https', 'HTTPS'), ('http/https', 'HTTP and HTTPS'), ('https-only', 'HTTPS only')], default='http', help_text='Select the protocol(s) for this website<br><tt>HTTPS only</tt> performs a redirection from <tt>http</tt> to <tt>https</tt>.', max_length=16, verbose_name='protocol')),
|
||||||
|
('is_active', models.BooleanField(default=True, verbose_name='active')),
|
||||||
|
('comments', models.TextField(blank=True, default='')),
|
||||||
|
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='websites', to=settings.AUTH_USER_MODEL, verbose_name='Account')),
|
||||||
|
('contents', models.ManyToManyField(through='websites.Content', to='webapps.WebApp')),
|
||||||
|
('domains', models.ManyToManyField(blank=True, related_name='websites', to='domains.Domain', verbose_name='domains')),
|
||||||
|
('target_server', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='websites', to='orchestration.Server', verbose_name='Target Server')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'unique_together': {('name', 'account', 'target_server')},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='WebsiteDirective',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(choices=[(None, '-------'), ('HTTPD', [('redirect', 'Redirection'), ('proxy', 'Proxy'), ('error-document', 'ErrorDocumentRoot')]), ('SSL', [('ssl-ca', 'SSL CA'), ('ssl-cert', 'SSL cert'), ('ssl-key', 'SSL key')]), ('ModSecurity', [('sec-rule-remove', 'SecRuleRemoveById'), ('sec-engine', 'SecRuleEngine Off')]), ('SaaS', [('wordpress-saas', 'WordPress SaaS'), ('dokuwiki-saas', 'DokuWiki SaaS'), ('drupal-saas', 'Drupdal SaaS'), ('moodle-saas', 'Moodle SaaS')])], db_index=True, max_length=128, verbose_name='name')),
|
||||||
|
('value', models.CharField(blank=True, max_length=256, verbose_name='value')),
|
||||||
|
('website', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='directives', to='websites.Website', verbose_name='web site')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='content',
|
||||||
|
name='website',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='websites.Website', verbose_name='web site'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='content',
|
||||||
|
unique_together={('website', 'path')},
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,3 @@
|
||||||
|
[Trash Info]
|
||||||
|
Path=orchestra/contrib/systemusers/migrations/0001_initial.py
|
||||||
|
DeletionDate=2023-07-22T10:04:42
|
|
@ -0,0 +1,3 @@
|
||||||
|
[Trash Info]
|
||||||
|
Path=orchestra/contrib/webapps/forms.py
|
||||||
|
DeletionDate=2023-07-24T18:44:25
|
|
@ -0,0 +1,3 @@
|
||||||
|
[Trash Info]
|
||||||
|
Path=orchestra/contrib/websites/migrations
|
||||||
|
DeletionDate=2023-08-17T11:42:31
|
|
@ -0,0 +1,3 @@
|
||||||
|
[Trash Info]
|
||||||
|
Path=orchestra/contrib/websites/migrations
|
||||||
|
DeletionDate=2023-08-17T11:45:11
|
|
@ -0,0 +1,3 @@
|
||||||
|
[Trash Info]
|
||||||
|
Path=orchestra/contrib/websites/migrations
|
||||||
|
DeletionDate=2023-08-17T11:14:47
|
|
@ -0,0 +1,42 @@
|
||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ignoredPackages">
|
||||||
|
<value>
|
||||||
|
<list size="24">
|
||||||
|
<item index="0" class="java.lang.String" itemvalue="paramiko" />
|
||||||
|
<item index="1" class="java.lang.String" itemvalue="fabric" />
|
||||||
|
<item index="2" class="java.lang.String" itemvalue="django-admin-tools" />
|
||||||
|
<item index="3" class="java.lang.String" itemvalue="django-fluent-dashboard" />
|
||||||
|
<item index="4" class="java.lang.String" itemvalue="phonenumbers" />
|
||||||
|
<item index="5" class="java.lang.String" itemvalue="jsonfield" />
|
||||||
|
<item index="6" class="java.lang.String" itemvalue="amqp" />
|
||||||
|
<item index="7" class="java.lang.String" itemvalue="python-dateutil" />
|
||||||
|
<item index="8" class="java.lang.String" itemvalue="django-localflavor" />
|
||||||
|
<item index="9" class="java.lang.String" itemvalue="ecdsa" />
|
||||||
|
<item index="10" class="java.lang.String" itemvalue="kombu" />
|
||||||
|
<item index="11" class="java.lang.String" itemvalue="django-extensions" />
|
||||||
|
<item index="12" class="java.lang.String" itemvalue="celery" />
|
||||||
|
<item index="13" class="java.lang.String" itemvalue="django-countries" />
|
||||||
|
<item index="14" class="java.lang.String" itemvalue="Pygments" />
|
||||||
|
<item index="15" class="java.lang.String" itemvalue="django-filter" />
|
||||||
|
<item index="16" class="java.lang.String" itemvalue="Django" />
|
||||||
|
<item index="17" class="java.lang.String" itemvalue="anyjson" />
|
||||||
|
<item index="18" class="java.lang.String" itemvalue="django-celery" />
|
||||||
|
<item index="19" class="java.lang.String" itemvalue="djangorestframework" />
|
||||||
|
<item index="20" class="java.lang.String" itemvalue="Markdown" />
|
||||||
|
<item index="21" class="java.lang.String" itemvalue="django-iban" />
|
||||||
|
<item index="22" class="java.lang.String" itemvalue="billiard" />
|
||||||
|
<item index="23" class="java.lang.String" itemvalue="passlib" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
|
||||||
|
<option name="processCode" value="true" />
|
||||||
|
<option name="processLiterals" value="true" />
|
||||||
|
<option name="processComments" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="JavaScriptSettings">
|
||||||
|
<option name="languageLevel" value="ES6" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.7 (zfs-blues)" project-jdk-type="Python SDK" />
|
||||||
|
</project>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/orchestra.iml" filepath="$PROJECT_DIR$/.idea/orchestra.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="FacetManager">
|
||||||
|
<facet type="django" name="Django">
|
||||||
|
<configuration>
|
||||||
|
<option name="rootFolder" value="$MODULE_DIR$/orchestra/conf/project_template" />
|
||||||
|
<option name="settingsModule" value="orchestra/settings.py" />
|
||||||
|
<option name="manageScript" value="manage.py" />
|
||||||
|
<option name="environment" value="<map/>" />
|
||||||
|
<option name="doNotUseTestRunner" value="false" />
|
||||||
|
<option name="trackFilePattern" value="migrations" />
|
||||||
|
</configuration>
|
||||||
|
</facet>
|
||||||
|
</component>
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
<component name="TemplatesService">
|
||||||
|
<option name="TEMPLATE_CONFIGURATION" value="Django" />
|
||||||
|
<option name="TEMPLATE_FOLDERS">
|
||||||
|
<list>
|
||||||
|
<option value="$MODULE_DIR$/orchestra/contrib/bills/templates" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="TestRunnerService">
|
||||||
|
<option name="projectConfiguration" value="pytest" />
|
||||||
|
<option name="PROJECT_TEST_RUNNER" value="pytest" />
|
||||||
|
</component>
|
||||||
|
</module>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -0,0 +1,261 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ChangeListManager">
|
||||||
|
<list default="true" id="573278bf-2cdd-4afd-973a-1660f3364e9d" name="Default Changelist" comment="">
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/api/actions.py" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/api/actions.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/api/serializers.py" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/api/serializers.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/accounts/models.py" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/accounts/models.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/accounts/serializers.py" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/accounts/serializers.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/bills/admin.py" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/bills/admin.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/bills/locale/ca/LC_MESSAGES/django.mo" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/bills/locale/ca/LC_MESSAGES/django.mo" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/bills/locale/ca/LC_MESSAGES/django.po" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/bills/locale/ca/LC_MESSAGES/django.po" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/bills/locale/es/LC_MESSAGES/django.mo" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/bills/locale/es/LC_MESSAGES/django.mo" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/bills/locale/es/LC_MESSAGES/django.po" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/bills/locale/es/LC_MESSAGES/django.po" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/bills/models.py" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/bills/models.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/bills/settings.py" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/bills/settings.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/bills/templates/bills/microspective.css" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/bills/templates/bills/microspective.css" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/bills/templates/bills/microspective.html" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/bills/templates/bills/microspective.html" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/databases/admin.py" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/databases/admin.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/databases/models.py" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/databases/models.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/domains/admin.py" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/domains/admin.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/domains/backends.py" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/domains/backends.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/domains/forms.py" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/domains/forms.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/domains/models.py" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/domains/models.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/domains/settings.py" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/domains/settings.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/domains/validators.py" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/domains/validators.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/lists/api.py" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/lists/api.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/lists/backends.py" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/lists/backends.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/lists/serializers.py" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/lists/serializers.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/mailboxes/backends.py" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/mailboxes/backends.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/orchestration/backends.py" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/orchestration/backends.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/payments/actions.py" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/payments/actions.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/payments/methods/sepadirectdebit.py" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/payments/methods/sepadirectdebit.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/saas/models.py" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/saas/models.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/saas/services/helpers.py" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/saas/services/helpers.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/systemusers/backends.py" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/systemusers/backends.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/systemusers/models.py" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/systemusers/models.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/webapps/backends/php.py" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/webapps/backends/php.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/webapps/models.py" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/webapps/models.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/webapps/options.py" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/webapps/options.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/webapps/serializers.py" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/webapps/serializers.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/webapps/signals.py" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/webapps/signals.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/websites/admin.py" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/websites/admin.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/websites/backends/apache.py" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/websites/backends/apache.py" afterDir="false" />
|
||||||
|
<change beforePath="$PROJECT_DIR$/orchestra/contrib/websites/models.py" beforeDir="false" afterPath="$PROJECT_DIR$/orchestra/contrib/websites/models.py" afterDir="false" />
|
||||||
|
</list>
|
||||||
|
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
|
||||||
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
|
</component>
|
||||||
|
<component name="FileEditorManager">
|
||||||
|
<leaf SIDE_TABS_SIZE_LIMIT_KEY="300">
|
||||||
|
<file pinned="false" current-in-tab="true">
|
||||||
|
<entry file="file://$PROJECT_DIR$/orchestra/contrib/lists/backends.py">
|
||||||
|
<provider selected="true" editor-type-id="text-editor">
|
||||||
|
<state relative-caret-position="589">
|
||||||
|
<caret line="124" column="4" lean-forward="true" selection-start-line="124" selection-start-column="4" selection-end-line="124" selection-end-column="4" />
|
||||||
|
</state>
|
||||||
|
</provider>
|
||||||
|
</entry>
|
||||||
|
</file>
|
||||||
|
<file pinned="false" current-in-tab="false">
|
||||||
|
<entry file="file://$PROJECT_DIR$/orchestra/contrib/orchestration/backends.py">
|
||||||
|
<provider selected="true" editor-type-id="text-editor">
|
||||||
|
<state relative-caret-position="4256">
|
||||||
|
<caret line="224" column="8" selection-start-line="224" selection-start-column="8" selection-end-line="224" selection-end-column="8" />
|
||||||
|
</state>
|
||||||
|
</provider>
|
||||||
|
</entry>
|
||||||
|
</file>
|
||||||
|
</leaf>
|
||||||
|
</component>
|
||||||
|
<component name="FindInProjectRecents">
|
||||||
|
<findStrings>
|
||||||
|
<find>check_origin</find>
|
||||||
|
<find>updated_</find>
|
||||||
|
<find>UPDATED_CHECK_ORIGIN</find>
|
||||||
|
</findStrings>
|
||||||
|
<dirStrings>
|
||||||
|
<dir>$PROJECT_DIR$/orchestra/contrib/lists</dir>
|
||||||
|
</dirStrings>
|
||||||
|
</component>
|
||||||
|
<component name="Git.Settings">
|
||||||
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
|
<component name="IdeDocumentHistory">
|
||||||
|
<option name="CHANGED_PATHS">
|
||||||
|
<list>
|
||||||
|
<option value="$PROJECT_DIR$/orchestra/contrib/lists/backends.py" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="ProjectConfigurationFiles">
|
||||||
|
<option name="files">
|
||||||
|
<list>
|
||||||
|
<option value="$PROJECT_DIR$/.idea/orchestra.iml" />
|
||||||
|
<option value="$PROJECT_DIR$/.idea/misc.xml" />
|
||||||
|
<option value="$PROJECT_DIR$/.idea/modules.xml" />
|
||||||
|
<option value="$PROJECT_DIR$/.idea/vcs.xml" />
|
||||||
|
<option value="$PROJECT_DIR$/.idea/inspectionProfiles/Project_Default.xml" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="ProjectFrameBounds" extendedState="6">
|
||||||
|
<option name="x" value="959" />
|
||||||
|
<option name="width" value="961" />
|
||||||
|
<option name="height" value="1056" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectView">
|
||||||
|
<navigator proportions="" version="1">
|
||||||
|
<foldersAlwaysOnTop value="true" />
|
||||||
|
</navigator>
|
||||||
|
<panes>
|
||||||
|
<pane id="ProjectPane">
|
||||||
|
<subPane>
|
||||||
|
<expand>
|
||||||
|
<path>
|
||||||
|
<item name="orchestra" type="b2602c69:ProjectViewProjectNode" />
|
||||||
|
<item name="orchestra" type="462c0819:PsiDirectoryNode" />
|
||||||
|
</path>
|
||||||
|
<path>
|
||||||
|
<item name="orchestra" type="b2602c69:ProjectViewProjectNode" />
|
||||||
|
<item name="orchestra" type="462c0819:PsiDirectoryNode" />
|
||||||
|
<item name="orchestra" type="462c0819:PsiDirectoryNode" />
|
||||||
|
</path>
|
||||||
|
<path>
|
||||||
|
<item name="orchestra" type="b2602c69:ProjectViewProjectNode" />
|
||||||
|
<item name="orchestra" type="462c0819:PsiDirectoryNode" />
|
||||||
|
<item name="orchestra" type="462c0819:PsiDirectoryNode" />
|
||||||
|
<item name="contrib" type="462c0819:PsiDirectoryNode" />
|
||||||
|
</path>
|
||||||
|
<path>
|
||||||
|
<item name="orchestra" type="b2602c69:ProjectViewProjectNode" />
|
||||||
|
<item name="orchestra" type="462c0819:PsiDirectoryNode" />
|
||||||
|
<item name="orchestra" type="462c0819:PsiDirectoryNode" />
|
||||||
|
<item name="contrib" type="462c0819:PsiDirectoryNode" />
|
||||||
|
<item name="lists" type="462c0819:PsiDirectoryNode" />
|
||||||
|
</path>
|
||||||
|
</expand>
|
||||||
|
<select />
|
||||||
|
</subPane>
|
||||||
|
</pane>
|
||||||
|
<pane id="Scope" />
|
||||||
|
</panes>
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent">
|
||||||
|
<property name="WebServerToolWindowFactoryState" value="false" />
|
||||||
|
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
|
||||||
|
<property name="node.js.detected.package.eslint" value="true" />
|
||||||
|
<property name="node.js.detected.package.tslint" value="true" />
|
||||||
|
<property name="node.js.path.for.package.eslint" value="project" />
|
||||||
|
<property name="node.js.path.for.package.tslint" value="project" />
|
||||||
|
<property name="node.js.selected.package.eslint" value="(autodetect)" />
|
||||||
|
<property name="node.js.selected.package.tslint" value="(autodetect)" />
|
||||||
|
<property name="nodejs_interpreter_path.stuck_in_default_project" value="undefined stuck path" />
|
||||||
|
<property name="nodejs_npm_path_reset_for_default_project" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="RunDashboard">
|
||||||
|
<option name="ruleStates">
|
||||||
|
<list>
|
||||||
|
<RuleState>
|
||||||
|
<option name="name" value="ConfigurationTypeDashboardGroupingRule" />
|
||||||
|
</RuleState>
|
||||||
|
<RuleState>
|
||||||
|
<option name="name" value="StatusDashboardGroupingRule" />
|
||||||
|
</RuleState>
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="RunManager">
|
||||||
|
<configuration name="orchestra" type="Python.DjangoServer" factoryName="Django server">
|
||||||
|
<module name="orchestra" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="PARENT_ENVS" value="true" />
|
||||||
|
<envs>
|
||||||
|
<env name="PYTHONUNBUFFERED" value="1" />
|
||||||
|
</envs>
|
||||||
|
<option name="SDK_HOME" value="" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="" />
|
||||||
|
<option name="IS_MODULE_SDK" value="false" />
|
||||||
|
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||||
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
|
<option name="launchJavascriptDebuger" value="false" />
|
||||||
|
<option name="port" value="8000" />
|
||||||
|
<option name="host" value="" />
|
||||||
|
<option name="additionalOptions" value="" />
|
||||||
|
<option name="browserUrl" value="" />
|
||||||
|
<option name="runTestServer" value="false" />
|
||||||
|
<option name="runNoReload" value="false" />
|
||||||
|
<option name="useCustomRunCommand" value="false" />
|
||||||
|
<option name="customRunCommand" value="" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
||||||
|
<component name="SvnConfiguration">
|
||||||
|
<configuration />
|
||||||
|
</component>
|
||||||
|
<component name="TaskManager">
|
||||||
|
<task active="true" id="Default" summary="Default task">
|
||||||
|
<changelist id="573278bf-2cdd-4afd-973a-1660f3364e9d" name="Default Changelist" comment="" />
|
||||||
|
<created>1582799032840</created>
|
||||||
|
<option name="number" value="Default" />
|
||||||
|
<option name="presentableId" value="Default" />
|
||||||
|
<updated>1582799032840</updated>
|
||||||
|
<workItem from="1582799053215" duration="2986000" />
|
||||||
|
<workItem from="1582882260836" duration="4157000" />
|
||||||
|
<workItem from="1582890671958" duration="815000" />
|
||||||
|
</task>
|
||||||
|
<servers />
|
||||||
|
</component>
|
||||||
|
<component name="TimeTrackingManager">
|
||||||
|
<option name="totallyTimeSpent" value="7958000" />
|
||||||
|
</component>
|
||||||
|
<component name="ToolWindowManager">
|
||||||
|
<frame x="-1" y="0" width="1922" height="1057" extended-state="6" />
|
||||||
|
<editor active="true" />
|
||||||
|
<layout>
|
||||||
|
<window_info active="true" content_ui="combo" id="Project" order="0" visible="true" weight="0.124068156" />
|
||||||
|
<window_info id="Structure" order="1" side_tool="true" weight="0.25" />
|
||||||
|
<window_info id="Favorites" order="2" side_tool="true" />
|
||||||
|
<window_info anchor="bottom" id="Message" order="0" />
|
||||||
|
<window_info anchor="bottom" id="Find" order="1" />
|
||||||
|
<window_info anchor="bottom" id="Run" order="2" />
|
||||||
|
<window_info anchor="bottom" id="Debug" order="3" weight="0.4" />
|
||||||
|
<window_info anchor="bottom" id="Cvs" order="4" weight="0.25" />
|
||||||
|
<window_info anchor="bottom" id="Inspection" order="5" weight="0.4" />
|
||||||
|
<window_info anchor="bottom" id="TODO" order="6" />
|
||||||
|
<window_info anchor="bottom" id="Docker" order="7" show_stripe_button="false" />
|
||||||
|
<window_info anchor="bottom" id="Version Control" order="8" />
|
||||||
|
<window_info anchor="bottom" id="Database Changes" order="9" />
|
||||||
|
<window_info anchor="bottom" id="Event Log" order="10" side_tool="true" />
|
||||||
|
<window_info anchor="bottom" id="Terminal" order="11" />
|
||||||
|
<window_info anchor="bottom" id="Python Console" order="12" />
|
||||||
|
<window_info anchor="right" id="Commander" internal_type="SLIDING" order="0" type="SLIDING" weight="0.4" />
|
||||||
|
<window_info anchor="right" id="Ant Build" order="1" weight="0.25" />
|
||||||
|
<window_info anchor="right" content_ui="combo" id="Hierarchy" order="2" weight="0.25" />
|
||||||
|
<window_info anchor="right" id="SciView" order="3" />
|
||||||
|
<window_info anchor="right" id="Database" order="4" />
|
||||||
|
</layout>
|
||||||
|
</component>
|
||||||
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
|
<option name="version" value="1" />
|
||||||
|
</component>
|
||||||
|
<component name="editorHistoryManager">
|
||||||
|
<entry file="file://$PROJECT_DIR$/orchestra/contrib/lists/backends.py">
|
||||||
|
<provider selected="true" editor-type-id="text-editor">
|
||||||
|
<state relative-caret-position="589">
|
||||||
|
<caret line="124" column="4" lean-forward="true" selection-start-line="124" selection-start-column="4" selection-end-line="124" selection-end-column="4" />
|
||||||
|
</state>
|
||||||
|
</provider>
|
||||||
|
</entry>
|
||||||
|
<entry file="file://$PROJECT_DIR$/orchestra/contrib/orchestration/backends.py">
|
||||||
|
<provider selected="true" editor-type-id="text-editor">
|
||||||
|
<state relative-caret-position="4256">
|
||||||
|
<caret line="224" column="8" selection-start-line="224" selection-start-column="8" selection-end-line="224" selection-end-column="8" />
|
||||||
|
</state>
|
||||||
|
</provider>
|
||||||
|
</entry>
|
||||||
|
</component>
|
||||||
|
</project>
|
4
TODO.md
4
TODO.md
|
@ -170,10 +170,10 @@ django-admin.py compilemessages -l ca
|
||||||
|
|
||||||
https://docs.djangoproject.com/en/1.7/topics/i18n/translation/#joining-strings-string-concat
|
https://docs.djangoproject.com/en/1.7/topics/i18n/translation/#joining-strings-string-concat
|
||||||
|
|
||||||
from django.utils.translation import ugettext
|
from django.utils.translation import gettext
|
||||||
from django.utils import translation
|
from django.utils import translation
|
||||||
translation.activate('ca')
|
translation.activate('ca')
|
||||||
ugettext("Description")
|
gettext("Description")
|
||||||
|
|
||||||
* saas validate_creation generic approach, for all backends. standard output
|
* saas validate_creation generic approach, for all backends. standard output
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,7 @@
|
||||||
```python
|
```python
|
||||||
import os
|
import os
|
||||||
import textwrap
|
import textwrap
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from orchestra.contrib.orchestration import ServiceController, replace
|
from orchestra.contrib.orchestration import ServiceController, replace
|
||||||
from orchestra.contrib.resources import ServiceMonitor
|
from orchestra.contrib.resources import ServiceMonitor
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ from django.contrib import admin
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.shortcuts import render, redirect
|
from django.shortcuts import render, redirect
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from .dashboard import *
|
from .dashboard import *
|
||||||
from .options import *
|
from .options import *
|
||||||
|
|
|
@ -3,7 +3,7 @@ from functools import partial
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.core.mail import send_mass_mail
|
from django.core.mail import send_mass_mail
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.utils.translation import ungettext, ugettext_lazy as _
|
from django.utils.translation import ngettext, gettext_lazy as _
|
||||||
|
|
||||||
from .. import settings
|
from .. import settings
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ class SendEmail(object):
|
||||||
if extra_to:
|
if extra_to:
|
||||||
emails.append((subject, message, email_from, extra_to))
|
emails.append((subject, message, email_from, extra_to))
|
||||||
send_mass_mail(emails, fail_silently=False)
|
send_mass_mail(emails, fail_silently=False)
|
||||||
msg = ungettext(
|
msg = ngettext(
|
||||||
_("Message has been sent to one %s.") % self.opts.verbose_name_plural,
|
_("Message has been sent to one %s.") % self.opts.verbose_name_plural,
|
||||||
_("Message has been sent to %i %s.") % (num, self.opts.verbose_name_plural),
|
_("Message has been sent to %i %s.") % (num, self.opts.verbose_name_plural),
|
||||||
num
|
num
|
||||||
|
@ -124,7 +124,7 @@ def base_disable(modeladmin, request, queryset, disable=True):
|
||||||
'verbose_name_plural': opts.verbose_name_plural,
|
'verbose_name_plural': opts.verbose_name_plural,
|
||||||
'num': num
|
'num': num
|
||||||
}
|
}
|
||||||
msg = ungettext(
|
msg = ngettext(
|
||||||
_("Selected %(verbose_name)s and related services has been %(action_name)s.") % context,
|
_("Selected %(verbose_name)s and related services has been %(action_name)s.") % context,
|
||||||
_("%(num)s selected %(verbose_name_plural)s and related services have been %(action_name)s.") % context,
|
_("%(num)s selected %(verbose_name_plural)s and related services have been %(action_name)s.") % context,
|
||||||
num)
|
num)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from fluent_dashboard import dashboard, appsettings
|
from fluent_dashboard import dashboard, appsettings
|
||||||
from fluent_dashboard.modules import CmsAppIconList
|
from fluent_dashboard.modules import CmsAppIconList
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,10 @@ from django.contrib import messages
|
||||||
from django.contrib.admin import helpers
|
from django.contrib.admin import helpers
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.utils.decorators import available_attrs
|
from django.utils.encoding import force_str
|
||||||
from django.utils.encoding import force_text
|
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
from django.utils.text import capfirst
|
from django.utils.text import capfirst
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
def admin_field(method):
|
def admin_field(method):
|
||||||
|
@ -50,7 +49,7 @@ def action_with_confirmation(action_name=None, extra_context=None, validator=Non
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(func, extra_context=extra_context, template=template, action_name=action_name, validatior=validator):
|
def decorator(func, extra_context=extra_context, template=template, action_name=action_name, validatior=validator):
|
||||||
@wraps(func, assigned=available_attrs(func))
|
@wraps(func)
|
||||||
def inner(modeladmin, request, queryset, action_name=action_name, extra_context=extra_context, validator=validator):
|
def inner(modeladmin, request, queryset, action_name=action_name, extra_context=extra_context, validator=validator):
|
||||||
if validator is not None:
|
if validator is not None:
|
||||||
try:
|
try:
|
||||||
|
@ -69,10 +68,10 @@ def action_with_confirmation(action_name=None, extra_context=None, validator=Non
|
||||||
action_value = func.__name__
|
action_value = func.__name__
|
||||||
|
|
||||||
if len(queryset) == 1:
|
if len(queryset) == 1:
|
||||||
objects_name = force_text(opts.verbose_name)
|
objects_name = force_str(opts.verbose_name)
|
||||||
obj = queryset.get()
|
obj = queryset.get()
|
||||||
else:
|
else:
|
||||||
objects_name = force_text(opts.verbose_name_plural)
|
objects_name = force_str(opts.verbose_name_plural)
|
||||||
obj = None
|
obj = None
|
||||||
if not action_name:
|
if not action_name:
|
||||||
action_name = func.__name__
|
action_name = func.__name__
|
||||||
|
|
|
@ -5,8 +5,8 @@ from django import forms
|
||||||
from django.contrib.admin import helpers
|
from django.contrib.admin import helpers
|
||||||
from django.core import validators
|
from django.core import validators
|
||||||
from django.forms.models import modelformset_factory, BaseModelFormSet
|
from django.forms.models import modelformset_factory, BaseModelFormSet
|
||||||
from django.template import Template
|
from django.template import Template, Context
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from orchestra.forms.widgets import SpanWidget
|
from orchestra.forms.widgets import SpanWidget
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ class AdminFormMixin(object):
|
||||||
context = {
|
context = {
|
||||||
'adminform': adminform
|
'adminform': adminform
|
||||||
}
|
}
|
||||||
return template.render(context)
|
return template.render(Context(context))
|
||||||
|
|
||||||
|
|
||||||
class AdminFormSet(BaseModelFormSet):
|
class AdminFormSet(BaseModelFormSet):
|
||||||
|
@ -74,7 +74,7 @@ class AdminFormSet(BaseModelFormSet):
|
||||||
context = {
|
context = {
|
||||||
'formset': self
|
'formset': self
|
||||||
}
|
}
|
||||||
return template.render(context)
|
return template.render(Context(context))
|
||||||
|
|
||||||
|
|
||||||
class AdminPasswordChangeForm(forms.Form):
|
class AdminPasswordChangeForm(forms.Form):
|
||||||
|
|
|
@ -3,7 +3,7 @@ from copy import deepcopy
|
||||||
from admin_tools.menu import items, Menu
|
from admin_tools.menu import items, Menu
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.text import capfirst
|
from django.utils.text import capfirst
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from orchestra.core import services, accounts, administration
|
from orchestra.core import services, accounts, administration
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ class OrchestraMenu(Menu):
|
||||||
self.children = [
|
self.children = [
|
||||||
# items.MenuItem(
|
# items.MenuItem(
|
||||||
# mark_safe('{site_name} <span style="{version_style}">v{version}</span>'.format(
|
# mark_safe('{site_name} <span style="{version_style}">v{version}</span>'.format(
|
||||||
# site_name=force_text(settings.SITE_VERBOSE_NAME),
|
# site_name=force_str(settings.SITE_VERBOSE_NAME),
|
||||||
# version_style="text-transform:none; float:none; font-size:smaller; background:none;",
|
# version_style="text-transform:none; float:none; font-size:smaller; background:none;",
|
||||||
# version=get_version())),
|
# version=get_version())),
|
||||||
# reverse('admin:index')
|
# reverse('admin:index')
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from urllib import parse
|
from urllib import parse
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.conf.urls import url
|
from django.urls import re_path as url
|
||||||
from django.contrib import admin, messages
|
from django.contrib import admin, messages
|
||||||
from django.contrib.admin.options import IS_POPUP_VAR
|
from django.contrib.admin.options import IS_POPUP_VAR
|
||||||
from django.contrib.admin.utils import unquote
|
from django.contrib.admin.utils import unquote
|
||||||
|
@ -12,9 +12,9 @@ from django.forms.models import BaseInlineFormSet
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_str
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.decorators.debug import sensitive_post_parameters
|
from django.views.decorators.debug import sensitive_post_parameters
|
||||||
|
|
||||||
from orchestra.models.utils import has_db_field
|
from orchestra.models.utils import has_db_field
|
||||||
|
@ -230,7 +230,7 @@ class ExtendedModelAdmin(ChangeViewActionsMixin,
|
||||||
if obj is None:
|
if obj is None:
|
||||||
opts = self.model._meta
|
opts = self.model._meta
|
||||||
raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {
|
raise Http404(_('%(name)s object with primary key %(key)r does not exist.') % {
|
||||||
'name': force_text(opts.verbose_name), 'key': escape(object_id)})
|
'name': force_str(opts.verbose_name), 'key': escape(object_id)})
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
from django.contrib.admin.options import get_content_type_for_model
|
from django.contrib.admin.options import get_content_type_for_model
|
||||||
from django.conf import settings as django_settings
|
from django.conf import settings as django_settings
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_str
|
||||||
from django.utils.module_loading import autodiscover_modules
|
from django.utils.module_loading import autodiscover_modules
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import gettext as _
|
||||||
from rest_framework.routers import DefaultRouter
|
from rest_framework.routers import DefaultRouter
|
||||||
|
|
||||||
from orchestra import settings
|
from orchestra import settings
|
||||||
|
@ -52,7 +52,7 @@ class LogApiMixin(object):
|
||||||
user_id=request.user.pk,
|
user_id=request.user.pk,
|
||||||
content_type_id=get_content_type_for_model(instance).pk,
|
content_type_id=get_content_type_for_model(instance).pk,
|
||||||
object_id=instance.pk,
|
object_id=instance.pk,
|
||||||
object_repr=force_text(instance),
|
object_repr=force_str(instance),
|
||||||
action_flag=action,
|
action_flag=action,
|
||||||
change_message=message,
|
change_message=message,
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,7 +3,7 @@ import copy
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.forms import widgets
|
from django.forms import widgets
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.utils import model_meta
|
from rest_framework.utils import model_meta
|
||||||
|
|
||||||
|
|
|
@ -9,10 +9,10 @@ from django.db import router
|
||||||
from django.shortcuts import redirect, render
|
from django.shortcuts import redirect, render
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_str
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
from django.utils.text import capfirst
|
from django.utils.text import capfirst
|
||||||
from django.utils.translation import ungettext, ugettext_lazy as _
|
from django.utils.translation import ngettext, gettext_lazy as _
|
||||||
|
|
||||||
from orchestra.core import services
|
from orchestra.core import services
|
||||||
|
|
||||||
|
@ -84,7 +84,7 @@ def delete_related_services(modeladmin, request, queryset):
|
||||||
def format(obj, account=False):
|
def format(obj, account=False):
|
||||||
has_admin = obj.__class__ in admin_site._registry
|
has_admin = obj.__class__ in admin_site._registry
|
||||||
opts = obj._meta
|
opts = obj._meta
|
||||||
no_edit_link = '%s: %s' % (capfirst(opts.verbose_name), force_text(obj))
|
no_edit_link = '%s: %s' % (capfirst(opts.verbose_name), force_str(obj))
|
||||||
|
|
||||||
if has_admin:
|
if has_admin:
|
||||||
try:
|
try:
|
||||||
|
@ -154,7 +154,7 @@ def delete_related_services(modeladmin, request, queryset):
|
||||||
if accounts:
|
if accounts:
|
||||||
relateds = len(to_delete)
|
relateds = len(to_delete)
|
||||||
for obj in to_delete:
|
for obj in to_delete:
|
||||||
obj_display = force_text(obj)
|
obj_display = force_str(obj)
|
||||||
modeladmin.log_deletion(request, obj, obj_display)
|
modeladmin.log_deletion(request, obj, obj_display)
|
||||||
obj.delete()
|
obj.delete()
|
||||||
context = {
|
context = {
|
||||||
|
@ -167,9 +167,9 @@ def delete_related_services(modeladmin, request, queryset):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if len(queryset) == 1:
|
if len(queryset) == 1:
|
||||||
objects_name = force_text(opts.verbose_name)
|
objects_name = force_str(opts.verbose_name)
|
||||||
else:
|
else:
|
||||||
objects_name = force_text(opts.verbose_name_plural)
|
objects_name = force_str(opts.verbose_name_plural)
|
||||||
|
|
||||||
model_count = {}
|
model_count = {}
|
||||||
for model, objs in collector.model_objs.items():
|
for model, objs in collector.model_objs.items():
|
||||||
|
@ -214,7 +214,7 @@ def disable_selected(modeladmin, request, queryset, disable=True):
|
||||||
account.disable() if disable else account.enable()
|
account.disable() if disable else account.enable()
|
||||||
modeladmin.log_change(request, account, verbose_action_name.capitalize())
|
modeladmin.log_change(request, account, verbose_action_name.capitalize())
|
||||||
n += 1
|
n += 1
|
||||||
modeladmin.message_user(request, ungettext(
|
modeladmin.message_user(request, ngettext(
|
||||||
_("One account has been successfully %s.") % verbose_action_name,
|
_("One account has been successfully %s.") % verbose_action_name,
|
||||||
_("%i accounts have been successfully %s.") % (n, verbose_action_name),
|
_("%i accounts have been successfully %s.") % (n, verbose_action_name),
|
||||||
n)
|
n)
|
||||||
|
@ -227,7 +227,7 @@ def disable_selected(modeladmin, request, queryset, disable=True):
|
||||||
def format(obj):
|
def format(obj):
|
||||||
has_admin = obj.__class__ in admin_site._registry
|
has_admin = obj.__class__ in admin_site._registry
|
||||||
opts = obj._meta
|
opts = obj._meta
|
||||||
no_edit_link = '%s: %s' % (capfirst(opts.verbose_name), force_text(obj))
|
no_edit_link = '%s: %s' % (capfirst(opts.verbose_name), force_str(obj))
|
||||||
if has_admin:
|
if has_admin:
|
||||||
try:
|
try:
|
||||||
admin_url = reverse(
|
admin_url = reverse(
|
||||||
|
@ -258,9 +258,9 @@ def disable_selected(modeladmin, request, queryset, disable=True):
|
||||||
display.append([format(account), current])
|
display.append([format(account), current])
|
||||||
|
|
||||||
if len(queryset) == 1:
|
if len(queryset) == 1:
|
||||||
objects_name = force_text(opts.verbose_name)
|
objects_name = force_str(opts.verbose_name)
|
||||||
else:
|
else:
|
||||||
objects_name = force_text(opts.verbose_name_plural)
|
objects_name = force_str(opts.verbose_name_plural)
|
||||||
|
|
||||||
context = dict(
|
context = dict(
|
||||||
admin_site.each_context(request),
|
admin_site.each_context(request),
|
||||||
|
|
|
@ -4,7 +4,7 @@ from urllib.parse import parse_qsl
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.conf.urls import url
|
from django.urls import re_path as url
|
||||||
from django.contrib import admin, messages
|
from django.contrib import admin, messages
|
||||||
from django.contrib.admin.utils import unquote
|
from django.contrib.admin.utils import unquote
|
||||||
from django.contrib.auth import admin as auth
|
from django.contrib.auth import admin as auth
|
||||||
|
@ -12,7 +12,7 @@ from django.urls import reverse
|
||||||
from django.http import HttpResponseRedirect
|
from django.http import HttpResponseRedirect
|
||||||
from django.templatetags.static import static
|
from django.templatetags.static import static
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from orchestra.admin import ExtendedModelAdmin, ChangePasswordAdminMixin
|
from orchestra.admin import ExtendedModelAdmin, ChangePasswordAdminMixin
|
||||||
from orchestra.admin.actions import SendEmail
|
from orchestra.admin.actions import SendEmail
|
||||||
|
@ -26,12 +26,13 @@ from .actions import (list_contacts, service_report, delete_related_services, di
|
||||||
enable_selected)
|
enable_selected)
|
||||||
from .forms import AccountCreationForm
|
from .forms import AccountCreationForm
|
||||||
from .models import Account
|
from .models import Account
|
||||||
|
from .filters import HasTipeServerFilter
|
||||||
|
|
||||||
|
|
||||||
class AccountAdmin(ChangePasswordAdminMixin, auth.UserAdmin, ExtendedModelAdmin):
|
class AccountAdmin(ChangePasswordAdminMixin, auth.UserAdmin, ExtendedModelAdmin):
|
||||||
list_display = ('username', 'full_name', 'type', 'is_active')
|
list_display = ('username', 'full_name', 'type', 'is_active')
|
||||||
list_filter = (
|
list_filter = (
|
||||||
'type', 'is_active',
|
'type', 'is_active', HasTipeServerFilter
|
||||||
)
|
)
|
||||||
add_fieldsets = (
|
add_fieldsets = (
|
||||||
(_("User"), {
|
(_("User"), {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from rest_framework import viewsets, exceptions
|
from rest_framework import viewsets, exceptions
|
||||||
|
|
||||||
from orchestra.api import router, SetPasswordApiMixin, LogApiMixin
|
from orchestra.api import router, SetPasswordApiMixin, LogApiMixin
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
from django.db.models.signals import post_migrate
|
from django.db.models.signals import post_migrate
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from orchestra.core import services, accounts
|
from orchestra.core import services, accounts
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
from django.contrib.admin import SimpleListFilter
|
from django.contrib.admin import SimpleListFilter
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from orchestra.contrib.orchestration.models import Server
|
||||||
|
from orchestra.contrib.websites.models import Website
|
||||||
|
from orchestra.settings import WEB_SERVERS
|
||||||
|
|
||||||
class IsActiveListFilter(SimpleListFilter):
|
class IsActiveListFilter(SimpleListFilter):
|
||||||
title = _("is active")
|
title = _("is active")
|
||||||
|
@ -25,3 +27,16 @@ class IsActiveListFilter(SimpleListFilter):
|
||||||
elif self.value() == 'object':
|
elif self.value() == 'object':
|
||||||
return queryset.filter(is_active=False)
|
return queryset.filter(is_active=False)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
class HasTipeServerFilter(SimpleListFilter):
|
||||||
|
title = _("has type server")
|
||||||
|
parameter_name = 'has_servers'
|
||||||
|
|
||||||
|
def lookups(self, request, model_admin):
|
||||||
|
return [ (x.id, x.name) for x in Server.objects.filter(name__in=WEB_SERVERS) ]
|
||||||
|
|
||||||
|
def queryset(self, request, queryset):
|
||||||
|
if self.value() is not None:
|
||||||
|
serverWebsites = Website.objects.filter(target_server=self.value())
|
||||||
|
return queryset.filter(id__in=[ x.account.id for x in serverWebsites ] )
|
||||||
|
return queryset
|
|
@ -4,7 +4,7 @@ from collections import OrderedDict
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from orchestra.forms import UserCreationForm
|
from orchestra.forms import UserCreationForm
|
||||||
|
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
import django.core.validators
|
|
||||||
import django.db.models.deletion
|
|
||||||
import django.utils.timezone
|
|
||||||
import django.contrib.auth.models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
# Permissions and contenttypes
|
|
||||||
('auth', '0006_require_contenttypes_0002'),
|
|
||||||
('systemusers', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Account',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(verbose_name='ID', primary_key=True, serialize=False, auto_created=True)),
|
|
||||||
('password', models.CharField(verbose_name='password', max_length=128)),
|
|
||||||
('last_login', models.DateTimeField(blank=True, verbose_name='last login', null=True)),
|
|
||||||
('username', models.CharField(help_text='Required. 64 characters or fewer. Letters, digits and ./-/_ only.', unique=True, validators=[django.core.validators.RegexValidator('^[\\w.-]+$', 'Enter a valid username.', 'invalid')], max_length=32, verbose_name='username')),
|
|
||||||
('short_name', models.CharField(blank=True, verbose_name='short name', max_length=64)),
|
|
||||||
('full_name', models.CharField(verbose_name='full name', max_length=256)),
|
|
||||||
('email', models.EmailField(help_text='Used for password recovery', max_length=254, verbose_name='email address')),
|
|
||||||
('type', models.CharField(verbose_name='type', choices=[('INDIVIDUAL', 'Individual'), ('ASSOCIATION', 'Association'), ('CUSTOMER', 'Customer'), ('COMPANY', 'Company'), ('PUBLICBODY', 'Public body'), ('STAFF', 'Staff'), ('FRIEND', 'Friend')], max_length=32, default='INDIVIDUAL')),
|
|
||||||
('language', models.CharField(verbose_name='language', choices=[('EN', 'English')], max_length=2, default='EN')),
|
|
||||||
('comments', models.TextField(blank=True, verbose_name='comments', max_length=256)),
|
|
||||||
('is_superuser', models.BooleanField(help_text='Designates that this user has all permissions without explicitly assigning them.', default=False, verbose_name='superuser status')),
|
|
||||||
('is_active', models.BooleanField(help_text='Designates whether this account should be treated as active. Unselect this instead of deleting accounts.', default=True, verbose_name='active')),
|
|
||||||
('date_joined', models.DateTimeField(verbose_name='date joined', default=django.utils.timezone.now)),
|
|
||||||
('main_systemuser', models.ForeignKey(to='systemusers.SystemUser', editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='accounts_main')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
managers=[
|
|
||||||
('objects', django.contrib.auth.models.UserManager()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,86 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.5 on 2021-04-22 11:08
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import django.contrib.auth.models
|
|
||||||
import django.core.validators
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
import django.utils.timezone
|
|
||||||
import orchestra.contrib.accounts.models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
replaces = [('accounts', '0001_initial'), ('accounts', '0002_auto_20170528_2005'), ('accounts', '0003_auto_20210330_1049'), ('accounts', '0004_auto_20210422_1108')]
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('systemusers', '0001_initial'),
|
|
||||||
('auth', '0006_require_contenttypes_0002'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Account',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('password', models.CharField(max_length=128, verbose_name='password')),
|
|
||||||
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
|
|
||||||
('username', models.CharField(help_text='Required. 64 characters or fewer. Letters, digits and ./-/_ only.', max_length=32, unique=True, validators=[django.core.validators.RegexValidator('^[\\w.-]+$', 'Enter a valid username.', 'invalid')], 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', max_length=254, verbose_name='email address')),
|
|
||||||
('type', models.CharField(choices=[('INDIVIDUAL', 'Individual'), ('ASSOCIATION', 'Association'), ('CUSTOMER', 'Customer'), ('COMPANY', 'Company'), ('PUBLICBODY', 'Public body'), ('STAFF', 'Staff'), ('FRIEND', 'Friend')], default='INDIVIDUAL', max_length=32, verbose_name='type')),
|
|
||||||
('language', models.CharField(choices=[('EN', 'English')], default='EN', 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(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='accounts_main', to='systemusers.SystemUser')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'abstract': False,
|
|
||||||
},
|
|
||||||
managers=[
|
|
||||||
('objects', django.contrib.auth.models.UserManager()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.AlterModelManagers(
|
|
||||||
name='account',
|
|
||||||
managers=[
|
|
||||||
('objects', orchestra.contrib.accounts.models.AccountManager()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='account',
|
|
||||||
name='language',
|
|
||||||
field=models.CharField(choices=[('CA', 'Catalan'), ('ES', 'Spanish'), ('EN', 'English')], default='CA', max_length=2, verbose_name='language'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='account',
|
|
||||||
name='type',
|
|
||||||
field=models.CharField(choices=[('INDIVIDUAL', 'Individual'), ('ASSOCIATION', 'Association'), ('CUSTOMER', 'Customer'), ('STAFF', 'Staff'), ('FRIEND', 'Friend')], default='INDIVIDUAL', max_length=32, verbose_name='type'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='account',
|
|
||||||
name='username',
|
|
||||||
field=models.CharField(help_text='Required. 32 characters or fewer. Letters, digits and ./-/_ only.', max_length=32, unique=True, validators=[django.core.validators.RegexValidator('^[\\w.-]+$', 'Enter a valid username.', 'invalid')], verbose_name='username'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='account',
|
|
||||||
name='language',
|
|
||||||
field=models.CharField(choices=[('EN', 'English')], default='EN', max_length=2, verbose_name='language'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='account',
|
|
||||||
name='type',
|
|
||||||
field=models.CharField(choices=[('INDIVIDUAL', 'Individual'), ('ASSOCIATION', 'Association'), ('CUSTOMER', 'Customer'), ('COMPANY', 'Company'), ('PUBLICBODY', 'Public body'), ('STAFF', 'Staff'), ('FRIEND', 'Friend')], default='INDIVIDUAL', max_length=32, verbose_name='type'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='account',
|
|
||||||
name='main_systemuser',
|
|
||||||
field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='accounts_main', to='systemusers.SystemUser'),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,38 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.5 on 2017-05-28 18:05
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
import django.core.validators
|
|
||||||
from django.db import migrations, models
|
|
||||||
import orchestra.contrib.accounts.models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('accounts', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterModelManagers(
|
|
||||||
name='account',
|
|
||||||
managers=[
|
|
||||||
('objects', orchestra.contrib.accounts.models.AccountManager()),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='account',
|
|
||||||
name='language',
|
|
||||||
field=models.CharField(choices=[('CA', 'Catalan'), ('ES', 'Spanish'), ('EN', 'English')], default='CA', max_length=2, verbose_name='language'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='account',
|
|
||||||
name='type',
|
|
||||||
field=models.CharField(choices=[('INDIVIDUAL', 'Individual'), ('ASSOCIATION', 'Association'), ('CUSTOMER', 'Customer'), ('STAFF', 'Staff'), ('FRIEND', 'Friend')], default='INDIVIDUAL', max_length=32, verbose_name='type'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='account',
|
|
||||||
name='username',
|
|
||||||
field=models.CharField(help_text='Required. 32 characters or fewer. Letters, digits and ./-/_ only.', max_length=32, unique=True, validators=[django.core.validators.RegexValidator('^[\\w.-]+$', 'Enter a valid username.', 'invalid')], verbose_name='username'),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,25 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.5 on 2021-03-30 10:49
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('accounts', '0002_auto_20170528_2005'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='account',
|
|
||||||
name='language',
|
|
||||||
field=models.CharField(choices=[('EN', 'English')], default='EN', max_length=2, verbose_name='language'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='account',
|
|
||||||
name='type',
|
|
||||||
field=models.CharField(choices=[('INDIVIDUAL', 'Individual'), ('ASSOCIATION', 'Association'), ('CUSTOMER', 'Customer'), ('COMPANY', 'Company'), ('PUBLICBODY', 'Public body'), ('STAFF', 'Staff'), ('FRIEND', 'Friend')], default='INDIVIDUAL', max_length=32, verbose_name='type'),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -5,7 +5,7 @@ from django.db import models
|
||||||
from django.db.models import signals
|
from django.db.models import signals
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.utils import timezone, translation
|
from django.utils import timezone, translation
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
#from orchestra.contrib.orchestration.middlewares import OperationsMiddleware
|
#from orchestra.contrib.orchestration.middlewares import OperationsMiddleware
|
||||||
#from orchestra.contrib.orchestration import Operation
|
#from orchestra.contrib.orchestration import Operation
|
||||||
|
@ -165,7 +165,6 @@ class Account(auth.AbstractBaseUser):
|
||||||
elif obj and getattr(obj, 'account', None) == self:
|
elif obj and getattr(obj, 'account', None) == self:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def has_perms(self, perm_list, obj=None):
|
def has_perms(self, perm_list, obj=None):
|
||||||
"""
|
"""
|
||||||
Returns True if the user has each of the specified permissions. If
|
Returns True if the user has each of the specified permissions. If
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from orchestra.contrib.settings import Setting
|
from orchestra.contrib.settings import Setting
|
||||||
from orchestra.settings import ORCHESTRA_BASE_DOMAIN
|
from orchestra.settings import ORCHESTRA_BASE_DOMAIN
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{% extends "orchestra/admin/change_form.html" %}
|
{% extends "orchestra/admin/change_form.html" %}
|
||||||
{% load i18n admin_urls admin_static admin_modify %}
|
{% load i18n admin_urls static admin_modify %}
|
||||||
|
|
||||||
|
|
||||||
{% block breadcrumbs %}
|
{% block breadcrumbs %}
|
||||||
|
|
|
@ -12,7 +12,7 @@ from django.http import HttpResponse, HttpResponseRedirect
|
||||||
from django.shortcuts import render, redirect
|
from django.shortcuts import render, redirect
|
||||||
from django.utils import translation, timezone
|
from django.utils import translation, timezone
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import ungettext, ugettext_lazy as _
|
from django.utils.translation import ngettext, gettext_lazy as _
|
||||||
|
|
||||||
from orchestra.admin.decorators import action_with_confirmation
|
from orchestra.admin.decorators import action_with_confirmation
|
||||||
from orchestra.admin.forms import AdminFormSet
|
from orchestra.admin.forms import AdminFormSet
|
||||||
|
@ -69,7 +69,7 @@ def close_bills(modeladmin, request, queryset, action='close_bills'):
|
||||||
'url': url,
|
'url': url,
|
||||||
'num': num,
|
'num': num,
|
||||||
}
|
}
|
||||||
message = ungettext(
|
message = ngettext(
|
||||||
_('<a href="%(url)s">One related transaction</a> has been created') % context,
|
_('<a href="%(url)s">One related transaction</a> has been created') % context,
|
||||||
_('<a href="%(url)s">%(num)i related transactions</a> have been created') % context,
|
_('<a href="%(url)s">%(num)i related transactions</a> have been created') % context,
|
||||||
num)
|
num)
|
||||||
|
@ -111,7 +111,7 @@ def send_bills_action(modeladmin, request, queryset):
|
||||||
bill.send()
|
bill.send()
|
||||||
modeladmin.log_change(request, bill, 'Sent')
|
modeladmin.log_change(request, bill, 'Sent')
|
||||||
num += 1
|
num += 1
|
||||||
messages.success(request, ungettext(
|
messages.success(request, ngettext(
|
||||||
_("One bill has been sent."),
|
_("One bill has been sent."),
|
||||||
_("%i bills have been sent.") % num,
|
_("%i bills have been sent.") % num,
|
||||||
num))
|
num))
|
||||||
|
@ -135,7 +135,7 @@ def download_bills(modeladmin, request, queryset):
|
||||||
pdf = bill.as_pdf()
|
pdf = bill.as_pdf()
|
||||||
archive.writestr('%s.pdf' % bill.number, pdf)
|
archive.writestr('%s.pdf' % bill.number, pdf)
|
||||||
archive.close()
|
archive.close()
|
||||||
response = HttpResponse(bytesio.getvalue(), content_type='application/pdf')
|
response = HttpResponse(bytesio.getvalue(), content_type='application/zip')
|
||||||
response['Content-Disposition'] = 'attachment; filename="orchestra-bills.zip"'
|
response['Content-Disposition'] = 'attachment; filename="orchestra-bills.zip"'
|
||||||
return response
|
return response
|
||||||
bill = queryset[0]
|
bill = queryset[0]
|
||||||
|
@ -299,7 +299,7 @@ def amend_bills(modeladmin, request, queryset):
|
||||||
'url': amend_url,
|
'url': amend_url,
|
||||||
'num': num,
|
'num': num,
|
||||||
}
|
}
|
||||||
messages.success(request, mark_safe(ungettext(
|
messages.success(request, mark_safe(ngettext(
|
||||||
_('<a href="%(url)s">One amendment bill</a> have been generated.') % context,
|
_('<a href="%(url)s">One amendment bill</a> have been generated.') % context,
|
||||||
_('<a href="%(url)s">%(num)i amendment bills</a> have been generated.') % context,
|
_('<a href="%(url)s">%(num)i amendment bills</a> have been generated.') % context,
|
||||||
num
|
num
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.conf.urls import url
|
from django.urls import re_path as url
|
||||||
from django.contrib import admin, messages
|
from django.contrib import admin, messages
|
||||||
from django.contrib.admin.utils import unquote
|
from django.contrib.admin.utils import unquote
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
@ -9,7 +9,7 @@ from django.db.models.functions import Coalesce
|
||||||
from django.templatetags.static import static
|
from django.templatetags.static import static
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
|
|
||||||
from orchestra.admin import ExtendedModelAdmin
|
from orchestra.admin import ExtendedModelAdmin
|
||||||
|
@ -126,7 +126,7 @@ class ClosedBillLineInline(BillLineInline):
|
||||||
return line.compute_total()
|
return line.compute_total()
|
||||||
display_total.short_description = _("Total")
|
display_total.short_description = _("Total")
|
||||||
|
|
||||||
def has_add_permission(self, request):
|
def has_add_permission(self, request, obj):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ from django.contrib.admin import SimpleListFilter
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from . models import Bill
|
from . models import Bill
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from orchestra.admin.utils import admin_link
|
from orchestra.admin.utils import admin_link
|
||||||
from orchestra.forms import SpanWidget
|
from orchestra.forms import SpanWidget
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_str
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.text import capfirst
|
from django.utils.text import capfirst
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from orchestra.admin.utils import change_url
|
from orchestra.admin.utils import change_url
|
||||||
|
|
||||||
|
@ -16,14 +16,14 @@ def validate_contact(request, bill, error=True):
|
||||||
valid = True
|
valid = True
|
||||||
send = messages.error if error else messages.warning
|
send = messages.error if error else messages.warning
|
||||||
if not hasattr(bill.account, 'billcontact'):
|
if not hasattr(bill.account, 'billcontact'):
|
||||||
account = force_text(bill.account)
|
account = force_str(bill.account)
|
||||||
url = reverse('admin:accounts_account_change', args=(bill.account_id,))
|
url = reverse('admin:accounts_account_change', args=(bill.account_id,))
|
||||||
message = msg.format(relation=_("Related"), account=account, url=url)
|
message = msg.format(relation=_("Related"), account=account, url=url)
|
||||||
send(request, mark_safe(message))
|
send(request, mark_safe(message))
|
||||||
valid = False
|
valid = False
|
||||||
main = type(bill).account.field.related_model.objects.get_main()
|
main = type(bill).account.field.related_model.objects.get_main()
|
||||||
if not hasattr(main, 'billcontact'):
|
if not hasattr(main, 'billcontact'):
|
||||||
account = force_text(main)
|
account = force_str(main)
|
||||||
url = reverse('admin:accounts_account_change', args=(main.id,))
|
url = reverse('admin:accounts_account_change', args=(main.id,))
|
||||||
message = msg.format(relation=_("Main"), account=account, url=url)
|
message = msg.format(relation=_("Main"), account=account, url=url)
|
||||||
send(request, mark_safe(message))
|
send(request, mark_safe(message))
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -8,9 +8,9 @@ from django.db.models import F, Sum
|
||||||
from django.db.models.functions import Coalesce
|
from django.db.models.functions import Coalesce
|
||||||
from django.template import loader
|
from django.template import loader
|
||||||
from django.utils import timezone, translation
|
from django.utils import timezone, translation
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_str
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from orchestra.admin.utils import change_url
|
from orchestra.admin.utils import change_url
|
||||||
from orchestra.contrib.accounts.models import Account
|
from orchestra.contrib.accounts.models import Account
|
||||||
|
@ -209,7 +209,7 @@ class Bill(models.Model):
|
||||||
|
|
||||||
def get_payment_state_display(self):
|
def get_payment_state_display(self):
|
||||||
value = self.payment_state
|
value = self.payment_state
|
||||||
return force_text(dict(self.PAYMENT_STATES).get(value, value))
|
return force_str(dict(self.PAYMENT_STATES).get(value, value))
|
||||||
|
|
||||||
def get_current_transaction(self):
|
def get_current_transaction(self):
|
||||||
return self.transactions.exclude_rejected().first()
|
return self.transactions.exclude_rejected().first()
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from orchestra.admin import AtLeastOneRequiredInlineFormSet, ExtendedModelAdmin
|
from orchestra.admin import AtLeastOneRequiredInlineFormSet, ExtendedModelAdmin
|
||||||
from orchestra.admin.actions import SendEmail
|
from orchestra.admin.actions import SendEmail
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from django.contrib.admin import SimpleListFilter
|
from django.contrib.admin import SimpleListFilter
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from .models import Contact
|
from .models import Contact
|
||||||
|
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,7 +1,7 @@
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.core.validators import RegexValidator
|
from django.core.validators import RegexValidator
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from orchestra.core import validators
|
from orchestra.core import validators
|
||||||
from orchestra.models.fields import MultiSelectField
|
from orchestra.models.fields import MultiSelectField
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
from django.conf.urls import url
|
from django.urls import re_path as url
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.auth.admin import UserAdmin
|
from django.contrib.auth.admin import UserAdmin
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from orchestra.admin import ExtendedModelAdmin, ChangePasswordAdminMixin
|
from orchestra.admin import ExtendedModelAdmin, ChangePasswordAdminMixin
|
||||||
from orchestra.admin.utils import change_url
|
from orchestra.admin.utils import change_url
|
||||||
|
@ -11,7 +11,7 @@ from orchestra.contrib.accounts.actions import list_accounts
|
||||||
from orchestra.contrib.accounts.admin import SelectAccountAdminMixin
|
from orchestra.contrib.accounts.admin import SelectAccountAdminMixin
|
||||||
|
|
||||||
from .filters import HasUserListFilter, HasDatabaseListFilter
|
from .filters import HasUserListFilter, HasDatabaseListFilter
|
||||||
from .forms import DatabaseCreationForm, DatabaseUserChangeForm, DatabaseUserCreationForm
|
from .forms import DatabaseCreationForm, DatabaseUserChangeForm, DatabaseUserCreationForm, DatabaseForm
|
||||||
from .models import Database, DatabaseUser
|
from .models import Database, DatabaseUser
|
||||||
|
|
||||||
def save_selected(modeladmin, request, queryset):
|
def save_selected(modeladmin, request, queryset):
|
||||||
|
@ -20,21 +20,21 @@ def save_selected(modeladmin, request, queryset):
|
||||||
save_selected.short_description = "Re-save selected objects"
|
save_selected.short_description = "Re-save selected objects"
|
||||||
|
|
||||||
class DatabaseAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
class DatabaseAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
||||||
list_display = ('name', 'type', 'display_users', 'account_link')
|
list_display = ('name', 'type', 'target_server', 'display_users', 'account_link')
|
||||||
list_filter = ('type', HasUserListFilter)
|
list_filter = ('type', HasUserListFilter)
|
||||||
search_fields = ('name', 'account__username')
|
search_fields = ('name', 'account__username')
|
||||||
change_readonly_fields = ('name', 'type')
|
change_readonly_fields = ('name', 'type', 'target_server')
|
||||||
extra = 1
|
extra = 1
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, {
|
(None, {
|
||||||
'classes': ('extrapretty',),
|
'classes': ('extrapretty',),
|
||||||
'fields': ('account_link', 'name', 'type', 'users', 'display_users', 'comments'),
|
'fields': ('account_link', 'name', 'type', 'users', 'display_users', 'comments', 'target_server'),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
add_fieldsets = (
|
add_fieldsets = (
|
||||||
(None, {
|
(None, {
|
||||||
'classes': ('wide',),
|
'classes': ('wide',),
|
||||||
'fields': ('account_link', 'name', 'type')
|
'fields': ('account_link', 'name', 'type', 'target_server')
|
||||||
}),
|
}),
|
||||||
(_("Create new user"), {
|
(_("Create new user"), {
|
||||||
'classes': ('wide',),
|
'classes': ('wide',),
|
||||||
|
@ -45,11 +45,10 @@ class DatabaseAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
||||||
'fields': ('user',)
|
'fields': ('user',)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
form = DatabaseForm
|
||||||
add_form = DatabaseCreationForm
|
add_form = DatabaseCreationForm
|
||||||
readonly_fields = ('account_link', 'display_users',)
|
readonly_fields = ('account_link', 'display_users',)
|
||||||
filter_horizontal = ['users']
|
filter_horizontal = ['users']
|
||||||
filter_by_account_fields = ('users',)
|
|
||||||
list_prefetch_related = ('users',)
|
|
||||||
actions = (list_accounts, save_selected)
|
actions = (list_accounts, save_selected)
|
||||||
|
|
||||||
@mark_safe
|
@mark_safe
|
||||||
|
@ -71,6 +70,7 @@ class DatabaseAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
||||||
username=form.cleaned_data['username'],
|
username=form.cleaned_data['username'],
|
||||||
type=obj.type,
|
type=obj.type,
|
||||||
account_id=obj.account.pk,
|
account_id=obj.account.pk,
|
||||||
|
target_server=form.cleaned_data['target_server'],
|
||||||
)
|
)
|
||||||
user.set_password(form.cleaned_data["password1"])
|
user.set_password(form.cleaned_data["password1"])
|
||||||
user.save()
|
user.save()
|
||||||
|
@ -78,22 +78,22 @@ class DatabaseAdmin(SelectAccountAdminMixin, ExtendedModelAdmin):
|
||||||
|
|
||||||
|
|
||||||
class DatabaseUserAdmin(SelectAccountAdminMixin, ChangePasswordAdminMixin, ExtendedModelAdmin):
|
class DatabaseUserAdmin(SelectAccountAdminMixin, ChangePasswordAdminMixin, ExtendedModelAdmin):
|
||||||
list_display = ('username', 'type', 'display_databases', 'account_link')
|
list_display = ('username', 'target_server', 'type', 'display_databases', 'account_link')
|
||||||
list_filter = ('type', HasDatabaseListFilter)
|
list_filter = ('type', HasDatabaseListFilter)
|
||||||
search_fields = ('username', 'account__username')
|
search_fields = ('username', 'account__username')
|
||||||
form = DatabaseUserChangeForm
|
form = DatabaseUserChangeForm
|
||||||
add_form = DatabaseUserCreationForm
|
add_form = DatabaseUserCreationForm
|
||||||
change_readonly_fields = ('username', 'type')
|
change_readonly_fields = ('username', 'type', 'target_server')
|
||||||
fieldsets = (
|
fieldsets = (
|
||||||
(None, {
|
(None, {
|
||||||
'classes': ('extrapretty',),
|
'classes': ('extrapretty',),
|
||||||
'fields': ('account_link', 'username', 'password', 'type', 'display_databases')
|
'fields': ('account_link', 'username', 'password', 'type', 'display_databases', 'target_server', 'permision')
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
add_fieldsets = (
|
add_fieldsets = (
|
||||||
(None, {
|
(None, {
|
||||||
'classes': ('extrapretty',),
|
'classes': ('extrapretty',),
|
||||||
'fields': ('account_link', 'username', 'password1', 'password2', 'type')
|
'fields': ('account_link', 'username', 'password1', 'password2', 'type', 'target_server', 'permision')
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
readonly_fields = ('account_link', 'display_databases',)
|
readonly_fields = ('account_link', 'display_databases',)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from django.apps import AppConfig
|
from django.apps import AppConfig
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from orchestra.core import services
|
from orchestra.core import services
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from orchestra.contrib.orchestration import ServiceController, replace
|
from orchestra.contrib.orchestration import ServiceController, replace
|
||||||
from orchestra.contrib.resources import ServiceMonitor
|
from orchestra.contrib.resources import ServiceMonitor
|
||||||
|
@ -36,10 +36,16 @@ class MySQLController(ServiceController):
|
||||||
'username': user.username,
|
'username': user.username,
|
||||||
'grant': 'WITH GRANT OPTION' if user == context['owner'] else ''
|
'grant': 'WITH GRANT OPTION' if user == context['owner'] else ''
|
||||||
})
|
})
|
||||||
self.append(textwrap.dedent("""\
|
if user.permision == "ro":
|
||||||
mysql -e 'GRANT ALL PRIVILEGES ON `%(database)s`.* TO "%(username)s"@"%(host)s" %(grant)s;'\
|
self.append(textwrap.dedent("""\
|
||||||
""") % context
|
mysql -e 'GRANT SELECT ON `%(database)s`.* TO "%(username)s"@"%(host)s" %(grant)s;'\
|
||||||
)
|
""") % context
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.append(textwrap.dedent("""\
|
||||||
|
mysql -e 'GRANT ALL PRIVILEGES ON `%(database)s`.* TO "%(username)s"@"%(host)s" %(grant)s;'\
|
||||||
|
""") % context
|
||||||
|
)
|
||||||
|
|
||||||
def delete(self, database):
|
def delete(self, database):
|
||||||
if database.type != database.MYSQL:
|
if database.type != database.MYSQL:
|
||||||
|
@ -85,8 +91,8 @@ class MySQLUserController(ServiceController):
|
||||||
context = self.get_context(user)
|
context = self.get_context(user)
|
||||||
self.append(textwrap.dedent("""\
|
self.append(textwrap.dedent("""\
|
||||||
# Create user %(username)s
|
# Create user %(username)s
|
||||||
mysql -e 'CREATE USER "%(username)s"@"%(host)s";' || true # User already exists
|
mysql -e 'CREATE USER IF NOT EXISTS "%(username)s"@"%(host)s";'
|
||||||
mysql -e 'UPDATE mysql.user SET Password="%(password)s" WHERE User="%(username)s";'\
|
mysql -e 'ALTER USER IF EXISTS "%(username)s"@"%(host)s" IDENTIFIED BY PASSWORD "%(password)s";'\
|
||||||
""") % context
|
""") % context
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -172,7 +178,7 @@ class MysqlDisk(ServiceMonitor):
|
||||||
def get_context(self, db):
|
def get_context(self, db):
|
||||||
context = {
|
context = {
|
||||||
'db_name': db.name,
|
'db_name': db.name,
|
||||||
'db_dirname': db.name.replace('-', '@003f'),
|
'db_dirname': db.name.replace('-', '@002d'),
|
||||||
'db_id': db.pk,
|
'db_id': db.pk,
|
||||||
'db_type': db.type,
|
'db_type': db.type,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from django.contrib.admin import SimpleListFilter
|
from django.contrib.admin import SimpleListFilter
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
class HasUserListFilter(SimpleListFilter):
|
class HasUserListFilter(SimpleListFilter):
|
||||||
|
|
|
@ -3,7 +3,7 @@ from django.contrib.auth.forms import ReadOnlyPasswordHashField
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from orchestra.core import validators
|
from orchestra.core import validators
|
||||||
|
|
||||||
|
@ -31,13 +31,39 @@ class DatabaseUserCreationForm(forms.ModelForm):
|
||||||
return password2
|
return password2
|
||||||
|
|
||||||
|
|
||||||
|
class DatabaseForm(forms.ModelForm):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Database
|
||||||
|
fields = ('name', 'users', 'type', 'account', 'target_server')
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(DatabaseForm, self).__init__(*args, **kwargs)
|
||||||
|
# muestra solo los usuarios del mismo server
|
||||||
|
account_id = self.instance.account_id
|
||||||
|
database_server_id = self.instance.target_server_id
|
||||||
|
if account_id:
|
||||||
|
self.fields['users'].queryset = DatabaseUser.objects.filter(account=account_id, target_server=database_server_id)
|
||||||
|
|
||||||
|
def clean(self):
|
||||||
|
# verifica que los usuarios petenecen al servidor de la bbdd
|
||||||
|
database_server_id = self.instance.target_server_id
|
||||||
|
users = self.cleaned_data.get('users')
|
||||||
|
if users and database_server_id:
|
||||||
|
for user in users:
|
||||||
|
if user.target_server_id != database_server_id:
|
||||||
|
self.add_error("users", _(f"{user.username} does not belong to the database server"))
|
||||||
|
|
||||||
|
return self.cleaned_data
|
||||||
|
|
||||||
|
|
||||||
class DatabaseCreationForm(DatabaseUserCreationForm):
|
class DatabaseCreationForm(DatabaseUserCreationForm):
|
||||||
username = forms.CharField(label=_("Username"), max_length=16,
|
username = forms.CharField(label=_("Username"), max_length=32,
|
||||||
required=False, validators=[validators.validate_name],
|
required=False, validators=[validators.validate_name],
|
||||||
help_text=_("Required. 16 characters or fewer. Letters, digits and "
|
help_text=_("Required. 32 characters or fewer. Letters, digits and "
|
||||||
"@/./+/-/_ only."),
|
"@/./+/-/_ only."),
|
||||||
error_messages={
|
error_messages={
|
||||||
'invalid': _("This value may contain 16 characters or fewer, only letters, numbers and "
|
'invalid': _("This value may contain 32 characters or fewer, only letters, numbers and "
|
||||||
"@/./+/-/_ characters.")})
|
"@/./+/-/_ characters.")})
|
||||||
user = forms.ModelChoiceField(required=False, queryset=DatabaseUser.objects)
|
user = forms.ModelChoiceField(required=False, queryset=DatabaseUser.objects)
|
||||||
|
|
||||||
|
@ -50,13 +76,14 @@ class DatabaseCreationForm(DatabaseUserCreationForm):
|
||||||
account_id = self.initial.get('account', self.initial_account)
|
account_id = self.initial.get('account', self.initial_account)
|
||||||
if account_id:
|
if account_id:
|
||||||
qs = self.fields['user'].queryset.filter(account=account_id).order_by('username')
|
qs = self.fields['user'].queryset.filter(account=account_id).order_by('username')
|
||||||
choices = [ (u.pk, "%s (%s)" % (u, u.get_type_display())) for u in qs ]
|
choices = [ (u.pk, "%s (%s) (%s)" % (u, u.get_type_display(), str(u.target_server.name) )) for u in qs ]
|
||||||
self.fields['user'].queryset = qs
|
self.fields['user'].queryset = qs
|
||||||
self.fields['user'].choices = [(None, '--------'),] + choices
|
self.fields['user'].choices = [(None, '--------'),] + choices
|
||||||
|
|
||||||
def clean_username(self):
|
def clean_username(self):
|
||||||
username = self.cleaned_data.get('username')
|
username = self.cleaned_data.get('username')
|
||||||
if DatabaseUser.objects.filter(username=username).exists():
|
server = self.cleaned_data.get('target_server')
|
||||||
|
if DatabaseUser.objects.filter(username=username, target_server=server).exists():
|
||||||
raise ValidationError("Provided username already exists.")
|
raise ValidationError("Provided username already exists.")
|
||||||
return username
|
return username
|
||||||
|
|
||||||
|
@ -76,6 +103,9 @@ class DatabaseCreationForm(DatabaseUserCreationForm):
|
||||||
if user and user.type != self.cleaned_data.get('type'):
|
if user and user.type != self.cleaned_data.get('type'):
|
||||||
msg = _("Database type and user type doesn't match")
|
msg = _("Database type and user type doesn't match")
|
||||||
raise ValidationError(msg)
|
raise ValidationError(msg)
|
||||||
|
if user and user.target_server != self.cleaned_data.get('target_server'):
|
||||||
|
msg = _("Database server and user server doesn't match")
|
||||||
|
raise ValidationError(msg)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
|
|
@ -1,52 +1,46 @@
|
||||||
# -*- coding: utf-8 -*-
|
# Generated by Django 2.2.28 on 2023-06-28 17:06
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import models, migrations
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
import orchestra.core.validators
|
import orchestra.core.validators
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
migrations.CreateModel(
|
|
||||||
name='Database',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(serialize=False, primary_key=True, verbose_name='ID', auto_created=True)),
|
|
||||||
('name', models.CharField(verbose_name='name', max_length=64, validators=[orchestra.core.validators.validate_name])),
|
|
||||||
('type', models.CharField(default='mysql', choices=[('mysql', 'MySQL'), ('postgres', 'PostgreSQL')], verbose_name='type', max_length=32)),
|
|
||||||
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='databases', verbose_name='Account', to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='DatabaseUser',
|
name='DatabaseUser',
|
||||||
fields=[
|
fields=[
|
||||||
('id', models.AutoField(serialize=False, primary_key=True, verbose_name='ID', auto_created=True)),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('username', models.CharField(verbose_name='username', max_length=16, validators=[orchestra.core.validators.validate_name])),
|
('username', models.CharField(max_length=16, validators=[orchestra.core.validators.validate_name], verbose_name='username')),
|
||||||
('password', models.CharField(verbose_name='password', max_length=256)),
|
('password', models.CharField(max_length=256, verbose_name='password')),
|
||||||
('type', models.CharField(default='mysql', choices=[('mysql', 'MySQL'), ('postgres', 'PostgreSQL')], verbose_name='type', max_length=32)),
|
('type', models.CharField(choices=[('mysql', 'MySQL')], default='mysql', max_length=32, verbose_name='type')),
|
||||||
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='databaseusers', verbose_name='Account', to=settings.AUTH_USER_MODEL)),
|
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='databaseusers', to=settings.AUTH_USER_MODEL, verbose_name='Account')),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name_plural': 'DB users',
|
'verbose_name_plural': 'DB users',
|
||||||
|
'unique_together': {('username', 'type')},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.CreateModel(
|
||||||
model_name='database',
|
name='Database',
|
||||||
name='users',
|
fields=[
|
||||||
field=models.ManyToManyField(related_name='databases', to='databases.DatabaseUser', verbose_name='users', blank=True),
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
),
|
('name', models.CharField(max_length=64, validators=[orchestra.core.validators.validate_name], verbose_name='name')),
|
||||||
migrations.AlterUniqueTogether(
|
('type', models.CharField(choices=[('mysql', 'MySQL')], default='mysql', max_length=32, verbose_name='type')),
|
||||||
name='databaseuser',
|
('comments', models.TextField(blank=True, default='')),
|
||||||
unique_together=set([('username', 'type')]),
|
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='databases', to=settings.AUTH_USER_MODEL, verbose_name='Account')),
|
||||||
),
|
('users', models.ManyToManyField(blank=True, related_name='databases', to='databases.DatabaseUser', verbose_name='users')),
|
||||||
migrations.AlterUniqueTogether(
|
],
|
||||||
name='database',
|
options={
|
||||||
unique_together=set([('name', 'type')]),
|
'unique_together': {('name', 'type')},
|
||||||
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,82 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.5 on 2021-04-22 11:25
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
import orchestra.core.validators
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
replaces = [('databases', '0001_initial'), ('databases', '0002_auto_20170528_2005'), ('databases', '0003_database_comments'), ('databases', '0004_auto_20210330_1049')]
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Database',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('name', models.CharField(max_length=64, validators=[orchestra.core.validators.validate_name], verbose_name='name')),
|
|
||||||
('type', models.CharField(choices=[('mysql', 'MySQL'), ('postgres', 'PostgreSQL')], default='mysql', max_length=32, verbose_name='type')),
|
|
||||||
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='databases', to=settings.AUTH_USER_MODEL, verbose_name='Account')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='DatabaseUser',
|
|
||||||
fields=[
|
|
||||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('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(choices=[('mysql', 'MySQL'), ('postgres', 'PostgreSQL')], default='mysql', max_length=32, verbose_name='type')),
|
|
||||||
('account', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='databaseusers', to=settings.AUTH_USER_MODEL, verbose_name='Account')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name_plural': 'DB users',
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='database',
|
|
||||||
name='users',
|
|
||||||
field=models.ManyToManyField(blank=True, related_name='databases', to='databases.DatabaseUser', verbose_name='users'),
|
|
||||||
),
|
|
||||||
migrations.AlterUniqueTogether(
|
|
||||||
name='databaseuser',
|
|
||||||
unique_together=set([('username', 'type')]),
|
|
||||||
),
|
|
||||||
migrations.AlterUniqueTogether(
|
|
||||||
name='database',
|
|
||||||
unique_together=set([('name', 'type')]),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='database',
|
|
||||||
name='type',
|
|
||||||
field=models.CharField(choices=[('mysql', 'MySQL')], default='mysql', max_length=32, verbose_name='type'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='databaseuser',
|
|
||||||
name='type',
|
|
||||||
field=models.CharField(choices=[('mysql', 'MySQL')], default='mysql', max_length=32, verbose_name='type'),
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='database',
|
|
||||||
name='comments',
|
|
||||||
field=models.TextField(blank=True, default=''),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='database',
|
|
||||||
name='type',
|
|
||||||
field=models.CharField(choices=[('mysql', 'MySQL'), ('postgres', 'PostgreSQL')], default='mysql', max_length=32, verbose_name='type'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='databaseuser',
|
|
||||||
name='type',
|
|
||||||
field=models.CharField(choices=[('mysql', 'MySQL'), ('postgres', 'PostgreSQL')], default='mysql', max_length=32, verbose_name='type'),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,25 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.5 on 2017-05-28 18:05
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('databases', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='database',
|
|
||||||
name='type',
|
|
||||||
field=models.CharField(choices=[('mysql', 'MySQL')], default='mysql', max_length=32, verbose_name='type'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='databaseuser',
|
|
||||||
name='type',
|
|
||||||
field=models.CharField(choices=[('mysql', 'MySQL')], default='mysql', max_length=32, verbose_name='type'),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Generated by Django 2.2.28 on 2023-06-28 17:11
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('orchestration', '__first__'),
|
||||||
|
('databases', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='databaseuser',
|
||||||
|
name='target_server',
|
||||||
|
field=models.ForeignKey(default=3, on_delete=django.db.models.deletion.CASCADE, to='orchestration.Server', verbose_name='Target Server'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Generated by Django 2.2.28 on 2023-06-29 16:38
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('orchestration', '__first__'),
|
||||||
|
('databases', '0002_databaseuser_target_server'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='databaseuser',
|
||||||
|
name='permision',
|
||||||
|
field=models.CharField(choices=[('all', 'all'), ('ro', 'read only')], default='all', max_length=20, verbose_name='Permisson'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='databaseuser',
|
||||||
|
name='target_server',
|
||||||
|
field=models.ForeignKey(default=3, on_delete=django.db.models.deletion.CASCADE, to='orchestration.Server', verbose_name='Server'),
|
||||||
|
),
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='databaseuser',
|
||||||
|
unique_together={('username', 'type', 'target_server')},
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,20 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.5 on 2020-02-04 11:21
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('databases', '0002_auto_20170528_2005'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='database',
|
|
||||||
name='comments',
|
|
||||||
field=models.TextField(default=''),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -1,30 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
# Generated by Django 1.10.5 on 2021-03-30 10:49
|
|
||||||
from __future__ import unicode_literals
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('databases', '0003_database_comments'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='database',
|
|
||||||
name='comments',
|
|
||||||
field=models.TextField(blank=True, default=''),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='database',
|
|
||||||
name='type',
|
|
||||||
field=models.CharField(choices=[('mysql', 'MySQL'), ('postgres', 'PostgreSQL')], default='mysql', max_length=32, verbose_name='type'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='databaseuser',
|
|
||||||
name='type',
|
|
||||||
field=models.CharField(choices=[('mysql', 'MySQL'), ('postgres', 'PostgreSQL')], default='mysql', max_length=32, verbose_name='type'),
|
|
||||||
),
|
|
||||||
]
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Generated by Django 2.2.28 on 2023-06-29 16:57
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('orchestration', '__first__'),
|
||||||
|
('databases', '0003_auto_20230629_1838'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='database',
|
||||||
|
name='target_server',
|
||||||
|
field=models.ForeignKey(default=3, on_delete=django.db.models.deletion.CASCADE, to='orchestration.Server', verbose_name='Server'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Generated by Django 2.2.28 on 2023-07-05 10:08
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('orchestration', '__first__'),
|
||||||
|
('databases', '0004_database_target_server'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterUniqueTogether(
|
||||||
|
name='database',
|
||||||
|
unique_together={('name', 'type', 'target_server')},
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Generated by Django 2.2.28 on 2023-07-05 10:37
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import orchestra.core.validators
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('databases', '0005_auto_20230705_1208'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='databaseuser',
|
||||||
|
name='username',
|
||||||
|
field=models.CharField(max_length=32, validators=[orchestra.core.validators.validate_name], verbose_name='username'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -1,7 +1,7 @@
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from orchestra.core import validators
|
from orchestra.core import validators
|
||||||
|
|
||||||
|
@ -23,9 +23,11 @@ class Database(models.Model):
|
||||||
account = models.ForeignKey('accounts.Account', on_delete=models.CASCADE,
|
account = models.ForeignKey('accounts.Account', on_delete=models.CASCADE,
|
||||||
verbose_name=_("Account"), related_name='databases')
|
verbose_name=_("Account"), related_name='databases')
|
||||||
comments = models.TextField(default="", blank=True)
|
comments = models.TextField(default="", blank=True)
|
||||||
|
target_server = models.ForeignKey('orchestration.Server', on_delete=models.CASCADE,
|
||||||
|
verbose_name=_("Server"), default=3 )
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ('name', 'type')
|
unique_together = ('name', 'type', 'target_server')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "%s" % self.name
|
return "%s" % self.name
|
||||||
|
@ -54,7 +56,12 @@ class DatabaseUser(models.Model):
|
||||||
MYSQL = Database.MYSQL
|
MYSQL = Database.MYSQL
|
||||||
POSTGRESQL = Database.POSTGRESQL
|
POSTGRESQL = Database.POSTGRESQL
|
||||||
|
|
||||||
username = models.CharField(_("username"), max_length=16, # MySQL usernames 16 char long
|
typeOfPermision = [
|
||||||
|
('all','all'),
|
||||||
|
('ro', 'read only'),
|
||||||
|
]
|
||||||
|
|
||||||
|
username = models.CharField(_("username"), max_length=32, # MySQL usernames 16 char long
|
||||||
validators=[validators.validate_name])
|
validators=[validators.validate_name])
|
||||||
password = models.CharField(_("password"), max_length=256)
|
password = models.CharField(_("password"), max_length=256)
|
||||||
type = models.CharField(_("type"), max_length=32,
|
type = models.CharField(_("type"), max_length=32,
|
||||||
|
@ -62,10 +69,14 @@ class DatabaseUser(models.Model):
|
||||||
default=settings.DATABASES_DEFAULT_TYPE)
|
default=settings.DATABASES_DEFAULT_TYPE)
|
||||||
account = models.ForeignKey('accounts.Account', on_delete=models.CASCADE,
|
account = models.ForeignKey('accounts.Account', on_delete=models.CASCADE,
|
||||||
verbose_name=_("Account"), related_name='databaseusers')
|
verbose_name=_("Account"), related_name='databaseusers')
|
||||||
|
target_server = models.ForeignKey('orchestration.Server', on_delete=models.CASCADE,
|
||||||
|
verbose_name=_("Server"), default=3 )
|
||||||
|
permision = models.CharField(verbose_name=_("Permisson"), max_length=20, choices=typeOfPermision, default='all')
|
||||||
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name_plural = _("DB users")
|
verbose_name_plural = _("DB users")
|
||||||
unique_together = ('username', 'type')
|
unique_together = ('username', 'type', 'target_server')
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.username
|
return self.username
|
||||||
|
|
|
@ -7,7 +7,7 @@ from django.db.models.functions import Concat, Coalesce
|
||||||
from django.forms.models import modelformset_factory
|
from django.forms.models import modelformset_factory
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import ungettext, ugettext_lazy as _
|
from django.utils.translation import ngettext, gettext_lazy as _
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
|
|
||||||
from orchestra.admin.utils import get_object_from_url, change_url, admin_link
|
from orchestra.admin.utils import get_object_from_url, change_url, admin_link
|
||||||
|
@ -84,7 +84,7 @@ def edit_records(modeladmin, request, queryset):
|
||||||
change_message = modeladmin.construct_change_message(request, fake_form, [formset])
|
change_message = modeladmin.construct_change_message(request, fake_form, [formset])
|
||||||
modeladmin.log_change(request, formset.instance, change_message)
|
modeladmin.log_change(request, formset.instance, change_message)
|
||||||
num = len(formsets)
|
num = len(formsets)
|
||||||
message = ungettext(
|
message = ngettext(
|
||||||
_("Records for one selected domain have been updated."),
|
_("Records for one selected domain have been updated."),
|
||||||
_("Records for %i selected domains have been updated.") % num,
|
_("Records for %i selected domains have been updated.") % num,
|
||||||
num)
|
num)
|
||||||
|
@ -127,7 +127,7 @@ def set_soa(modeladmin, request, queryset):
|
||||||
modeladmin.log_change(request, domain, change_message)
|
modeladmin.log_change(request, domain, change_message)
|
||||||
domain.save()
|
domain.save()
|
||||||
num = len(queryset)
|
num = len(queryset)
|
||||||
msg = ungettext(
|
msg = ngettext(
|
||||||
_("SOA record for one domain has been updated."),
|
_("SOA record for one domain has been updated."),
|
||||||
_("SOA record for %s domains has been updated.") % num,
|
_("SOA record for %s domains has been updated.") % num,
|
||||||
num
|
num
|
||||||
|
|
|
@ -5,7 +5,7 @@ from django.db.models.functions import Concat, Coalesce
|
||||||
from django.templatetags.static import static
|
from django.templatetags.static import static
|
||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import ugettext, ugettext_lazy as _
|
from django.utils.translation import gettext, gettext_lazy as _
|
||||||
|
|
||||||
from orchestra.admin import ExtendedModelAdmin
|
from orchestra.admin import ExtendedModelAdmin
|
||||||
from orchestra.admin.utils import admin_link, change_url
|
from orchestra.admin.utils import admin_link, change_url
|
||||||
|
|
|
@ -2,7 +2,7 @@ import re
|
||||||
import socket
|
import socket
|
||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from orchestra.contrib.orchestration import ServiceController
|
from orchestra.contrib.orchestration import ServiceController
|
||||||
from orchestra.contrib.orchestration import Operation
|
from orchestra.contrib.orchestration import Operation
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue