forked from github/plane
Merge branch 'feat/self_hosted_instance' of github.com:makeplane/plane into feat/self_hosted_instance
This commit is contained in:
commit
c4d5ccdae4
@ -1,49 +0,0 @@
|
|||||||
import os, sys
|
|
||||||
|
|
||||||
|
|
||||||
sys.path.append("/code")
|
|
||||||
|
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "plane.settings.production")
|
|
||||||
|
|
||||||
import django
|
|
||||||
|
|
||||||
django.setup()
|
|
||||||
|
|
||||||
|
|
||||||
def load_config():
|
|
||||||
from plane.license.models import InstanceConfiguration
|
|
||||||
|
|
||||||
config_keys = {
|
|
||||||
# Authentication Settings
|
|
||||||
"GOOGLE_CLIENT_ID": os.environ.get("GOOGLE_CLIENT_ID"),
|
|
||||||
"GITHUB_CLIENT_ID": os.environ.get("GITHUB_CLIENT_ID"),
|
|
||||||
"GITHUB_CLIENT_SECRET": os.environ.get("GITHUB_CLIENT_SECRET"),
|
|
||||||
"ENABLE_SIGNUP": os.environ.get("ENABLE_SIGNUP", "1"),
|
|
||||||
"ENABLE_EMAIL_PASSWORD": os.environ.get("ENABLE_EMAIL_PASSWORD", "1"),
|
|
||||||
"ENABLE_MAGIC_LINK_LOGIN": os.environ.get("ENABLE_MAGIC_LINK_LOGIN", "0"),
|
|
||||||
# Email Settings
|
|
||||||
"EMAIL_HOST": os.environ.get("EMAIL_HOST", ""),
|
|
||||||
"EMAIL_HOST_USER": os.environ.get("EMAIL_HOST_USER", ""),
|
|
||||||
"EMAIL_HOST_PASSWORD": os.environ.get("EMAIL_HOST_PASSWORD"),
|
|
||||||
"EMAIL_PORT": os.environ.get("EMAIL_PORT", "587"),
|
|
||||||
"EMAIL_FROM": os.environ.get("EMAIL_FROM", ""),
|
|
||||||
"EMAIL_USE_TLS": os.environ.get("EMAIL_USE_TLS", "1"),
|
|
||||||
"EMAIL_USE_SSL": os.environ.get("EMAIL_USE_SSL", "0"),
|
|
||||||
# Open AI Settings
|
|
||||||
"OPENAI_API_BASE": os.environ.get("", "https://api.openai.com/v1"),
|
|
||||||
"OPENAI_API_KEY": os.environ.get("OPENAI_API_KEY", "sk-"),
|
|
||||||
"GPT_ENGINE": os.environ.get("GPT_ENGINE", "gpt-3.5-turbo"),
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, value in config_keys.items():
|
|
||||||
obj, created = InstanceConfiguration.objects.get_or_create(
|
|
||||||
key=key
|
|
||||||
)
|
|
||||||
obj.value = value
|
|
||||||
obj.save()
|
|
||||||
|
|
||||||
print(f"{key} loaded with value from environment variable.")
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
load_config()
|
|
@ -4,9 +4,9 @@ python manage.py wait_for_db
|
|||||||
python manage.py migrate
|
python manage.py migrate
|
||||||
|
|
||||||
# Register instance
|
# Register instance
|
||||||
python bin/instance_registration.py
|
python manage.py register_instance
|
||||||
# Load the configuration variable
|
# Load the configuration variable
|
||||||
python bin/instance_configuration.py
|
python manage.py configure_instance
|
||||||
# Create the default bucket
|
# Create the default bucket
|
||||||
python bin/bucket_script.py
|
python bin/bucket_script.py
|
||||||
|
|
||||||
|
71
apiserver/plane/db/management/commands/create_bucket.py
Normal file
71
apiserver/plane/db/management/commands/create_bucket.py
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
# Python imports
|
||||||
|
import boto3
|
||||||
|
import json
|
||||||
|
from botocore.exceptions import ClientError
|
||||||
|
|
||||||
|
# Django imports
|
||||||
|
from django.core.management import BaseCommand
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Create the default bucket for the instance"
|
||||||
|
|
||||||
|
def set_bucket_public_policy(self, s3_client, bucket_name):
|
||||||
|
public_policy = {
|
||||||
|
"Version": "2012-10-17",
|
||||||
|
"Statement": [{
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Principal": "*",
|
||||||
|
"Action": ["s3:GetObject"],
|
||||||
|
"Resource": [f"arn:aws:s3:::{bucket_name}/*"]
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
s3_client.put_bucket_policy(
|
||||||
|
Bucket=bucket_name,
|
||||||
|
Policy=json.dumps(public_policy)
|
||||||
|
)
|
||||||
|
self.stdout.write(self.style.SUCCESS(f"Public read access policy set for bucket '{bucket_name}'."))
|
||||||
|
except ClientError as e:
|
||||||
|
self.stdout.write(self.style.ERROR(f"Error setting public read access policy: {e}"))
|
||||||
|
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
# Create a session using the credentials from Django settings
|
||||||
|
try:
|
||||||
|
session = boto3.session.Session(
|
||||||
|
aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
|
||||||
|
aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
|
||||||
|
)
|
||||||
|
# Create an S3 client using the session
|
||||||
|
s3_client = session.client('s3', endpoint_url=settings.AWS_S3_ENDPOINT_URL)
|
||||||
|
bucket_name = settings.AWS_STORAGE_BUCKET_NAME
|
||||||
|
|
||||||
|
self.stdout.write(self.style.NOTICE("Checking bucket..."))
|
||||||
|
|
||||||
|
# Check if the bucket exists
|
||||||
|
s3_client.head_bucket(Bucket=bucket_name)
|
||||||
|
|
||||||
|
self.set_bucket_public_policy(s3_client, bucket_name)
|
||||||
|
except ClientError as e:
|
||||||
|
error_code = int(e.response['Error']['Code'])
|
||||||
|
bucket_name = settings.AWS_STORAGE_BUCKET_NAME
|
||||||
|
if error_code == 404:
|
||||||
|
# Bucket does not exist, create it
|
||||||
|
self.stdout.write(self.style.WARNING(f"Bucket '{bucket_name}' does not exist. Creating bucket..."))
|
||||||
|
try:
|
||||||
|
s3_client.create_bucket(Bucket=bucket_name)
|
||||||
|
self.stdout.write(self.style.SUCCESS(f"Bucket '{bucket_name}' created successfully."))
|
||||||
|
self.set_bucket_public_policy(s3_client, bucket_name)
|
||||||
|
except ClientError as create_error:
|
||||||
|
self.stdout.write(self.style.ERROR(f"Failed to create bucket: {create_error}"))
|
||||||
|
elif error_code == 403:
|
||||||
|
# Access to the bucket is forbidden
|
||||||
|
self.stdout.write(self.style.ERROR(f"Access to the bucket '{bucket_name}' is forbidden. Check permissions."))
|
||||||
|
else:
|
||||||
|
# Another ClientError occurred
|
||||||
|
self.stdout.write(self.style.ERROR(f"Failed to check bucket: {e}"))
|
||||||
|
except Exception as ex:
|
||||||
|
# Handle any other exception
|
||||||
|
self.stdout.write(self.style.ERROR(f"An error occurred: {ex}"))
|
0
apiserver/plane/license/management/__init__.py
Normal file
0
apiserver/plane/license/management/__init__.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# Python imports
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Django imports
|
||||||
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
# Module imports
|
||||||
|
from plane.license.models import InstanceConfiguration
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Configure instance variables"
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
config_keys = {
|
||||||
|
# Authentication Settings
|
||||||
|
"GOOGLE_CLIENT_ID": os.environ.get("GOOGLE_CLIENT_ID"),
|
||||||
|
"GITHUB_CLIENT_ID": os.environ.get("GITHUB_CLIENT_ID"),
|
||||||
|
"GITHUB_CLIENT_SECRET": os.environ.get("GITHUB_CLIENT_SECRET"),
|
||||||
|
"ENABLE_SIGNUP": os.environ.get("ENABLE_SIGNUP", "1"),
|
||||||
|
"ENABLE_EMAIL_PASSWORD": os.environ.get("ENABLE_EMAIL_PASSWORD", "1"),
|
||||||
|
"ENABLE_MAGIC_LINK_LOGIN": os.environ.get("ENABLE_MAGIC_LINK_LOGIN", "0"),
|
||||||
|
# Email Settings
|
||||||
|
"EMAIL_HOST": os.environ.get("EMAIL_HOST", ""),
|
||||||
|
"EMAIL_HOST_USER": os.environ.get("EMAIL_HOST_USER", ""),
|
||||||
|
"EMAIL_HOST_PASSWORD": os.environ.get("EMAIL_HOST_PASSWORD"),
|
||||||
|
"EMAIL_PORT": os.environ.get("EMAIL_PORT", "587"),
|
||||||
|
"EMAIL_FROM": os.environ.get("EMAIL_FROM", ""),
|
||||||
|
"EMAIL_USE_TLS": os.environ.get("EMAIL_USE_TLS", "1"),
|
||||||
|
"EMAIL_USE_SSL": os.environ.get("EMAIL_USE_SSL", "0"),
|
||||||
|
# Open AI Settings
|
||||||
|
"OPENAI_API_BASE": os.environ.get("", "https://api.openai.com/v1"),
|
||||||
|
"OPENAI_API_KEY": os.environ.get("OPENAI_API_KEY", "sk-"),
|
||||||
|
"GPT_ENGINE": os.environ.get("GPT_ENGINE", "gpt-3.5-turbo"),
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value in config_keys.items():
|
||||||
|
obj, created = InstanceConfiguration.objects.get_or_create(
|
||||||
|
key=key
|
||||||
|
)
|
||||||
|
if created:
|
||||||
|
obj.value = value
|
||||||
|
obj.save()
|
||||||
|
self.stdout.write(self.style.SUCCESS(f"{key} loaded with value from environment variable."))
|
||||||
|
else:
|
||||||
|
self.stdout.write(self.style.WARNING(f"{key} configuration already exists"))
|
@ -1,41 +1,43 @@
|
|||||||
# Python imports
|
# Python imports
|
||||||
import os, sys
|
|
||||||
import json
|
import json
|
||||||
import uuid
|
import os
|
||||||
import requests
|
import requests
|
||||||
|
import uuid
|
||||||
|
|
||||||
# Django imports
|
# Django imports
|
||||||
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.core.validators import validate_email
|
||||||
|
|
||||||
|
# Module imports
|
||||||
|
from plane.db.models import User
|
||||||
|
from plane.license.models import Instance, InstanceAdmin
|
||||||
|
|
||||||
|
|
||||||
sys.path.append("/code")
|
class Command(BaseCommand):
|
||||||
|
help = "Check if instance in registered else register"
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "plane.settings.production")
|
|
||||||
|
|
||||||
import django
|
|
||||||
|
|
||||||
django.setup()
|
|
||||||
|
|
||||||
|
|
||||||
def instance_registration():
|
|
||||||
try:
|
|
||||||
# Module imports
|
|
||||||
from plane.db.models import User
|
|
||||||
from plane.license.models import Instance, InstanceAdmin
|
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
# Check if the instance is registered
|
# Check if the instance is registered
|
||||||
instance = Instance.objects.first()
|
instance = Instance.objects.first()
|
||||||
|
|
||||||
# If instance is None then register this instance
|
# If instance is None then register this instance
|
||||||
if instance is None:
|
if instance is None:
|
||||||
with open("/code/package.json", "r") as file:
|
with open("package.json", "r") as file:
|
||||||
# Load JSON content from the file
|
# Load JSON content from the file
|
||||||
data = json.load(file)
|
data = json.load(file)
|
||||||
|
|
||||||
admin_email = os.environ.get("ADMIN_EMAIL")
|
admin_email = os.environ.get("ADMIN_EMAIL")
|
||||||
|
|
||||||
|
try:
|
||||||
|
validate_email(admin_email)
|
||||||
|
except ValidationError:
|
||||||
|
CommandError(f"{admin_email} is not a valid ADMIN_EMAIL")
|
||||||
|
|
||||||
# Raise an exception if the admin email is not provided
|
# Raise an exception if the admin email is not provided
|
||||||
if not admin_email:
|
if not admin_email:
|
||||||
raise Exception("ADMIN_EMAIL is required")
|
raise CommandError("ADMIN_EMAIL is required")
|
||||||
|
|
||||||
# Check if the admin email user exists
|
# Check if the admin email user exists
|
||||||
user = User.objects.filter(email=admin_email).first()
|
user = User.objects.filter(email=admin_email).first()
|
||||||
@ -49,7 +51,7 @@ def instance_registration():
|
|||||||
license_engine_base_url = os.environ.get("LICENSE_ENGINE_BASE_URL")
|
license_engine_base_url = os.environ.get("LICENSE_ENGINE_BASE_URL")
|
||||||
|
|
||||||
if not license_engine_base_url:
|
if not license_engine_base_url:
|
||||||
raise Exception("LICENSE_ENGINE_BASE_URL is required")
|
raise CommandError("LICENSE_ENGINE_BASE_URL is required")
|
||||||
|
|
||||||
headers = {"Content-Type": "application/json"}
|
headers = {"Content-Type": "application/json"}
|
||||||
|
|
||||||
@ -84,21 +86,19 @@ def instance_registration():
|
|||||||
role=20,
|
role=20,
|
||||||
)
|
)
|
||||||
|
|
||||||
print(
|
self.stdout.write(
|
||||||
|
self.style.SUCCESS(
|
||||||
f"Instance succesfully registered with owner: {instance.primary_owner.email}"
|
f"Instance succesfully registered with owner: {instance.primary_owner.email}"
|
||||||
)
|
)
|
||||||
return
|
|
||||||
|
|
||||||
print("Instance could not be registered")
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
print(
|
|
||||||
f"Instance already registered with instance owner: {instance.primary_owner.email}"
|
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
except ImportError:
|
|
||||||
raise ImportError
|
|
||||||
|
|
||||||
|
self.stdout.write(self.style.WARNING("Instance could not be registered"))
|
||||||
if __name__ == "__main__":
|
return
|
||||||
instance_registration()
|
else:
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.SUCCESS(
|
||||||
|
f"Instance already registered with instance owner: {instance.primary_owner.email}"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return
|
@ -1,4 +1,4 @@
|
|||||||
# Generated by Django 4.2.5 on 2023-11-13 14:31
|
# Generated by Django 4.2.5 on 2023-11-15 14:22
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
@ -15,6 +15,34 @@ class Migration(migrations.Migration):
|
|||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Instance',
|
||||||
|
fields=[
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
|
||||||
|
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
|
||||||
|
('instance_name', models.CharField(max_length=255)),
|
||||||
|
('whitelist_emails', models.TextField(blank=True, null=True)),
|
||||||
|
('instance_id', models.CharField(max_length=25, unique=True)),
|
||||||
|
('license_key', models.CharField(blank=True, max_length=256, null=True)),
|
||||||
|
('api_key', models.CharField(max_length=16)),
|
||||||
|
('version', models.CharField(max_length=10)),
|
||||||
|
('primary_email', models.CharField(max_length=256)),
|
||||||
|
('last_checked_at', models.DateTimeField()),
|
||||||
|
('namespace', models.CharField(blank=True, max_length=50, null=True)),
|
||||||
|
('is_telemetry_enabled', models.BooleanField(default=True)),
|
||||||
|
('is_support_required', models.BooleanField(default=True)),
|
||||||
|
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
|
||||||
|
('primary_owner', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='instance_primary_owner', to=settings.AUTH_USER_MODEL)),
|
||||||
|
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Instance',
|
||||||
|
'verbose_name_plural': 'Instances',
|
||||||
|
'db_table': 'instances',
|
||||||
|
'ordering': ('-created_at',),
|
||||||
|
},
|
||||||
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='InstanceConfiguration',
|
name='InstanceConfiguration',
|
||||||
fields=[
|
fields=[
|
||||||
@ -34,30 +62,21 @@ class Migration(migrations.Migration):
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
migrations.CreateModel(
|
migrations.CreateModel(
|
||||||
name='Instance',
|
name='InstanceAdmin',
|
||||||
fields=[
|
fields=[
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
|
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
|
||||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
|
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
|
||||||
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
|
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
|
||||||
('instance_name', models.CharField(max_length=255)),
|
('role', models.PositiveIntegerField(choices=[(20, 'Owner'), (15, 'Admin')], default=15)),
|
||||||
('whitelist_emails', models.TextField(blank=True, null=True)),
|
|
||||||
('instance_id', models.CharField(max_length=25, unique=True)),
|
|
||||||
('license_key', models.CharField(blank=True, max_length=256, null=True)),
|
|
||||||
('api_key', models.CharField(max_length=16)),
|
|
||||||
('version', models.CharField(max_length=10)),
|
|
||||||
('email', models.CharField(max_length=256)),
|
|
||||||
('last_checked_at', models.DateTimeField()),
|
|
||||||
('namespace', models.CharField(blank=True, max_length=50, null=True)),
|
|
||||||
('is_telemetry_enabled', models.BooleanField(default=True)),
|
|
||||||
('is_support_required', models.BooleanField(default=True)),
|
|
||||||
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
|
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
|
||||||
('owner', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='instance_owner', to=settings.AUTH_USER_MODEL)),
|
('instance', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='admins', to='license.instance')),
|
||||||
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
|
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
|
||||||
|
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='instance_owner', to=settings.AUTH_USER_MODEL)),
|
||||||
],
|
],
|
||||||
options={
|
options={
|
||||||
'verbose_name': 'Instance',
|
'verbose_name': 'Instance Admin',
|
||||||
'verbose_name_plural': 'Instances',
|
'verbose_name_plural': 'Instance Admins',
|
||||||
'db_table': 'instances',
|
'db_table': 'instance_admins',
|
||||||
'ordering': ('-created_at',),
|
'ordering': ('-created_at',),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -1,50 +0,0 @@
|
|||||||
# Generated by Django 4.2.5 on 2023-11-14 10:14
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
||||||
('license', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RenameField(
|
|
||||||
model_name='instance',
|
|
||||||
old_name='email',
|
|
||||||
new_name='primary_email',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='instance',
|
|
||||||
name='owner',
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='instance',
|
|
||||||
name='primary_owner',
|
|
||||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='instance_primary_owner', to=settings.AUTH_USER_MODEL),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='InstanceAdmin',
|
|
||||||
fields=[
|
|
||||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')),
|
|
||||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')),
|
|
||||||
('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)),
|
|
||||||
('role', models.PositiveIntegerField(choices=[(20, 'Owner'), (15, 'Admin')], default=15)),
|
|
||||||
('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')),
|
|
||||||
('instance', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='admins', to='license.instance')),
|
|
||||||
('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='%(class)s_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')),
|
|
||||||
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='instance_owner', to=settings.AUTH_USER_MODEL)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Instance Admin',
|
|
||||||
'verbose_name_plural': 'Instance Admins',
|
|
||||||
'db_table': 'instance_admins',
|
|
||||||
'ordering': ('-created_at',),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
Loading…
Reference in New Issue
Block a user