/payment", views.group_payment, name="payment"),
]
diff --git a/custom_auth/migrations/0001_initial.py b/custom_auth/migrations/0001_initial.py
index 5610db9..d4e05e8 100644
--- a/custom_auth/migrations/0001_initial.py
+++ b/custom_auth/migrations/0001_initial.py
@@ -1,136 +1,200 @@
# Generated by Django 4.2.7 on 2023-11-26 12:06
-import custom_auth.models
-from django.conf import settings
+import uuid
+
import django.contrib.auth.validators
-from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
+from django.conf import settings
+from django.db import migrations, models
+
+import custom_auth.models
import dsbd.models
-import uuid
class Migration(migrations.Migration):
-
initial = True
- dependencies = [
- ]
+ dependencies = []
operations = [
migrations.CreateModel(
- name='User',
+ name="User",
fields=[
- ('id', models.BigAutoField(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')),
- ('created_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='作成日')),
- ('updated_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='更新日')),
- ('username', models.CharField(max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
- ('username_jp', models.CharField(max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username(japanese)')),
- ('email', models.EmailField(max_length=254, unique=True, verbose_name='email')),
- ('is_staff', models.BooleanField(default=False, verbose_name='管理者ステータス')),
- ('is_active', models.BooleanField(default=False, verbose_name='有効')),
+ ("id", models.BigAutoField(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")),
+ ("created_at", models.DateTimeField(default=django.utils.timezone.now, verbose_name="作成日")),
+ ("updated_at", models.DateTimeField(default=django.utils.timezone.now, verbose_name="更新日")),
+ (
+ "username",
+ models.CharField(
+ max_length=150,
+ unique=True,
+ validators=[django.contrib.auth.validators.UnicodeUsernameValidator()],
+ verbose_name="username",
+ ),
+ ),
+ (
+ "username_jp",
+ models.CharField(
+ max_length=150,
+ unique=True,
+ validators=[django.contrib.auth.validators.UnicodeUsernameValidator()],
+ verbose_name="username(japanese)",
+ ),
+ ),
+ ("email", models.EmailField(max_length=254, unique=True, verbose_name="email")),
+ ("is_staff", models.BooleanField(default=False, verbose_name="管理者ステータス")),
+ ("is_active", models.BooleanField(default=False, verbose_name="有効")),
],
options={
- 'verbose_name': 'ユーザ',
- 'verbose_name_plural': 'ユーザ',
+ "verbose_name": "ユーザ",
+ "verbose_name_plural": "ユーザ",
},
),
migrations.CreateModel(
- name='Group',
+ name="Group",
fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('created_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='作成日')),
- ('updated_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='更新日')),
- ('name', models.CharField(max_length=150, unique=True, verbose_name='name')),
- ('name_jp', models.CharField(max_length=150, unique=True, verbose_name='name(japanese)')),
- ('comment', models.CharField(blank=True, default='', max_length=250, verbose_name='comment')),
- ('status', models.IntegerField(choices=[(1, '有効'), (10, 'ユーザより廃止'), (11, '運営委員より廃止'), (12, '審査落ち')], default=0, verbose_name='ステータス')),
- ('add_service', models.BooleanField(default=False, verbose_name='サービス追加許可')),
- ('membership_type', models.IntegerField(choices=[(1, '一般会員'), (40, '運営委員(優勝)'), (70, '学生委員'), (90, '運営委員(無償)'), (99, 'その他')], default=1, verbose_name='会員種別')),
- ('membership_expired_at', models.DateTimeField(blank=True, null=True, verbose_name='有効期限')),
- ('agree', models.BooleanField(default=False, verbose_name='規約確認済み')),
- ('question', dsbd.models.MediumTextField(default='', verbose_name='質問内容')),
- ('is_pass', models.BooleanField(default=False, verbose_name='審査OK')),
- ('stripe_customer_id', models.CharField(blank=True, max_length=200, null=True, verbose_name='Stripe(CusID)')),
- ('stripe_subscription_id', models.CharField(blank=True, max_length=200, null=True, verbose_name='Stripe(SubID)')),
- ('postcode', models.CharField(default='', max_length=20, verbose_name='郵便番号')),
- ('address', models.CharField(default='', max_length=250, verbose_name='住所')),
- ('address_en', models.CharField(default='', max_length=250, verbose_name='住所(English)')),
- ('phone', models.CharField(default='', max_length=30, verbose_name='phone')),
- ('country', models.CharField(default='Japan', max_length=100, verbose_name='居住国')),
- ('contract_type', models.CharField(choices=[('E-Mail', 'E-Mail'), ('AirMail', 'AirMail)')], default='E-Mail', max_length=100, verbose_name='契約種別')),
+ ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
+ ("created_at", models.DateTimeField(default=django.utils.timezone.now, verbose_name="作成日")),
+ ("updated_at", models.DateTimeField(default=django.utils.timezone.now, verbose_name="更新日")),
+ ("name", models.CharField(max_length=150, unique=True, verbose_name="name")),
+ ("name_jp", models.CharField(max_length=150, unique=True, verbose_name="name(japanese)")),
+ ("comment", models.CharField(blank=True, default="", max_length=250, verbose_name="comment")),
+ (
+ "status",
+ models.IntegerField(
+ choices=[(1, "有効"), (10, "ユーザより廃止"), (11, "運営委員より廃止"), (12, "審査落ち")],
+ default=0,
+ verbose_name="ステータス",
+ ),
+ ),
+ ("add_service", models.BooleanField(default=False, verbose_name="サービス追加許可")),
+ (
+ "membership_type",
+ models.IntegerField(
+ choices=[
+ (1, "一般会員"),
+ (40, "運営委員(優勝)"),
+ (70, "学生委員"),
+ (90, "運営委員(無償)"),
+ (99, "その他"),
+ ],
+ default=1,
+ verbose_name="会員種別",
+ ),
+ ),
+ ("membership_expired_at", models.DateTimeField(blank=True, null=True, verbose_name="有効期限")),
+ ("agree", models.BooleanField(default=False, verbose_name="規約確認済み")),
+ ("question", dsbd.models.MediumTextField(default="", verbose_name="質問内容")),
+ ("is_pass", models.BooleanField(default=False, verbose_name="審査OK")),
+ (
+ "stripe_customer_id",
+ models.CharField(blank=True, max_length=200, null=True, verbose_name="Stripe(CusID)"),
+ ),
+ (
+ "stripe_subscription_id",
+ models.CharField(blank=True, max_length=200, null=True, verbose_name="Stripe(SubID)"),
+ ),
+ ("postcode", models.CharField(default="", max_length=20, verbose_name="郵便番号")),
+ ("address", models.CharField(default="", max_length=250, verbose_name="住所")),
+ ("address_en", models.CharField(default="", max_length=250, verbose_name="住所(English)")),
+ ("phone", models.CharField(default="", max_length=30, verbose_name="phone")),
+ ("country", models.CharField(default="Japan", max_length=100, verbose_name="居住国")),
+ (
+ "contract_type",
+ models.CharField(
+ choices=[("E-Mail", "E-Mail"), ("AirMail", "AirMail)")],
+ default="E-Mail",
+ max_length=100,
+ verbose_name="契約種別",
+ ),
+ ),
],
options={
- 'verbose_name': 'グループ',
- 'verbose_name_plural': 'グループ',
+ "verbose_name": "グループ",
+ "verbose_name_plural": "グループ",
},
),
migrations.CreateModel(
- name='UserGroup',
+ name="UserGroup",
fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('created_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='作成日')),
- ('updated_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='更新日')),
- ('is_admin', models.BooleanField(default=False, verbose_name='管理者')),
- ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='custom_auth.group')),
- ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
+ ("created_at", models.DateTimeField(default=django.utils.timezone.now, verbose_name="作成日")),
+ ("updated_at", models.DateTimeField(default=django.utils.timezone.now, verbose_name="更新日")),
+ ("is_admin", models.BooleanField(default=False, verbose_name="管理者")),
+ ("group", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="custom_auth.group")),
+ ("user", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
- 'verbose_name': 'ユーザ・グループ',
- 'verbose_name_plural': 'ユーザ・グループ',
- 'unique_together': {('user', 'group')},
+ "verbose_name": "ユーザ・グループ",
+ "verbose_name_plural": "ユーザ・グループ",
+ "unique_together": {("user", "group")},
},
),
migrations.CreateModel(
- name='UserEmailVerify',
+ name="UserEmailVerify",
fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('created_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='作成日')),
- ('token', models.CharField(max_length=100, verbose_name='token')),
- ('expired_at', models.DateTimeField(default=custom_auth.models.email_verify_expire_date, verbose_name='有効期限')),
- ('is_used', models.BooleanField(default=False, verbose_name='使用済み')),
- ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
+ ("created_at", models.DateTimeField(default=django.utils.timezone.now, verbose_name="作成日")),
+ ("token", models.CharField(max_length=100, verbose_name="token")),
+ (
+ "expired_at",
+ models.DateTimeField(default=custom_auth.models.email_verify_expire_date, verbose_name="有効期限"),
+ ),
+ ("is_used", models.BooleanField(default=False, verbose_name="使用済み")),
+ ("user", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
- 'verbose_name': 'E-Mail用のVerify',
- 'verbose_name_plural': 'E-Mail用のVerify',
+ "verbose_name": "E-Mail用のVerify",
+ "verbose_name_plural": "E-Mail用のVerify",
},
),
migrations.CreateModel(
- name='UserActivateToken',
+ name="UserActivateToken",
fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('created_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='作成日')),
- ('token', models.UUIDField(default=uuid.uuid4, verbose_name='Active token')),
- ('expired_at', models.DateTimeField(default=custom_auth.models.user_activate_expire_date, verbose_name='有効期限')),
- ('is_used', models.BooleanField(default=False, verbose_name='使用済み')),
- ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
+ ("created_at", models.DateTimeField(default=django.utils.timezone.now, verbose_name="作成日")),
+ ("token", models.UUIDField(default=uuid.uuid4, verbose_name="Active token")),
+ (
+ "expired_at",
+ models.DateTimeField(default=custom_auth.models.user_activate_expire_date, verbose_name="有効期限"),
+ ),
+ ("is_used", models.BooleanField(default=False, verbose_name="使用済み")),
+ ("user", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
- 'verbose_name': 'Activate用のToken',
- 'verbose_name_plural': 'Activate用のToken',
+ "verbose_name": "Activate用のToken",
+ "verbose_name_plural": "Activate用のToken",
},
),
migrations.CreateModel(
- name='TOTPDevice',
+ name="TOTPDevice",
fields=[
- ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('created_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='作成日')),
- ('title', models.CharField(max_length=100, verbose_name='title')),
- ('secret', models.CharField(max_length=100, verbose_name='secret')),
- ('is_active', models.BooleanField(default=False, verbose_name='有効')),
- ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
+ ("created_at", models.DateTimeField(default=django.utils.timezone.now, verbose_name="作成日")),
+ ("title", models.CharField(max_length=100, verbose_name="title")),
+ ("secret", models.CharField(max_length=100, verbose_name="secret")),
+ ("is_active", models.BooleanField(default=False, verbose_name="有効")),
+ ("user", models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
- 'verbose_name': 'TOTP Device',
- 'verbose_name_plural': 'TOTP Device',
+ "verbose_name": "TOTP Device",
+ "verbose_name_plural": "TOTP Device",
},
),
migrations.AddField(
- model_name='user',
- name='groups',
- field=models.ManyToManyField(blank=True, help_text='Specific Groups for this user.', related_name='user_set', related_query_name='user', through='custom_auth.UserGroup', to='custom_auth.group', verbose_name='groups'),
+ model_name="user",
+ name="groups",
+ field=models.ManyToManyField(
+ blank=True,
+ help_text="Specific Groups for this user.",
+ related_name="user_set",
+ related_query_name="user",
+ through="custom_auth.UserGroup",
+ to="custom_auth.group",
+ verbose_name="groups",
+ ),
),
]
diff --git a/custom_auth/migrations/0002_user_display_name.py b/custom_auth/migrations/0002_user_display_name.py
index 9470716..76ebaa7 100644
--- a/custom_auth/migrations/0002_user_display_name.py
+++ b/custom_auth/migrations/0002_user_display_name.py
@@ -4,15 +4,14 @@
class Migration(migrations.Migration):
-
dependencies = [
- ('custom_auth', '0001_initial'),
+ ("custom_auth", "0001_initial"),
]
operations = [
migrations.AddField(
- model_name='user',
- name='display_name',
- field=models.CharField(blank=True, default='', max_length=150, verbose_name='display_name'),
+ model_name="user",
+ name="display_name",
+ field=models.CharField(blank=True, default="", max_length=150, verbose_name="display_name"),
),
]
diff --git a/custom_auth/migrations/0003_rename_add_service_group_allow_service_add_and_more.py b/custom_auth/migrations/0003_rename_add_service_group_allow_service_add_and_more.py
index cc83fc9..cd5a5a6 100644
--- a/custom_auth/migrations/0003_rename_add_service_group_allow_service_add_and_more.py
+++ b/custom_auth/migrations/0003_rename_add_service_group_allow_service_add_and_more.py
@@ -4,29 +4,28 @@
class Migration(migrations.Migration):
-
dependencies = [
- ('custom_auth', '0002_user_display_name'),
+ ("custom_auth", "0002_user_display_name"),
]
operations = [
migrations.RenameField(
- model_name='group',
- old_name='add_service',
- new_name='allow_service_add',
+ model_name="group",
+ old_name="add_service",
+ new_name="allow_service_add",
),
migrations.RemoveField(
- model_name='group',
- name='address_en',
+ model_name="group",
+ name="address_en",
),
migrations.AddField(
- model_name='group',
- name='address_jp',
- field=models.CharField(default='', max_length=250, verbose_name='住所(Japanese)'),
+ model_name="group",
+ name="address_jp",
+ field=models.CharField(default="", max_length=250, verbose_name="住所(Japanese)"),
),
migrations.AddField(
- model_name='user',
- name='allow_group_add',
- field=models.BooleanField(default=True, verbose_name='グループ追加許可'),
+ model_name="user",
+ name="allow_group_add",
+ field=models.BooleanField(default=True, verbose_name="グループ追加許可"),
),
]
diff --git a/custom_auth/migrations/0004_group_users_alter_user_groups.py b/custom_auth/migrations/0004_group_users_alter_user_groups.py
index ab49162..d482c78 100644
--- a/custom_auth/migrations/0004_group_users_alter_user_groups.py
+++ b/custom_auth/migrations/0004_group_users_alter_user_groups.py
@@ -5,20 +5,23 @@
class Migration(migrations.Migration):
-
dependencies = [
- ('custom_auth', '0003_rename_add_service_group_allow_service_add_and_more'),
+ ("custom_auth", "0003_rename_add_service_group_allow_service_add_and_more"),
]
operations = [
migrations.AddField(
- model_name='group',
- name='users',
- field=models.ManyToManyField(blank=True, related_name='group_users_set', through='custom_auth.UserGroup', to=settings.AUTH_USER_MODEL),
+ model_name="group",
+ name="users",
+ field=models.ManyToManyField(
+ blank=True, related_name="group_users_set", through="custom_auth.UserGroup", to=settings.AUTH_USER_MODEL
+ ),
),
migrations.AlterField(
- model_name='user',
- name='groups',
- field=models.ManyToManyField(blank=True, related_name='user_set', through='custom_auth.UserGroup', to='custom_auth.group'),
+ model_name="user",
+ name="groups",
+ field=models.ManyToManyField(
+ blank=True, related_name="user_set", through="custom_auth.UserGroup", to="custom_auth.group"
+ ),
),
]
diff --git a/custom_auth/migrations/0005_alter_group_status.py b/custom_auth/migrations/0005_alter_group_status.py
new file mode 100644
index 0000000..e3ff3af
--- /dev/null
+++ b/custom_auth/migrations/0005_alter_group_status.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.1.1 on 2024-09-08 15:50
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('custom_auth', '0004_group_users_alter_user_groups'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='group',
+ name='status',
+ field=models.IntegerField(choices=[(1, '有効'), (10, 'ユーザより廃止'), (11, '運営委員より廃止'), (12, '審査落ち')], default=1, verbose_name='ステータス'),
+ ),
+ ]
diff --git a/custom_auth/migrations/0006_group_allow_jpnic_add.py b/custom_auth/migrations/0006_group_allow_jpnic_add.py
new file mode 100644
index 0000000..2fc45be
--- /dev/null
+++ b/custom_auth/migrations/0006_group_allow_jpnic_add.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.1.1 on 2024-09-13 16:58
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('custom_auth', '0005_alter_group_status'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='group',
+ name='allow_jpnic_add',
+ field=models.BooleanField(default=False, verbose_name='JPNIC情報追加許可'),
+ ),
+ ]
diff --git a/custom_auth/migrations/0007_historicalgroup_historicaluser_historicalusergroup.py b/custom_auth/migrations/0007_historicalgroup_historicaluser_historicalusergroup.py
new file mode 100644
index 0000000..371275a
--- /dev/null
+++ b/custom_auth/migrations/0007_historicalgroup_historicaluser_historicalusergroup.py
@@ -0,0 +1,110 @@
+# Generated by Django 5.1.1 on 2024-09-14 20:33
+
+import django.contrib.auth.validators
+import django.db.models.deletion
+import django.utils.timezone
+import dsbd.models
+import simple_history.models
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('custom_auth', '0006_group_allow_jpnic_add'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='HistoricalGroup',
+ fields=[
+ ('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
+ ('created_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='作成日')),
+ ('updated_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='更新日')),
+ ('name', models.CharField(db_index=True, max_length=150, verbose_name='name')),
+ ('name_jp', models.CharField(db_index=True, max_length=150, verbose_name='name(japanese)')),
+ ('comment', models.CharField(blank=True, default='', max_length=250, verbose_name='comment')),
+ ('status', models.IntegerField(choices=[(1, '有効'), (10, 'ユーザより廃止'), (11, '運営委員より廃止'), (12, '審査落ち')], default=1, verbose_name='ステータス')),
+ ('allow_service_add', models.BooleanField(default=False, verbose_name='サービス追加許可')),
+ ('allow_jpnic_add', models.BooleanField(default=False, verbose_name='JPNIC情報追加許可')),
+ ('membership_type', models.IntegerField(choices=[(1, '一般会員'), (40, '運営委員(優勝)'), (70, '学生委員'), (90, '運営委員(無償)'), (99, 'その他')], default=1, verbose_name='会員種別')),
+ ('membership_expired_at', models.DateTimeField(blank=True, null=True, verbose_name='有効期限')),
+ ('agree', models.BooleanField(default=False, verbose_name='規約確認済み')),
+ ('question', dsbd.models.MediumTextField(default='', verbose_name='質問内容')),
+ ('is_pass', models.BooleanField(default=False, verbose_name='審査OK')),
+ ('stripe_customer_id', models.CharField(blank=True, max_length=200, null=True, verbose_name='Stripe(CusID)')),
+ ('stripe_subscription_id', models.CharField(blank=True, max_length=200, null=True, verbose_name='Stripe(SubID)')),
+ ('postcode', models.CharField(default='', max_length=20, verbose_name='郵便番号')),
+ ('address', models.CharField(default='', max_length=250, verbose_name='住所')),
+ ('address_jp', models.CharField(default='', max_length=250, verbose_name='住所(Japanese)')),
+ ('phone', models.CharField(default='', max_length=30, verbose_name='phone')),
+ ('country', models.CharField(default='Japan', max_length=100, verbose_name='居住国')),
+ ('contract_type', models.CharField(choices=[('E-Mail', 'E-Mail'), ('AirMail', 'AirMail)')], default='E-Mail', max_length=100, verbose_name='契約種別')),
+ ('history_id', models.AutoField(primary_key=True, serialize=False)),
+ ('history_date', models.DateTimeField(db_index=True)),
+ ('history_change_reason', models.CharField(max_length=100, null=True)),
+ ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
+ ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
+ ],
+ options={
+ 'verbose_name': 'historical グループ',
+ 'verbose_name_plural': 'historical グループ',
+ 'ordering': ('-history_date', '-history_id'),
+ 'get_latest_by': ('history_date', 'history_id'),
+ },
+ bases=(simple_history.models.HistoricalChanges, models.Model),
+ ),
+ migrations.CreateModel(
+ name='HistoricalUser',
+ fields=[
+ ('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
+ ('password', models.CharField(max_length=128, verbose_name='password')),
+ ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
+ ('created_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='作成日')),
+ ('updated_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='更新日')),
+ ('display_name', models.CharField(blank=True, default='', max_length=150, verbose_name='display_name')),
+ ('username', models.CharField(db_index=True, max_length=150, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
+ ('username_jp', models.CharField(db_index=True, max_length=150, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username(japanese)')),
+ ('email', models.EmailField(db_index=True, max_length=254, verbose_name='email')),
+ ('is_staff', models.BooleanField(default=False, verbose_name='管理者ステータス')),
+ ('is_active', models.BooleanField(default=False, verbose_name='有効')),
+ ('allow_group_add', models.BooleanField(default=True, verbose_name='グループ追加許可')),
+ ('history_id', models.AutoField(primary_key=True, serialize=False)),
+ ('history_date', models.DateTimeField(db_index=True)),
+ ('history_change_reason', models.CharField(max_length=100, null=True)),
+ ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
+ ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
+ ],
+ options={
+ 'verbose_name': 'historical ユーザ',
+ 'verbose_name_plural': 'historical ユーザ',
+ 'ordering': ('-history_date', '-history_id'),
+ 'get_latest_by': ('history_date', 'history_id'),
+ },
+ bases=(simple_history.models.HistoricalChanges, models.Model),
+ ),
+ migrations.CreateModel(
+ name='HistoricalUserGroup',
+ fields=[
+ ('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
+ ('created_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='作成日')),
+ ('updated_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='更新日')),
+ ('is_admin', models.BooleanField(default=False, verbose_name='管理者')),
+ ('history_id', models.AutoField(primary_key=True, serialize=False)),
+ ('history_date', models.DateTimeField(db_index=True)),
+ ('history_change_reason', models.CharField(max_length=100, null=True)),
+ ('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
+ ('group', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='custom_auth.group')),
+ ('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
+ ('user', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL)),
+ ],
+ options={
+ 'verbose_name': 'historical ユーザ・グループ',
+ 'verbose_name_plural': 'historical ユーザ・グループ',
+ 'ordering': ('-history_date', '-history_id'),
+ 'get_latest_by': ('history_date', 'history_id'),
+ },
+ bases=(simple_history.models.HistoricalChanges, models.Model),
+ ),
+ ]
diff --git a/custom_auth/migrations/0008_remove_group_stripe_subscription_id_and_more.py b/custom_auth/migrations/0008_remove_group_stripe_subscription_id_and_more.py
new file mode 100644
index 0000000..93b2fb0
--- /dev/null
+++ b/custom_auth/migrations/0008_remove_group_stripe_subscription_id_and_more.py
@@ -0,0 +1,31 @@
+# Generated by Django 5.1.1 on 2024-09-16 18:25
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('custom_auth', '0007_historicalgroup_historicaluser_historicalusergroup'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='group',
+ name='stripe_subscription_id',
+ ),
+ migrations.RemoveField(
+ model_name='historicalgroup',
+ name='stripe_subscription_id',
+ ),
+ migrations.AddField(
+ model_name='historicaluser',
+ name='stripe_donate_customer_id',
+ field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Stripe Donate(CusID)'),
+ ),
+ migrations.AddField(
+ model_name='user',
+ name='stripe_donate_customer_id',
+ field=models.CharField(blank=True, max_length=200, null=True, verbose_name='Stripe Donate(CusID)'),
+ ),
+ ]
diff --git a/custom_auth/migrations/0009_alter_group_users_alter_historicaluser_username_jp_and_more.py b/custom_auth/migrations/0009_alter_group_users_alter_historicaluser_username_jp_and_more.py
new file mode 100644
index 0000000..fcb28f8
--- /dev/null
+++ b/custom_auth/migrations/0009_alter_group_users_alter_historicaluser_username_jp_and_more.py
@@ -0,0 +1,34 @@
+# Generated by Django 5.2.3 on 2025-06-22 15:34
+
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('custom_auth', '0008_remove_group_stripe_subscription_id_and_more'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='group',
+ name='users',
+ field=models.ManyToManyField(blank=True, related_name='group_users_set', through='custom_auth.UserGroup', through_fields=('group', 'user'), to=settings.AUTH_USER_MODEL),
+ ),
+ migrations.AlterField(
+ model_name='historicaluser',
+ name='username_jp',
+ field=models.CharField(max_length=150, verbose_name='username(japanese)'),
+ ),
+ migrations.AlterField(
+ model_name='user',
+ name='groups',
+ field=models.ManyToManyField(blank=True, related_name='user_set', through='custom_auth.UserGroup', through_fields=('user', 'group'), to='custom_auth.group'),
+ ),
+ migrations.AlterField(
+ model_name='user',
+ name='username_jp',
+ field=models.CharField(max_length=150, verbose_name='username(japanese)'),
+ ),
+ ]
diff --git a/custom_auth/migrations/0010_alter_group_membership_type_and_more.py b/custom_auth/migrations/0010_alter_group_membership_type_and_more.py
new file mode 100644
index 0000000..76898b2
--- /dev/null
+++ b/custom_auth/migrations/0010_alter_group_membership_type_and_more.py
@@ -0,0 +1,23 @@
+# Generated by Django 5.2.3 on 2025-06-22 15:49
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('custom_auth', '0009_alter_group_users_alter_historicaluser_username_jp_and_more'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='group',
+ name='membership_type',
+ field=models.IntegerField(choices=[(1, '一般会員'), (40, '運営委員(有償)'), (70, '学生委員'), (90, '運営委員(無償)'), (99, 'その他')], default=1, verbose_name='会員種別'),
+ ),
+ migrations.AlterField(
+ model_name='historicalgroup',
+ name='membership_type',
+ field=models.IntegerField(choices=[(1, '一般会員'), (40, '運営委員(有償)'), (70, '学生委員'), (90, '運営委員(無償)'), (99, 'その他')], default=1, verbose_name='会員種別'),
+ ),
+ ]
diff --git a/custom_auth/models.py b/custom_auth/models.py
index d94f94a..a055914 100644
--- a/custom_auth/models.py
+++ b/custom_auth/models.py
@@ -1,19 +1,19 @@
import uuid
import pyotp
+from django.apps import apps
from django.conf import settings
from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
from django.contrib.auth.hashers import make_password
+from django.contrib.auth.models import Group
from django.contrib.auth.validators import UnicodeUsernameValidator
from django.core.mail import send_mail
from django.db import models
from django.template.loader import render_to_string
from django.utils import timezone
-from django.contrib.auth.models import Group, GroupManager
-
-from custom_auth.tool import random_string
-from django.apps import apps
+from simple_history.models import HistoricalRecords
+from custom_auth.utils import random_string
from dsbd.models import MediumTextField
@@ -21,8 +21,9 @@ class GroupManager(models.Manager):
def active_filter(self):
return self.filter(is_active=True)
- def create_group(self, question, name, name_jp, postcode, address, address_jp, phone, country, contract_type,
- **extra_fields):
+ def create_group(
+ self, question, name, name_jp, postcode, address, address_jp, phone, country, contract_type, **extra_fields
+ ):
extra_fields.setdefault("is_pass", False)
return self.create(
agree=True,
@@ -35,15 +36,14 @@ def create_group(self, question, name, name_jp, postcode, address, address_jp, p
phone=phone,
country=country,
contract_type=contract_type,
- **extra_fields
+ **extra_fields,
)
- def update_group(self, group_id, postcode, address, address_en, email, phone, country):
+ def update_group(self, group_id, postcode, address, address_en, phone, country):
group = Group.objects.get(id=group_id)
group.postcode = postcode
group.address = address
group.address_en = address_en
- group.email = email
group.phone = phone
group.country = country
group.save()
@@ -62,7 +62,7 @@ def update_group(self, group_id, postcode, address, address_en, email, phone, co
# 90-99 特別枠(運営)
MEMBERSHIP_TYPE_CHOICES = (
(1, "一般会員"),
- (40, "運営委員(優勝)"),
+ (40, "運営委員(有償)"),
(70, "学生委員"),
(90, "運営委員(無償)"),
(99, "その他"),
@@ -74,14 +74,15 @@ def update_group(self, group_id, postcode, address, address_en, email, phone, co
)
-class Group(models.Model):
+class Group(models.Model): # noqa: F811
created_at = models.DateTimeField("作成日", default=timezone.now)
updated_at = models.DateTimeField("更新日", default=timezone.now)
name = models.CharField("name", max_length=150, unique=True)
name_jp = models.CharField("name(japanese)", max_length=150, unique=True)
comment = models.CharField("comment", max_length=250, default="", blank=True)
- status = models.IntegerField("ステータス", default=0, choices=GROUP_STATUS_CHOICES)
+ status = models.IntegerField("ステータス", default=1, choices=GROUP_STATUS_CHOICES)
allow_service_add = models.BooleanField("サービス追加許可", default=False)
+ allow_jpnic_add = models.BooleanField("JPNIC情報追加許可", default=False)
membership_type = models.IntegerField("会員種別", default=1, choices=MEMBERSHIP_TYPE_CHOICES)
membership_expired_at = models.DateTimeField("有効期限", blank=True, null=True)
@@ -92,7 +93,6 @@ class Group(models.Model):
# stripe
stripe_customer_id = models.CharField("Stripe(CusID)", max_length=200, blank=True, null=True)
- stripe_subscription_id = models.CharField("Stripe(SubID)", max_length=200, blank=True, null=True)
# group personal info
postcode = models.CharField("郵便番号", max_length=20, default="")
@@ -104,10 +104,11 @@ class Group(models.Model):
users = models.ManyToManyField(
"User",
blank=True,
- through='UserGroup',
- through_fields=('group', 'user'),
+ through="UserGroup",
+ through_fields=("group", "user"),
related_name="group_users_set",
)
+ history = HistoricalRecords()
objects = GroupManager()
@@ -124,9 +125,7 @@ def _create_user(self, username, username_jp, email, password, **extra_fields):
if not username:
raise ValueError("The given username must be set")
email = self.normalize_email(email)
- GlobalUserModel = apps.get_model(
- self.model._meta.app_label, self.model._meta.object_name
- )
+ GlobalUserModel = apps.get_model(self.model._meta.app_label, self.model._meta.object_name)
username = GlobalUserModel.normalize_username(username)
user = self.model(username=username, username_jp=username_jp, email=email, **extra_fields)
user.password = make_password(password)
@@ -138,13 +137,16 @@ def create_user(self, username, username_jp, email, password, **extra_fields):
extra_fields.setdefault("is_staff", False)
extra_fields.setdefault("is_active", False)
user = self._create_user(username, username_jp, email, password, **extra_fields)
- user_activate_token = UserActivateToken.objects.create(
- user=user
+ user_activate_token = UserActivateToken.objects.create(user=user)
+ subject = "Please Activate Your Account"
+ message = (
+ f"URLにアクセスしてアカウントを有効化してください。\n "
+ f"{settings.DOMAIN_URL}/activate/{user_activate_token.token}/"
)
- subject = 'Please Activate Your Account'
- message = f'URLにアクセスしてアカウントを有効化してください。\n {settings.DOMAIN_URL}/activate/{user_activate_token.token}/'
from_email = settings.DEFAULT_FROM_EMAIL
- recipient_list = [user.email, ]
+ recipient_list = [
+ user.email,
+ ]
send_mail(subject, message, from_email, recipient_list)
return user
@@ -158,25 +160,28 @@ def create_superuser(self, username, email=None, password=None, **extra_fields):
return self._create_user(username, email, password, **extra_fields)
- def change_email(self, user=None, email=''):
+ def change_email(self, user=None, email=""):
if not user:
raise ValueError("user_id is not found......")
if not email:
raise ValueError("email is not found......")
user.email = email
user.is_active = False
- user_activate_token = UserActivateToken.objects.create(
- user=user
+ user_activate_token = UserActivateToken.objects.create(user=user)
+ subject = "Please Activate Your Changed Email"
+ message = (
+ f"メールアドレスが変更されたので、URLにアクセスしてアカウントを有効化してください。\n"
+ f" {settings.DOMAIN_URL}/activate/{user_activate_token.token}/"
)
- subject = 'Please Activate Your Changed Email'
- message = f'メールアドレスが変更されたので、URLにアクセスしてアカウントを有効化してください。\n {settings.DOMAIN_URL}/activate/{user_activate_token.token}/'
from_email = settings.DEFAULT_FROM_EMAIL
- recipient_list = [email, ]
+ recipient_list = [
+ email,
+ ]
send_mail(subject, message, from_email, recipient_list)
user.save()
- def update_user(self, user=None, name='', name_jp='', display_name=''):
+ def update_user(self, user=None, name="", name_jp="", display_name=""):
if not user:
raise ValueError("user_id is not found......")
if not name:
@@ -198,24 +203,29 @@ class User(AbstractBaseUser):
updated_at = models.DateTimeField("更新日", default=timezone.now)
display_name = models.CharField("display_name", max_length=150, default="", blank=True)
username = models.CharField("username", max_length=150, validators=[username_validator], unique=True)
- username_jp = models.CharField("username(japanese)", max_length=150, validators=[username_validator], unique=True)
+ username_jp = models.CharField("username(japanese)", max_length=150)
email = models.EmailField("email", unique=True)
is_staff = models.BooleanField("管理者ステータス", default=False)
is_active = models.BooleanField("有効", default=False)
allow_group_add = models.BooleanField("グループ追加許可", default=True)
+
+ # stripe
+ stripe_donate_customer_id = models.CharField("Stripe Donate(CusID)", max_length=200, blank=True, null=True)
+
groups = models.ManyToManyField(
"Group",
blank=True,
- through='UserGroup',
- through_fields=('user', 'group'),
+ through="UserGroup",
+ through_fields=("user", "group"),
related_name="user_set",
)
+ history = HistoricalRecords()
objects = UserManager()
- EMAIL_FIELD = 'email'
- USERNAME_FIELD = 'username'
- REQUIRED_FIELDS = ['email']
+ EMAIL_FIELD = "email"
+ USERNAME_FIELD = "username"
+ REQUIRED_FIELDS = ["email"]
class Meta:
verbose_name = "ユーザ"
@@ -272,8 +282,8 @@ def activate_user_by_token(self, activate_token):
if user_activate_token.is_used:
raise ValueError("this token was used...")
if user_activate_token.user.is_active:
- raise ValueError('アカウントはすでに有効済みです')
- if hasattr(user_activate_token, 'user'):
+ raise ValueError("アカウントはすでに有効済みです")
+ if hasattr(user_activate_token, "user"):
user = user_activate_token.user
user.is_active = True
user.save()
@@ -291,7 +301,7 @@ class UserActivateToken(models.Model):
objects = UserActivateTokensManager()
class Meta:
- verbose_name = 'Activate用のToken'
+ verbose_name = "Activate用のToken"
verbose_name_plural = "Activate用のToken"
def __str__(self):
@@ -308,11 +318,12 @@ class UserGroup(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
is_admin = models.BooleanField("管理者", default=False)
+ history = HistoricalRecords()
class Meta:
- verbose_name = 'ユーザ・グループ'
+ verbose_name = "ユーザ・グループ"
verbose_name_plural = "ユーザ・グループ"
- unique_together = ('user', 'group')
+ unique_together = ("user", "group")
def __str__(self):
return "%s-%s" % (self.user.username, self.group.name)
@@ -325,18 +336,18 @@ def create_token(self, user=None):
code = random_string(10)
self.create(user_id=user.id, token=code)
subject = "認証コード"
- message = render_to_string("mail/account/two_auth.txt", {
- "code": code,
- "expired_time": settings.USER_LOGIN_VERIFY_EMAIL_EXPIRED_MINUTES
- })
+ message = render_to_string(
+ "mail/account/two_auth.txt",
+ {"code": code, "expired_time": settings.USER_LOGIN_VERIFY_EMAIL_EXPIRED_MINUTES},
+ )
send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [user.email], fail_silently=False)
- def check_token(self, user_id=None, token=''):
+ def check_token(self, user_id=None, token=""):
if not user_id:
raise ValueError("user_id is not found......")
try:
sign_up_key = self.filter(token=token, expired_at__gt=timezone.now(), is_used=False).first()
- except:
+ except Exception:
return False
# keyがない時
if not sign_up_key:
@@ -355,11 +366,14 @@ class UserEmailVerify(models.Model):
objects = UserEmailVerifyManager()
class Meta:
- verbose_name = 'E-Mail用のVerify'
+ verbose_name = "E-Mail用のVerify"
verbose_name_plural = "E-Mail用のVerify"
def __str__(self):
- return "%d[%s]" % (self.id, self.user.username,)
+ return "%d[%s]" % (
+ self.id,
+ self.user.username,
+ )
class TOTPDeviceManager(models.Manager):
@@ -372,15 +386,15 @@ def generate_secret(self, email=""):
otp_secret = pyotp.random_base32()
return {
"secret": otp_secret,
- "url": pyotp.totp.TOTP(otp_secret).provisioning_uri(name=email, issuer_name=settings.APP_NAME)
+ "url": pyotp.totp.TOTP(otp_secret).provisioning_uri(name=email, issuer_name=settings.APP_NAME),
}
- def create_secret(self, user=None, title='', otp_secret=''):
+ def create_secret(self, user=None, title="", otp_secret=""):
if not user:
raise ValueError("user_id is not found......")
self.create(title=title, user=user, secret=otp_secret, is_active=True)
- def check_totp(self, user=None, code=''):
+ def check_totp(self, user=None, code=""):
if not user:
raise ValueError("user_id is not found......")
totp_array = self.filter(user=user)
@@ -413,7 +427,7 @@ class TOTPDevice(models.Model):
objects = TOTPDeviceManager()
class Meta:
- verbose_name = 'TOTP Device'
+ verbose_name = "TOTP Device"
verbose_name_plural = "TOTP Device"
def __str__(self):
diff --git a/custom_auth/signals.py b/custom_auth/signals.py
index c9de311..a0d13d4 100644
--- a/custom_auth/signals.py
+++ b/custom_auth/signals.py
@@ -1,8 +1,8 @@
from django.db.models.signals import post_save, pre_delete, pre_save
from django.dispatch import receiver
-from custom_auth.models import User
-from dsbd.notify import notify_db_save
+from custom_auth.models import Group, User, UserGroup
+from dsbd.notify import notify_delete_db, notify_insert_db, notify_update_db
@receiver(pre_save, sender=User)
@@ -14,55 +14,57 @@ def user_model_pre_save(sender, instance, **kwargs):
@receiver(post_save, sender=User)
-def post_user(sender, instance, created, **kwargs):
+def user_model_post_save(sender, instance, created, **kwargs):
if created:
- text = get_create_user(True, instance)
- notify_db_save(table_name="User", type=0, data=text)
- else:
- text = get_update_user(instance._pre_save_instance, instance)
- notify_db_save(table_name="User", type=1, data=text)
+ notify_insert_db(model_name=sender.__name__, instance=instance)
+ return
+ notify_insert_db(model_name=sender.__name__, instance=instance)
@receiver(pre_delete, sender=User)
-def delete_user(sender, instance, **kwargs):
- text = get_create_user(False, instance)
- notify_db_save(table_name="User", type=2, data=text)
-
-
-def get_create_user(short, instance):
- text = '--%d[%s]--\n' % (instance.id, instance.username,)
- return text if not short else text + '有効: %r\nE-Mail: %s\n' % (
- instance.is_active, instance.email)
-
-
-def get_update_user(before, after):
- text = '%s----更新状況----\n' % (get_create_user(True, before),)
- if before.username != after.username:
- text += 'username: %s => %s\n' % (before.username, after.username)
- if before.username_jp != after.username_jp:
- text += 'username(jp): %s => %s\n' % (before.username, after.username)
- if before.email != after.email:
- text += 'E-Mail: %s => %s\n' % (before.email, after.email)
- if before.is_active != after.is_active:
- text += '有効: %r => %r\n' % (before.is_active, after.is_active)
- text += '------------\n'
- return text
-
-
-def get_create_signup_key(short, instance):
- text = '--%d[%s]--\n' % (instance.id, instance.key,)
- return text if not short else text + '利用済み: %r\n有効期限: %s\n' % (instance.is_used, instance.expired_at)
-
-
-def get_update_signup_key(before, after):
- text = '%s----更新状況----\n' % (get_create_signup_key(True, before),)
- if before.key != after.key:
- text += 'key: %s => %s\n' % (before.key, after.key)
- if before.is_used != after.is_used:
- text += '使用済み: %r => %r\n' % (before.is_used, after.is_used)
- if before.expired_at != after.expired_at:
- text += '有効期限: %s => %s\n' % (before.expired_at, after.expired_at)
- if before.comment != after.comment:
- text += 'comment: %r => %r\n' % (before.comment, after.comment)
- text += '------------\n'
- return text
+def user_model_pre_delete(sender, instance, **kwargs):
+ notify_delete_db(model_name=sender.__name__, instance=instance)
+
+
+@receiver(pre_save, sender=Group)
+def group_model_pre_save(sender, instance, **kwargs):
+ try:
+ instance._pre_save_instance = Group.objects.get(pk=instance.pk)
+ except Group.DoesNotExist:
+ instance._pre_save_instance = instance
+ # 審査NG => 審査OKの場合にサービス追加とJPNIC追加を出来るようにする
+ if not instance._pre_save_instance.is_pass and instance.is_pass:
+ instance.allow_service_add = True
+ instance.allow_jpnic_add = True
+
+ # Statusが1以外の場合はサービス追加とJPNIC追加を禁止する
+ if instance.status != 1:
+ instance.allow_service_add = False
+ instance.allow_jpnic_add = False
+
+
+@receiver(post_save, sender=Group)
+def group_model_post_save(sender, instance, created, **kwargs):
+ if created:
+ notify_insert_db(model_name=sender.__name__, instance=instance)
+ return
+
+ notify_update_db(model_name=sender.__name__, instance=instance)
+
+
+@receiver(pre_delete, sender=Group)
+def group_model_pre_delete(sender, instance, **kwargs):
+ notify_delete_db(model_name=sender.__name__, instance=instance)
+
+
+@receiver(post_save, sender=UserGroup)
+def user_group_model_post_save(sender, instance, created, **kwargs):
+ if created:
+ notify_insert_db(model_name=sender.__name__, instance=instance)
+ return
+ notify_insert_db(model_name=sender.__name__, instance=instance)
+
+
+@receiver(pre_delete, sender=UserGroup)
+def user_group_model_pre_delete(sender, instance, **kwargs):
+ notify_delete_db(model_name=sender.__name__, instance=instance)
diff --git a/custom_auth/urls.py b/custom_auth/urls.py
index ae0b80b..cff65cf 100644
--- a/custom_auth/urls.py
+++ b/custom_auth/urls.py
@@ -1,11 +1,11 @@
-from django.urls import path, include
+from django.urls import path
from . import views
app_name = "custom_auth"
urlpatterns = [
path("", views.index, name="index"),
- path("passwor", views.password_change, name="password_change"),
+ path("password", views.password_change, name="password_change"),
path("email", views.change_email, name="email_change"),
path("edit", views.edit_profile, name="edit_profile"),
path("two_auth", views.list_two_auth, name="list_two_auth"),
diff --git a/custom_auth/tool.py b/custom_auth/utils.py
similarity index 82%
rename from custom_auth/tool.py
rename to custom_auth/utils.py
index ebd5d94..0b85aeb 100644
--- a/custom_auth/tool.py
+++ b/custom_auth/utils.py
@@ -4,4 +4,4 @@
def random_string(num):
random_list = [random.choice(string.ascii_letters + string.digits) for i in range(num)]
- return ''.join(random_list)
\ No newline at end of file
+ return "".join(random_list)
diff --git a/custom_auth/views.py b/custom_auth/views.py
index 8cf8e6b..7b02403 100644
--- a/custom_auth/views.py
+++ b/custom_auth/views.py
@@ -1,17 +1,22 @@
import base64
-import time
from io import BytesIO
import pyotp
import qrcode
-import stripe
from django.contrib.auth.decorators import login_required
-from django.shortcuts import render, redirect
-
-from custom_auth.form import TwoAuthForm, GroupForm, MyPasswordChangeForm, EmailChangeForm, ProfileEditForm, \
- GroupAddForm
+from django.shortcuts import redirect, render
+from django.urls import reverse
+
+from custom_auth.form import (
+ EmailChangeForm,
+ GroupAddForm,
+ GroupForm,
+ MyPasswordChangeForm,
+ ProfileEditForm,
+ TwoAuthForm,
+)
from custom_auth.models import TOTPDevice, UserGroup
-from django.conf import settings
+from dsbd.payment import MEMBERSHIP_TAG_TYPE, Payment, check_expired
@login_required
@@ -23,22 +28,22 @@ def index(request):
@login_required
def password_change(request):
form = MyPasswordChangeForm(user=request.user, data=request.POST or None)
- if request.method == 'POST':
+ if request.method == "POST":
if form.is_valid():
form.save()
- return render(request, "done.html", {'text': "パスワードの変更を行いました"})
- context = {'form': form}
+ return render(request, "done.html", {"text": "パスワードの変更を行いました"})
+ context = {"form": form}
return render(request, "user/change_password.html", context)
@login_required
def change_email(request):
form = EmailChangeForm(data=request.POST or None)
- if request.method == 'POST':
+ if request.method == "POST":
if form.is_valid():
form.save(user=request.user)
- return render(request, "done.html", {'text': "メールアドレスの変更を行いました"})
- return render(request, "user/change_email.html", {'form': form})
+ return render(request, "done.html", {"text": "メールアドレスの変更を行いました"})
+ return render(request, "user/change_email.html", {"form": form})
@login_required
@@ -49,14 +54,14 @@ def edit_profile(request):
"username_jp": request.user.username_jp,
"display_name": request.user.display_name,
}
- if request.method == 'POST':
+ if request.method == "POST":
if form.is_valid():
form.save(user=request.user)
- return render(request, "done.html", {'text': "プロフィールの変更を行いました"})
+ return render(request, "done.html", {"text": "プロフィールの変更を行いました"})
else:
form = ProfileEditForm(initial=userdata)
- return render(request, "user/edit_profile.html", {'form': form})
+ return render(request, "user/edit_profile.html", {"form": form})
@login_required
@@ -66,21 +71,19 @@ def add_two_auth(request):
secret = TOTPDevice.objects.generate_secret()
form = TwoAuthForm()
buffer = BytesIO()
- qrcode.make(secret.get('url')).save(buffer)
+ qrcode.make(secret.get("url")).save(buffer)
qr = base64.b64encode(buffer.getvalue()).decode().replace("'", "")
- if request.method == 'POST':
- id = request.POST.get('id', 0)
- if id == 'submit' and initial_check:
+ if request.method == "POST":
+ id = request.POST.get("id", 0)
+ if id == "submit" and initial_check:
form = TwoAuthForm(request.POST)
otp_secret = request.POST.get("secret")
if form.is_valid():
- code = form.cleaned_data['code']
+ code = form.cleaned_data["code"]
verify_code = pyotp.TOTP(otp_secret).verify(code)
if verify_code:
TOTPDevice.objects.create_secret(
- user=request.user,
- title=form.cleaned_data['title'],
- otp_secret=otp_secret
+ user=request.user, title=form.cleaned_data["title"], otp_secret=otp_secret
)
return redirect("custom_auth:list_two_auth")
else:
@@ -91,12 +94,12 @@ def add_two_auth(request):
error = "request error"
context = {
- 'initial_check': initial_check,
- 'secret': secret.get('secret'),
- 'url': secret.get('url'),
- 'qr': qr,
- 'form': form,
- 'error': error
+ "initial_check": initial_check,
+ "secret": secret.get("secret"),
+ "url": secret.get("url"),
+ "qr": qr,
+ "form": form,
+ "error": error,
}
return render(request, "user/two_auth/add.html", context)
@@ -104,57 +107,41 @@ def add_two_auth(request):
@login_required
def list_two_auth(request):
- if request.method == 'POST':
- id = request.POST.get('id', 0)
- device_id = int(request.POST.get('device_id', 0))
- if id == 'delete':
+ if request.method == "POST":
+ id = request.POST.get("id", 0)
+ device_id = int(request.POST.get("device_id", 0))
+ if id == "delete":
TOTPDevice.objects.remove(id=device_id, user=request.user)
- context = {'devices': TOTPDevice.objects.list(user=request.user)}
+ context = {"devices": TOTPDevice.objects.list(user=request.user)}
return render(request, "user/two_auth/list.html", context)
@login_required
def list_groups(request):
- data = []
+ groups = []
for group in request.user.groups.all():
- data.append({
- "group": group,
- "administrator": group.usergroup_set.filter(user=request.user, is_admin=True).exists()
- })
+ groups.append(
+ {
+ "data": group,
+ "administrator": group.usergroup_set.filter(user=request.user, is_admin=True).exists(),
+ }
+ )
- if request.method == "POST":
- stripe.api_key = settings.STRIPE_SECRET_KEY
- id = request.POST.get("id", "")
- group_id = request.POST.get("group_id", 0)
- group = request.user.groups.get(id=group_id)
- administrator = group.usergroup_set.filter(user=request.user, is_admin=True).exists()
- if administrator and id == "create_stripe_customer":
- name = "[GROUP] %d: %s" % (int(group_id), group.name,)
- if not group.stripe_customer_id:
- cus = stripe.Customer.create(
- name=name,
- description="doornoc_service", # TODO: change description
- metadata={
- 'id': "doornoc_service", # TODO: change description
- 'user_id': request.user.id,
- 'group_id': group_id
- }
- )
- group.stripe_customer_id = cus.id
- group.save()
- redirect_url = "/group/%d/payment" % (int(group_id),)
- return redirect(redirect_url)
- elif administrator and id == "getting_portal":
- if group.stripe_customer_id:
- session = stripe.billing_portal.Session.create(
- customer=group.stripe_customer_id,
- return_url=settings.DOMAIN_URL + "/group"
- )
- return redirect(session.url, code=303)
+ context = {"groups": groups}
+ return render(request, "group/index.html", context)
- context = {
- "data": data
- }
+
+@login_required
+def list_group(request, group_id: int):
+ group = request.user.groups.get(id=group_id)
+ groups = [
+ {
+ "data": group,
+ "administrator": group.usergroup_set.filter(user=request.user, is_admin=True).exists(),
+ }
+ ]
+
+ context = {"groups": groups}
return render(request, "group/index.html", context)
@@ -164,182 +151,101 @@ def add_group(request):
form = GroupAddForm(data=request.POST or None)
if not request.user.allow_group_add:
error = "グループの新規登録が申請不可能です"
- elif request.method == 'POST':
+ elif request.method == "POST":
if form.is_valid():
try:
form.create_group(user_id=request.user.id)
- return render(request, "done.html", {'text': "登録・変更が完了しました"})
+ return render(request, "done.html", {"text": "登録・変更が完了しました"})
except ValueError as err:
error = err
- context = {
- "form": form,
- "error": error
- }
+ context = {"form": form, "error": error}
return render(request, "group/add.html", context)
@login_required
-def edit_group(request, group_id):
- error = None
- administrator = False
- try:
- group = request.user.groups.get(id=group_id)
- group_data = {
- "name": group.name,
- "postcode": group.postcode,
- "address": group.address_jp,
- "address_en": group.address,
- "phone": group.phone,
- "country": group.country,
- }
- administrator = group.usergroup_set.filter(user=request.user, is_admin=True).exists()
- if request.method == 'POST' and administrator and group.is_pass:
- form = GroupForm(data=request.POST)
- if form.is_valid():
- try:
- form.update_group(group_id=group.id)
- return render(request, "done.html", {'text': "登録・変更が完了しました"})
- except ValueError as err:
- error = err
- else:
- form = GroupForm(initial=group_data, edit=True, disable=not group.is_pass)
- except:
- group = None
- form = None
+def edit_group(request, group_id: int):
+ user_group = request.user.usergroup_set.filter(group_id=group_id, user=request.user).first()
+ if not user_group:
+ return render(request, "error.html", {"text": "このグループにアクセスする権限がありません"})
+ form = GroupForm(request.POST or None, instance=user_group.group, editable=not user_group.is_admin)
+ if request.method == "POST" and user_group.is_admin and user_group.group.is_pass:
+ if form.is_valid():
+ form.save()
+ return render(request, "done.html", {"text": "登録・変更が完了しました"})
- context = {
- "form": form,
- "group": group,
- "administrator": administrator,
- "error": error
- }
+ context = {"form": form, "group": user_group.group, "is_administrator": user_group.is_admin}
return render(request, "group/edit.html", context)
@login_required
-def group_permission(request, group_id):
+def group_permission(request, group_id: int):
error = None
- administrator = False
- permission_all = False
- try:
- group = request.user.groups.get(id=group_id)
- permission_all = group.usergroup_set.all()
- administrator = group.usergroup_set.filter(user=request.user, is_admin=True).exists()
- if request.method == 'POST' and administrator and group.is_active:
- id = request.POST.get('id', 0)
- is_exists = False
- for permission_user in permission_all:
- if permission_user.id == int(id):
- is_exists = True
- break
- if not is_exists:
- error = "変更権限がありません"
- else:
- try:
- user_group = UserGroup.objects.get(id=int(id))
- if "no_admin" in request.POST:
- user_group.is_admin = False
- user_group.save()
- elif "admin" in request.POST:
- user_group.is_admin = True
- user_group.save()
- return redirect('/group/permission/%d' % group_id)
- except:
- error = "アップデート処理でエラーが発生しました"
- except:
- group = None
+ user_group = request.user.usergroup_set.filter(group_id=group_id, user=request.user).first()
+ if not user_group:
+ return render(request, "error.html", {"text": "このグループにアクセスする権限がありません"})
+ permissions = user_group.group.usergroup_set.all()
+ if request.method == "POST" and user_group.is_admin and user_group.group.is_pass:
+ permission_id = int(request.POST.get("id", 0))
+ is_exists = user_group.group.usergroup_set.filter(id=permission_id).exists()
+ if not is_exists:
+ error = "変更権限がありません"
+ else:
+ try:
+ user_group = UserGroup.objects.get(id=permission_id)
+ if "no_admin" in request.POST:
+ user_group.is_admin = False
+ user_group.save()
+ elif "admin" in request.POST:
+ user_group.is_admin = True
+ user_group.save()
+ return redirect(reverse("custom_auth_group:permission", args=[group_id]))
+ except Exception:
+ error = "アップデート処理でエラーが発生しました"
context = {
- "group": group,
- "permission": permission_all,
- "administrator": administrator,
- "error": error
+ "group": user_group.group,
+ "permissions": permissions,
+ "is_administrator": user_group.is_admin,
+ "error": error,
}
return render(request, "group/edit_permission.html", context)
@login_required
-def group_payment(request, group_id):
- error = None
- administrator = False
- permission_all = False
- data = []
- try:
- group = request.user.groups.get(id=group_id)
- permission_all = group.usergroup_set.all()
- administrator = group.usergroup_set.filter(user=request.user, is_admin=True).exists()
-
- stripe.api_key = settings.STRIPE_SECRET_KEY
- products = stripe.Product.search(
- query="active:'true' AND metadata['id']:'doornoc_service'",
- )
- if administrator:
- for product in products:
- prices = stripe.Price.search(
- query="active:'true' AND product:'%s'" % (product.id,),
+def group_payment(request, group_id: int):
+ user_group = request.user.usergroup_set.filter(group_id=group_id, user=request.user, is_admin=True).first()
+ if not user_group:
+ return render(request, "error.html", {"text": "このグループにアクセスする権限がありません"})
+
+ payment = Payment()
+ product_id = payment.get_product(stripe_type=MEMBERSHIP_TAG_TYPE).id
+ prices = payment.get_prices(product_id=product_id)
+ is_expired = check_expired(expired_at=user_group.group.membership_expired_at)
+ if request.method == "POST":
+ match request.POST:
+ case {"billing_portal": _}:
+ session_url = payment.get_billing_portal(customer_id=user_group.group.stripe_customer_id)
+ return redirect(session_url, code=303)
+ case {"create_customer": _}:
+ if payment.create_customer(group_id=group_id, user_id=request.user.id):
+ return redirect(reverse("custom_auth_group:payment", args=[group_id]))
+ case {"checkout": _}:
+ price_id = request.POST.get("checkout", None)
+ if not price_id:
+ return render(request, "error.html", {"text": "price_idが不正です"})
+ session_url = payment.checkout(
+ price_id=price_id,
+ group=user_group.group,
+ user=request.user,
)
- tmp_prices = []
- idx_prices = 0
- for price in prices:
- tmp_price = [{
- "id": price.id,
- "interval": price.recurring.interval,
- "amount": price.unit_amount,
- "description": price.nickname
- }]
- if idx_prices == 0:
- tmp_prices = tmp_price
- else:
- if price.recurring.interval == "year":
- tmp_prices += tmp_price
- elif price.recurring.interval == "month":
- tmp_prices = tmp_price + tmp_prices
- idx_prices += 1
- data.append({
- "name": product.name,
- "prices": tmp_prices,
- "number": int(product.metadata.tag)
- })
- data.sort(key=lambda x: x['number'])
- except:
- group = None
- if administrator and request.method == "POST":
- id = request.POST.get("price_id", "")
- is_exists = False
- for one_data in data:
- for price in one_data["prices"]:
- if price["id"] == id:
- is_exists = True
- break
- url = settings.DOMAIN_URL + "/group"
- if is_exists and group.stripe_customer_id and not group.stripe_subscription_id:
- session = stripe.checkout.Session.create(
- mode="subscription",
- line_items=[
- {
- "price": id,
- "quantity": 1,
- },
- ],
- customer=group.stripe_customer_id,
- success_url=url,
- cancel_url=url,
- expires_at=int(time.time() + (60 * 30)),
- subscription_data={
- "metadata": {
- "type": "doornoc_membership",
- "group_id": group_id,
- "log": "[" + str(group.id) + "] " + group.name,
- }
- }
- )
- return redirect(session.url, code=303)
+ return redirect(session_url, code=303)
+ case {"update_membership": _}:
+ payment.update_membership_expired(group=user_group.group)
+ return redirect(reverse("custom_auth_group:payment", args=[group_id]))
context = {
- "data": data,
- "group": group,
- "permission": permission_all,
- "administrator": administrator,
- "error": error
+ "prices": prices,
+ "group": user_group.group,
+ "is_expired": is_expired,
}
- return render(request, "group/payment.html", context)
+ return render(request, "payment/payment.html", context)
diff --git a/dsbd/asgi.py b/dsbd/asgi.py
index f1564dc..321b9d1 100644
--- a/dsbd/asgi.py
+++ b/dsbd/asgi.py
@@ -1,5 +1,4 @@
-"""
-ASGI config for dsbd project.
+"""ASGI config for dsbd project.
It exposes the ASGI callable as a module-level variable named ``application``.
@@ -9,6 +8,7 @@
import os
+import django
from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import AllowedHostsOriginValidator
@@ -21,23 +21,18 @@ def __init__(self):
self.load_middleware(is_async=False)
super().__init__()
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dsbd.settings")
+django.setup()
django_asgi_app = get_asgi_application()
-os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'dsbd.settings')
-
-from ticket import routing
from custom_admin import routing as custom_admin_routing
+from ticket import routing
url = []
url += routing.urlpatterns
url += custom_admin_routing.urlpatterns
-application = ProtocolTypeRouter({
- 'http': django_asgi_app,
- 'websocket': AllowedHostsOriginValidator(
- AuthMiddlewareStack(
- URLRouter(url)
- )
- )
-})
+application = ProtocolTypeRouter(
+ {"http": django_asgi_app, "websocket": AllowedHostsOriginValidator(AuthMiddlewareStack(URLRouter(url)))}
+)
diff --git a/dsbd/form.py b/dsbd/form.py
index 77bb67c..7de83ae 100644
--- a/dsbd/form.py
+++ b/dsbd/form.py
@@ -1,5 +1,5 @@
-from django.contrib.auth.forms import AuthenticationForm, PasswordResetForm, SetPasswordForm
from django import forms
+from django.contrib.auth.forms import AuthenticationForm, PasswordResetForm, SetPasswordForm
from custom_auth.models import User
@@ -11,8 +11,8 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.fields.values():
- field.widget.attrs['class'] = 'form-control'
- field.widget.attrs['placeholder'] = field.label
+ field.widget.attrs["class"] = "form-control"
+ field.widget.attrs["placeholder"] = field.label
class OTPForm(forms.Form):
@@ -22,8 +22,8 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.fields.values():
- field.widget.attrs['class'] = 'form-control'
- field.widget.attrs['placeholder'] = field.label
+ field.widget.attrs["class"] = "form-control"
+ field.widget.attrs["placeholder"] = field.label
class AuthTOTP(forms.Form):
@@ -33,8 +33,8 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.fields.values():
- field.widget.attrs['class'] = 'form-control'
- field.widget.attrs['placeholder'] = field.label
+ field.widget.attrs["class"] = "form-control"
+ field.widget.attrs["placeholder"] = field.label
class ForgetForm(PasswordResetForm):
@@ -42,8 +42,8 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.fields.values():
- field.widget.attrs['class'] = 'form-control'
- field.widget.attrs['placeholder'] = field.label
+ field.widget.attrs["class"] = "form-control"
+ field.widget.attrs["placeholder"] = field.label
class NewSetPasswordForm(SetPasswordForm):
@@ -51,8 +51,8 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.fields.values():
- field.widget.attrs['class'] = 'form-control'
- field.widget.attrs['placeholder'] = field.label
+ field.widget.attrs["class"] = "form-control"
+ field.widget.attrs["placeholder"] = field.label
class SignUpForm(forms.Form):
@@ -67,12 +67,12 @@ def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for field in self.fields.values():
- field.widget.attrs['class'] = 'form-control'
- field.widget.attrs['placeholder'] = field.label
+ field.widget.attrs["class"] = "form-control"
+ field.widget.attrs["placeholder"] = field.label
def clean(self):
- password1 = self.cleaned_data.get('password1')
- password2 = self.cleaned_data.get('password2')
+ password1 = self.cleaned_data.get("password1")
+ password2 = self.cleaned_data.get("password2")
if password1 != password2:
raise forms.ValidationError("パスワードが一致しません")
diff --git a/dsbd/management/commands/db_auto_remove.py b/dsbd/management/commands/db_auto_remove.py
new file mode 100644
index 0000000..5e942a9
--- /dev/null
+++ b/dsbd/management/commands/db_auto_remove.py
@@ -0,0 +1,12 @@
+from django.core.management.base import BaseCommand
+from django.utils import timezone
+
+from notice.models import Notice
+
+
+class Command(BaseCommand):
+ def handle(self, *args, **options):
+ print("batch process for db_auto_remove")
+ now = timezone.now()
+ # 掲載終了の通知情報を削除
+ Notice.objects.filter(end_at__lt=now).delete()
diff --git a/dsbd/models.py b/dsbd/models.py
index 441d8fd..935fffe 100644
--- a/dsbd/models.py
+++ b/dsbd/models.py
@@ -7,4 +7,3 @@ def db_type(self, connection):
return "mediumtext"
else:
return super(MediumTextField, self).db_type(connection=connection)
-
diff --git a/dsbd/notify.py b/dsbd/notify.py
index 3be9d6d..eb9652f 100644
--- a/dsbd/notify.py
+++ b/dsbd/notify.py
@@ -1,57 +1,106 @@
from django.conf import settings
from slack_sdk import WebhookClient
+from dsbd.utils import get_admin_history_url, get_admin_url
-def notify_db_save(table_name="", type=0, data=""):
- event_name = "create"
- color = "good"
- if type == 1:
- event_name = "update"
- if type == 2:
- event_name = "delete"
- color = "danger"
+
+def notify_delete_db(model_name: str, instance):
+ admin_history_url = get_admin_history_url(instance)
+
+ message = f"レコードが削除されました:\n{instance}"
client = WebhookClient(settings.SLACK_WEBHOOK_LOG)
client.send(
- text="[%s(%s)]" % (table_name, event_name),
attachments=[
{
- "color": color,
- "title": "[%s(%s)]" % (table_name, event_name),
- "text": "%s" % (data,)
+ "color": "danger",
+ "title": "[%s]" % f"{model_name}",
+ "text": f"{message}\n管理画面(履歴): {admin_history_url}",
}
- ])
+ ],
+ )
+
+
+def notify_insert_db(model_name: str, instance):
+ field_details = []
+ for field in instance._meta.fields:
+ field_name = field.verbose_name
+ field_value = getattr(instance, field.name)
+ field_details.append(f"{field_name}: {field_value}")
+
+ admin_url = get_admin_url(instance)
+ admin_history_url = get_admin_history_url(instance)
+
+ detailed_info = "\n".join(field_details)
+ message = f"新しいレコードが登録されました:\n{detailed_info}"
+ client = WebhookClient(settings.SLACK_WEBHOOK_LOG)
+ client.send(
+ attachments=[
+ {
+ "color": "good",
+ "title": "[%s]" % f"{model_name}",
+ "text": f"{message}\n管理画面: {admin_url}\n管理画面(履歴): {admin_history_url}",
+ }
+ ],
+ )
+
+
+def notify_update_db(model_name: str, instance):
+ admin_url = get_admin_url(instance)
+ admin_history_url = get_admin_history_url(instance)
+
+ history = instance.history.all()
+ if history.count() < 2:
+ return # 履歴が2つ未満の場合は差分がないため、何もしない
+
+ latest = history.first() # 最新の履歴
+ previous = history[1] # 直前の履歴
+
+ changes = []
+
+ # フィールドごとの差分を確認
+ for field in instance._meta.fields:
+ field_name = field.name
+ old_value = getattr(previous, field_name, None)
+ new_value = getattr(latest, field_name, None)
+
+ if old_value != new_value:
+ changes.append(f"{field_name}: {old_value} -> {new_value}")
+
+ if changes:
+ message = f"モデル '{instance}' の以下のフィールドが変更されました:\n" + "\n".join(changes)
+ client = WebhookClient(settings.SLACK_WEBHOOK_LOG)
+ client.send(
+ attachments=[
+ {
+ "color": "good",
+ "title": "[%s]" % f"{model_name}",
+ "text": f"{message}\n管理画面: {admin_url}\n管理画面(履歴): {admin_history_url}",
+ }
+ ],
+ )
def notice_payment(metadata_type="", event_type="", data=None):
client = WebhookClient(settings.SLACK_WEBHOOK_LOG)
client.send(
- text="[%s(%s)] %s-%s [%d円(/%s)]" % (
- metadata_type,
- event_type,
- data['id'],
- data['name'],
- data['plan_amount'],
- data['plan_interval']
- ),
+ text="[%s(%s)] %s-%s [%d円(/%s)]"
+ % (metadata_type, event_type, data["id"], data["name"], data["plan_amount"], data["plan_interval"]),
attachments=[
{
"color": get_color(event_type),
- "title": "[%s(%s)] %s-%s" % (metadata_type, event_type, data['id'], data['name']),
- "text": "%s-%s\namount: %d(%s)\nstatus: %s" % (
- data['start'],
- data['end'],
- data['plan_amount'],
- data['plan_interval'],
- data['status']
- )
+ "title": "[%s(%s)] %s-%s" % (metadata_type, event_type, data["id"], data["name"]),
+ "text": "%s-%s\namount: %d(%s)\nstatus: %s"
+ % (data["start"], data["end"], data["plan_amount"], data["plan_interval"], data["status"]),
}
- ])
+ ],
+ )
def get_color(status):
- if status == "customer.subscription.created":
- return "warning"
- elif status == "customer.subscription.updated":
- return "good"
- elif status == "customer.subscription.deleted":
- return "danger"
+ match status:
+ case "customer.subscription.created":
+ return "warning"
+ case "customer.subscription.updated":
+ return "good"
+ case "customer.subscription.deleted":
+ return "danger"
diff --git a/dsbd/payment.py b/dsbd/payment.py
new file mode 100644
index 0000000..de6e48f
--- /dev/null
+++ b/dsbd/payment.py
@@ -0,0 +1,127 @@
+import time as default_time
+from datetime import datetime, time
+
+import stripe
+from django.conf import settings
+from django.utils import timezone
+
+from custom_auth.models import Group, User
+
+MEMBERSHIP_TAG_TYPE = f"{settings.STRIPE_MEMBERSHIP_TAG_NAME}"
+DONATE_TAG_TYPE = f"{settings.STRIPE_DONATE_TAG_NAME}"
+
+
+def check_expired(expired_at):
+ if not expired_at:
+ return True
+ return expired_at < timezone.now()
+
+
+class Payment:
+ def __init__(self, is_membership: bool = True):
+ self.stripe = stripe
+ self.stripe.api_key = settings.STRIPE_SECRET_KEY
+ self.is_membership = is_membership
+
+ def create_donate_customer(self, user: User):
+ name = f"[USER(DONATE)] {user.id}: {user.username}"
+ if user.stripe_donate_customer_id:
+ return None
+ customer = self.stripe.Customer.create(
+ name=name,
+ description="dsbd-system",
+ metadata={
+ "user_id": user.id,
+ "is_donate": True,
+ "dsbd_payment_system": "v2",
+ },
+ )
+ user.stripe_donate_customer_id = customer.id
+ user.save()
+ return customer.id
+
+ def create_customer(self, group_id: int, user_id: int):
+ group = Group.objects.get(id=group_id)
+ name = f"[GROUP] {group.id}: {group.name}"
+ if group.stripe_customer_id:
+ return None
+ customer = self.stripe.Customer.create(
+ name=name,
+ description="dsbd-system", # TODO: change description
+ metadata={
+ "user_id": user_id,
+ "group_id": group.id,
+ "dsbd_payment_system": "v2",
+ },
+ )
+ group.stripe_customer_id = customer.id
+ group.save()
+ return customer.id
+
+ def get_billing_portal(self, customer_id: str, group_id: int = 0):
+ return_url = f"{settings.DOMAIN_URL}/group"
+ if group_id != 0:
+ return_url += f"/{group_id}/payment"
+ if not self.is_membership:
+ return_url = f"{settings.DOMAIN_URL}/donate"
+ session = self.stripe.billing_portal.Session.create(customer=customer_id, return_url=return_url)
+ return session.url
+
+ def checkout(self, price_id: str, group: Group or None, user: User, is_subscription: bool = True):
+ return_url = f"{settings.DOMAIN_URL}/donate"
+ if self.is_membership:
+ return_url = f"{settings.DOMAIN_URL}/group/{group.id}/payment"
+ customer_id = user.stripe_donate_customer_id if not group else group.stripe_customer_id
+ log = f"[USER] {user.id}: {user.username}" if not group else f"[{group.id}] {group.name}"
+ session_data = {
+ "mode": "subscription" if is_subscription else "payment",
+ "line_items": [
+ {
+ "price": price_id,
+ "quantity": 1,
+ },
+ ],
+ "customer": customer_id,
+ "success_url": return_url,
+ "cancel_url": return_url,
+ "expires_at": int(default_time.time() + (60 * 30)),
+ }
+ metadata = {
+ "service_type": "donate" if not group else "membership",
+ "user_id": user.id,
+ "group_id": None if not group else group.id,
+ "log": log,
+ "dsbd_payment_system": "v2",
+ }
+ if is_subscription:
+ session_data["subscription_data"] = {"metadata": metadata}
+ else:
+ session_data["payment_intent_data"] = {"metadata": metadata}
+ session = self.stripe.checkout.Session.create(**session_data)
+ return session.url
+
+ def get_product(self, stripe_type: str):
+ products = self.stripe.Product.search(query=f"metadata['service_type']:'{stripe_type}'")
+ if not products["data"]:
+ raise ValueError("Products not found")
+ return products.data[0]
+
+ def get_prices(self, product_id: str):
+ prices = self.stripe.Price.list(product=product_id, limit=10)
+ return prices.data
+
+ def update_membership_expired(self, group: Group):
+ product_id = self.get_product(stripe_type=MEMBERSHIP_TAG_TYPE).id
+ subscriptions = self.stripe.Subscription.list(customer=group.stripe_customer_id)
+ period_end = None
+ for subscription in subscriptions.data:
+ if subscription.plan.product != product_id:
+ continue
+ period_start_date = datetime.fromtimestamp(subscription.current_period_start).date()
+ period_end_date = datetime.fromtimestamp(subscription.current_period_end).date()
+ period_start = datetime.combine(period_start_date, time(0, 0, 0))
+ tmp_period_end = datetime.combine(period_end_date, time(23, 59, 59))
+ if period_end is None or period_end < tmp_period_end:
+ period_end = tmp_period_end
+ group.membership_expired_at = period_end
+ group.save()
diff --git a/dsbd/settings.py b/dsbd/settings.py
index 3086283..4b18406 100644
--- a/dsbd/settings.py
+++ b/dsbd/settings.py
@@ -1,5 +1,4 @@
-"""
-Django settings for dsbd project.
+"""Django settings for dsbd project.
Generated by 'django-admin startproject' using Django 4.2.2.
@@ -9,19 +8,21 @@
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.2/ref/settings/
"""
+
import os
from importlib import import_module
from pathlib import Path
+
import ldap
-from django_auth_ldap.config import LDAPSearch, GroupOfNamesType
from django.contrib.messages import constants as messages
+from django_auth_ldap.config import LDAPSearch
def _import_ldap_group_type(group_type_name):
- mod = import_module('django_auth_ldap.config')
+ mod = import_module("django_auth_ldap.config")
try:
return getattr(mod, group_type_name)()
- except:
+ except Exception:
return None
@@ -32,82 +33,85 @@ def _import_ldap_group_type(group_type_name):
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
-SECRET_KEY = os.environ.get('SECRET_KEY', 'django-insecure-b5+e4te%74(m1y#kijgd4406u6)5_&=x^p85+0pwp^rs^8^s7x')
+SECRET_KEY = os.environ.get("SECRET_KEY", "django-insecure-b5+e4te%74(m1y#kijgd4406u6)5_&=x^p85+0pwp^rs^8^s7x")
# SECURITY WARNING: don't run with debug turned on in production!
-DEBUG = os.environ.get('DEBUG', 'false').lower() == 'true'
+DEBUG = os.environ.get("DEBUG", "false").lower() == "true"
-ADMIN_MODE = os.environ.get('ADMIN_MODE', 'false').lower() == 'true'
+ADMIN_MODE = os.environ.get("ADMIN_MODE", "false").lower() == "true"
-ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '*').split(' ')
-CSRF_TRUSTED_ORIGINS = os.environ.get('CSRF_TRUSTED_ORIGINS', 'http://localhost:8000').split(' ')
+ALLOWED_HOSTS = os.environ.get("ALLOWED_HOSTS", "*").split(" ")
+CSRF_TRUSTED_ORIGINS = os.environ.get("CSRF_TRUSTED_ORIGINS", "http://localhost:8000").split(" ")
-APP_NAME = 'HomeNOC User Dashboard'
-SITE_TITLE = os.environ.get('SITE_TITLE', 'HomeNOC User Dashboard')
-SITE_HEADER = os.environ.get('SITE_HEADER', 'HomeNOC User Dashboard')
+APP_NAME = "HomeNOC User Dashboard"
+SITE_TITLE = os.environ.get("SITE_TITLE", "HomeNOC User Dashboard")
+SITE_HEADER = os.environ.get("SITE_HEADER", "HomeNOC User Dashboard")
# Application definition
INSTALLED_APPS = [
- 'django.contrib.admin',
- 'django.contrib.auth',
- 'django.contrib.contenttypes',
- 'django.contrib.sessions',
- 'django.contrib.messages',
- 'dsbd',
- 'daphne',
- 'django.contrib.staticfiles',
- 'widget_tweaks',
- 'django_countries',
- 'debug_toolbar',
- 'custom_auth',
- 'custom_admin',
- 'notice',
- 'ticket',
- 'noc',
- 'router',
- 'ip',
- 'service',
+ "django.contrib.admin",
+ "django.contrib.auth",
+ "django.contrib.contenttypes",
+ "django.contrib.sessions",
+ "django.contrib.messages",
+ "dsbd",
+ 'channels',
+ "daphne",
+ "django.contrib.staticfiles",
+ "widget_tweaks",
+ "django_countries",
+ "debug_toolbar",
+ "custom_auth",
+ "custom_admin",
+ "notice",
+ "ticket",
+ "noc",
+ "router",
+ "ip",
+ "service",
+ "simple_history",
]
MIDDLEWARE = [
- 'django.middleware.security.SecurityMiddleware',
- 'django.contrib.sessions.middleware.SessionMiddleware',
- 'django.middleware.common.CommonMiddleware',
- 'django.middleware.csrf.CsrfViewMiddleware',
- 'django.contrib.auth.middleware.AuthenticationMiddleware',
- 'django.contrib.messages.middleware.MessageMiddleware',
- 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+ "django.middleware.security.SecurityMiddleware",
+ "django.contrib.sessions.middleware.SessionMiddleware",
+ "django.middleware.common.CommonMiddleware",
+ "django.middleware.csrf.CsrfViewMiddleware",
+ "django.contrib.auth.middleware.AuthenticationMiddleware",
+ "django.contrib.messages.middleware.MessageMiddleware",
+ "django.middleware.clickjacking.XFrameOptionsMiddleware",
+ "simple_history.middleware.HistoryRequestMiddleware",
]
AUTHENTICATION_BACKENDS = [
- 'django.contrib.auth.backends.ModelBackend',
- 'django_auth_ldap.backend.LDAPBackend',
+ "django.contrib.auth.backends.ModelBackend",
+ "django_auth_ldap.backend.LDAPBackend",
]
-ROOT_URLCONF = 'dsbd.urls'
+ROOT_URLCONF = "dsbd.urls"
TEMPLATES = [
{
- 'BACKEND': 'django.template.backends.django.DjangoTemplates',
- 'DIRS': [BASE_DIR / 'dsbd/templates'],
- 'APP_DIRS': True,
- 'OPTIONS': {
- 'context_processors': [
- 'django.template.context_processors.debug',
- 'django.template.context_processors.request',
- 'django.contrib.auth.context_processors.auth',
- 'django.contrib.messages.context_processors.messages',
+ "BACKEND": "django.template.backends.django.DjangoTemplates",
+ "DIRS": [BASE_DIR / "dsbd/templates"],
+ "APP_DIRS": True,
+ "OPTIONS": {
+ "context_processors": [
+ "django.template.context_processors.debug",
+ "django.template.context_processors.request",
+ "django.contrib.auth.context_processors.auth",
+ "django.contrib.messages.context_processors.messages",
],
- 'builtins': ['dsbd.templatetags.extra'],
+ "builtins": ["dsbd.templatetags.extra"],
},
},
]
-WSGI_APPLICATION = 'dsbd.wsgi.application'
+WSGI_APPLICATION = "dsbd.wsgi.application"
# Channels
-ASGI_APPLICATION = 'dsbd.asgi.application'
+ASGI_APPLICATION = "dsbd.asgi.application"
# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
@@ -115,11 +119,11 @@ def _import_ldap_group_type(group_type_name):
DATABASES = {
"default": {
"ENGINE": "django.db.backends.mysql",
- "NAME": os.environ.get('DATABASE_NAME', 'dsbd'),
- "USER": os.environ.get('DATABASE_USER', 'dsbd'),
- "PASSWORD": os.environ.get('DATABASE_PASSWORD', ''),
- "HOST": os.environ.get('DATABASE_HOST', 'localhost'),
- "PORT": os.environ.get('DATABASE_PORT', 3306),
+ "NAME": os.environ.get("DATABASE_NAME", "dsbd"),
+ "USER": os.environ.get("DATABASE_USER", "dsbd"),
+ "PASSWORD": os.environ.get("DATABASE_PASSWORD", ""),
+ "HOST": os.environ.get("DATABASE_HOST", "localhost"),
+ "PORT": os.environ.get("DATABASE_PORT", 3306),
"OPTIONS": {
"charset": "utf8mb4",
},
@@ -135,32 +139,28 @@ def _import_ldap_group_type(group_type_name):
AUTH_PASSWORD_VALIDATORS = [
{
- 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+ "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
},
{
- 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+ "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
},
{
- 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+ "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
},
{
- 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+ "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
},
]
# Channel
-CHANNEL_LAYERS = {
- "default": {
- "BACKEND": "channels.layers.InMemoryChannelLayer"
- }
-}
+CHANNEL_LAYERS = {"default": {"BACKEND": "channels.layers.InMemoryChannelLayer"}}
# Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/
-LANGUAGE_CODE = 'ja'
+LANGUAGE_CODE = "ja"
-TIME_ZONE = 'Asia/Tokyo'
+TIME_ZONE = "Asia/Tokyo"
USE_I18N = True
@@ -168,34 +168,38 @@ def _import_ldap_group_type(group_type_name):
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/
-STATICFILES_DIRS = [os.path.join(BASE_DIR, 'dsbd/static')]
-STATIC_ROOT = os.path.join(BASE_DIR, 'static')
-STATIC_URL = 'static/'
+STATICFILES_DIRS = [os.path.join(BASE_DIR, "dsbd/static")]
+STATIC_ROOT = os.path.join(BASE_DIR, "static/")
+STATIC_URL = "static/"
# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
-DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
+DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
# AUTH_USER_MODEL
-AUTH_USER_MODEL = 'custom_auth.User'
+AUTH_USER_MODEL = "custom_auth.User"
# Debug
if DEBUG:
import socket
- hostname, _, ips = socket.gethostbyname_ex(socket.gethostname())
- INTERNAL_IPS = [ip[: ip.rfind(".")] + ".1" for ip in ips] + ["127.0.0.1", ]
+ try:
+ hostname, _, ips = socket.gethostbyname_ex(socket.gethostname())
+ INTERNAL_IPS = [ip[:-1] + "1" for ip in ips] + ["127.0.0.1"]
+ except:
+ INTERNAL_IPS = ["127.0.0.1"]
MIDDLEWARE.append('debug_toolbar.middleware.DebugToolbarMiddleware')
LOGIN_URL = "sign_in"
LOGIN_REDIRECT_URL = "/"
-DOMAIN_URL = os.environ.get('DOMAIN_URL', 'http://localhost:8000')
+DOMAIN_URL = os.environ.get("DOMAIN_URL", "http://localhost:8000")
+ADMIN_DOMAIN_URL = os.environ.get("ADMIN_DOMAIN_URL", "http://localhost:8001")
-USER_LOGIN_VERIFY_EMAIL_EXPIRED_HOURS = os.environ.get('USER_LOGIN_VERIFY_EMAIL_EXPIRED_HOURS', 1)
-USER_LOGIN_VERIFY_EMAIL_EXPIRED_MINUTES = os.environ.get('USER_LOGIN_VERIFY_EMAIL_EXPIRED_MINUTES', 10)
-USER_ACTIVATE_EXPIRED_DAYS = os.environ.get('USER_ACTIVATE_EXPIRED_DAYS', 7)
+USER_LOGIN_VERIFY_EMAIL_EXPIRED_HOURS = os.environ.get("USER_LOGIN_VERIFY_EMAIL_EXPIRED_HOURS", 1)
+USER_LOGIN_VERIFY_EMAIL_EXPIRED_MINUTES = os.environ.get("USER_LOGIN_VERIFY_EMAIL_EXPIRED_MINUTES", 10)
+USER_ACTIVATE_EXPIRED_DAYS = os.environ.get("USER_ACTIVATE_EXPIRED_DAYS", 7)
# Custom URL
USAGE_URL = "https://www.homenoc.ad.jp/usage/"
@@ -203,41 +207,60 @@ def _import_ldap_group_type(group_type_name):
FEE_URL = "https://www.homenoc.ad.jp/about/membership/"
# Stripe
-STRIPE_PRIVATE_KEY = os.environ.get('STRIPE_PRIVATE_KEY', '')
-STRIPE_SECRET_KEY = os.environ.get('STRIPE_SECRET_KEY', '')
-STRIPE_WEBHOOK_SECRET_KEY = os.environ.get('STRIPE_WEBHOOK_SECRET_KEY', '')
+STRIPE_PRIVATE_KEY = os.environ.get("STRIPE_PRIVATE_KEY", "")
+STRIPE_SECRET_KEY = os.environ.get("STRIPE_SECRET_KEY", "")
+STRIPE_WEBHOOK_SECRET_KEY = os.environ.get("STRIPE_WEBHOOK_SECRET_KEY", "")
+STRIPE_MEMBERSHIP_TAG_NAME = os.environ.get("STRIPE_MEMBERSHIP_TAG_NAME", "membership")
+STRIPE_DONATE_TAG_NAME = os.environ.get("STRIPE_DONATE_TAG_NAME", "donate")
# Slack
-SLACK_WEBHOOK_LOG = os.environ.get('SLACK_WEBHOOK_LOG', '')
+SLACK_WEBHOOK_LOG = os.environ.get("SLACK_WEBHOOK_LOG", "")
# LDAP
-AUTH_LDAP_SERVER_URI = os.environ.get('AUTH_LDAP_SERVER_URI', '')
-AUTH_LDAP_BIND_DN = os.environ.get('AUTH_LDAP_BIND_DN', '')
-AUTH_LDAP_BIND_PASSWORD = os.environ.get('AUTH_LDAP_BIND_PASSWORD', '')
-AUTH_LDAP_USER_SEARCH_ATTR = os.environ.get('AUTH_LDAP_USER_SEARCH_ATTR', 'sAMAccountName')
+AUTH_LDAP_SERVER_URI = os.environ.get("AUTH_LDAP_SERVER_URI", "")
+AUTH_LDAP_BIND_DN = os.environ.get("AUTH_LDAP_BIND_DN", "")
+AUTH_LDAP_BIND_PASSWORD = os.environ.get("AUTH_LDAP_BIND_PASSWORD", "")
+AUTH_LDAP_USER_SEARCH_ATTR = os.environ.get("AUTH_LDAP_USER_SEARCH_ATTR", "sAMAccountName")
AUTH_LDAP_USER_SEARCH = LDAPSearch(
- os.environ.get('AUTH_LDAP_USER_SEARCH_BASE_DN', ''),
- ldap.SCOPE_SUBTREE,
- f'({AUTH_LDAP_USER_SEARCH_ATTR}=%(user)s)'
+ os.environ.get("AUTH_LDAP_USER_SEARCH_BASE_DN", ""), ldap.SCOPE_SUBTREE, f"({AUTH_LDAP_USER_SEARCH_ATTR}=%(user)s)"
)
AUTH_LDAP_USER_ATTR_MAP = {
- "first_name": os.environ.get('AUTH_LDAP_ATTR_FIRSTNAME', 'givenName'),
- "last_name": os.environ.get('AUTH_LDAP_ATTR_LASTNAME', 'sn'),
- "email": os.environ.get('AUTH_LDAP_ATTR_MAIL', 'mail'),
+ "first_name": os.environ.get("AUTH_LDAP_ATTR_FIRSTNAME", "givenName"),
+ "last_name": os.environ.get("AUTH_LDAP_ATTR_LASTNAME", "sn"),
+ "email": os.environ.get("AUTH_LDAP_ATTR_MAIL", "mail"),
}
-AUTH_LDAP_GROUP_TYPE = _import_ldap_group_type(os.environ.get('AUTH_LDAP_GROUP_TYPE', 'GroupOfNamesType'))
-AUTH_LDAP_REQUIRE_GROUP = os.environ.get('AUTH_LDAP_REQUIRE_GROUP_DN')
+AUTH_LDAP_GROUP_TYPE = _import_ldap_group_type(os.environ.get("AUTH_LDAP_GROUP_TYPE", "GroupOfNamesType"))
+AUTH_LDAP_REQUIRE_GROUP = os.environ.get("AUTH_LDAP_REQUIRE_GROUP_DN")
AUTH_LDAP_GROUP_SEARCH = LDAPSearch(
- os.environ.get('AUTH_LDAP_GROUP_SEARCH_BASE_DN', ''),
+ os.environ.get("AUTH_LDAP_GROUP_SEARCH_BASE_DN", ""),
ldap.SCOPE_SUBTREE,
"(objectClass=group)",
)
AUTH_LDAP_FIND_GROUP_PERMS = True
+AUTH_LDAP_ALWAYS_UPDATE_USER = True
AUTH_LDAP_USER_FLAGS_BY_GROUP = {
- "is_active": os.environ.get('AUTH_LDAP_REQUIRE_GROUP_DN', ''),
- "is_staff": os.environ.get('AUTH_LDAP_IS_ADMIN_DN', ''),
+ "is_active": os.environ.get("AUTH_LDAP_IS_ACTIVE_DN", ""),
+ "is_staff": os.environ.get("AUTH_LDAP_IS_ADMIN_DN", ""),
}
MESSAGE_TAGS = {
- messages.ERROR: 'danger',
+ messages.ERROR: "danger",
}
+
+# Email settings
+EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"
+EMAIL_HOST = os.environ.get("EMAIL_HOST", "")
+EMAIL_PORT = int(os.environ.get("EMAIL_PORT", 587))
+DEFAULT_FROM_EMAIL = os.environ.get("DEFAULT_FROM_EMAIL", "no-reply@doornoc.ad.jp")
+EMAIL_HOST_USER = os.environ.get("EMAIL_HOST_USER", "no-reply@doornoc.ad.jp")
+EMAIL_HOST_PASSWORD = os.environ.get("EMAIL_HOST_PASSWORD", "")
+EMAIL_USE_TLS = os.environ.get("EMAIL_USE_TLS", "false").lower() == "true"
+
+# Logging configuration
+if DEBUG:
+ LOGGING = {
+ "version": 1,
+ "disable_existing_loggers": False,
+ "handlers": {"console": {"class": "logging.StreamHandler"}},
+ "loggers": {"django_auth_ldap": {"level": "DEBUG", "handlers": ["console"]}},
+ }
\ No newline at end of file
diff --git a/dsbd/templates/activate.html b/dsbd/templates/activate.html
index 53c5961..9f79758 100644
--- a/dsbd/templates/activate.html
+++ b/dsbd/templates/activate.html
@@ -2,7 +2,7 @@
{% load widget_tweaks %}
{% load static %}
{% block content %}
- {{ message }}
+ {{ message }}
{% endblock content %}
{% block javascript %}
diff --git a/dsbd/templates/admin/base.html b/dsbd/templates/admin/base.html
index f408a10..d8ac8c0 100644
--- a/dsbd/templates/admin/base.html
+++ b/dsbd/templates/admin/base.html
@@ -1,145 +1,168 @@
{% load i18n static %}
{% get_current_language as LANGUAGE_CODE %}{% get_current_language_bidi as LANGUAGE_BIDI %}
-
+
- {% block title %}{% endblock %}
-
- {% block dark-mode-vars %}
-
-
- {% endblock %}
- {% if not is_popup and is_nav_sidebar_enabled %}
-
-
- {% endif %}
- {% block extrastyle %}{% endblock %}
+ {% block title %}{% endblock %}
+
+ {% block dark-mode-vars %}
+
+
+ {% endblock %}
+ {% if not is_popup and is_nav_sidebar_enabled %}
+
+
+ {% endif %}
+ {% block extrastyle %}{% endblock %}
+ {% if LANGUAGE_BIDI %}
+ {% endif %}
+ {% block extrahead %}{% endblock %}
+ {% block responsive %}
+
+
{% if LANGUAGE_BIDI %}
- {% endif %}
- {% block extrahead %}{% endblock %}
- {% block responsive %}
-
-
- {% if LANGUAGE_BIDI %}
- {% endif %}
- {% endblock %}
- {% block blockbots %}
- {% endblock %}
+
+ {% endif %}
+ {% endblock %}
+ {% block blockbots %}
+ {% endblock %}
-{% translate 'Skip to main content' %}
+{% translate 'Skip to main content' %}
- {% if not is_popup %}
-
- {% block header %}
-