diff --git a/rog/admin.py b/rog/admin.py index 4fcdc4b..4db2589 100644 --- a/rog/admin.py +++ b/rog/admin.py @@ -4,7 +4,7 @@ from django.shortcuts import render from leaflet.admin import LeafletGeoAdmin from leaflet.admin import LeafletGeoAdminMixin from leaflet_admin_list.admin import LeafletAdminListMixin -from .models import RogUser, Location, SystemSettings, JoinedEvent, Favorite, TravelList, TravelPoint, ShapeLayers, Event, Location_line, Location_polygon, JpnAdminMainPerf, Useractions, CustomUser, GifuAreas, UserTracks, templocation, UserUpload, EventUser, GoalImages, CheckinImages, NewEvent, Team, Category, Entry, Member, TempUser +from .models import RogUser, Location, SystemSettings, JoinedEvent, Favorite, TravelList, TravelPoint, ShapeLayers, Event, Location_line, Location_polygon, JpnAdminMainPerf, Useractions, CustomUser, GifuAreas, UserTracks, templocation, UserUpload, EventUser, GoalImages, CheckinImages, NewEvent, NewEvent2, Team, NewCategory, Category, Entry, Member, TempUser from django.contrib.auth.admin import UserAdmin from django.urls import path from django.shortcuts import render @@ -196,8 +196,8 @@ class TempLocationAdmin(LeafletGeoAdmin): list_display = ('location_id','cp', 'location_name', 'category', 'event_name', 'event_active', 'auto_checkin', 'checkin_radius', 'checkin_point', 'buy_point',) actions = [tranfer_to_location,] -@admin.register(NewEvent) -class NewEventAdmin(admin.ModelAdmin): +@admin.register(NewEvent2) +class NewEvent2Admin(admin.ModelAdmin): list_display = ['event_name', 'start_datetime', 'end_datetime'] search_fields = ['event_name'] @@ -206,8 +206,8 @@ class TeamAdmin(admin.ModelAdmin): list_display = ['zekken_number', 'team_name', 'owner'] search_fields = ['zekken_number', 'team_name', 'owner__email'] -@admin.register(Category) -class CategoryAdmin(admin.ModelAdmin): +@admin.register(NewCategory) +class NewCategoryAdmin(admin.ModelAdmin): list_display = ['category_name', 'category_number', 'duration', 'num_of_member', 'family', 'female'] list_filter = ['family', 'female'] search_fields = ['category_name'] diff --git a/rog/migrations/0033_alter_templocation_sub_loc_id.py b/rog/migrations/0033_alter_templocation_sub_loc_id.py deleted file mode 100644 index 00fadf7..0000000 --- a/rog/migrations/0033_alter_templocation_sub_loc_id.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.2.9 on 2022-09-09 13:18 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('rog', '0032_alter_location_sub_loc_id'), - ] - - operations = [ - migrations.AlterField( - model_name='templocation', - name='sub_loc_id', - field=models.CharField(blank=True, max_length=2048, null=True, verbose_name='Sub location id'), - ), - ] diff --git a/rog/migrations/0033_auto_20240724_1431.py b/rog/migrations/0033_auto_20240724_1431.py new file mode 100644 index 0000000..db652cd --- /dev/null +++ b/rog/migrations/0033_auto_20240724_1431.py @@ -0,0 +1,274 @@ +# Generated by Django 3.2.9 on 2024-07-24 05:31 + +import datetime +from django.conf import settings +import django.contrib.postgres.indexes +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('rog', '0032_alter_location_sub_loc_id'), + ] + + operations = [ + migrations.CreateModel( + name='Category', + fields=[ + ('category_name', models.CharField(max_length=255, primary_key=True, serialize=False)), + ('category_number', models.IntegerField(unique=True)), + ('duration', models.DurationField(default=datetime.timedelta(seconds=18000))), + ('num_of_member', models.IntegerField(default=1)), + ('family', models.BooleanField(default=False)), + ('female', models.BooleanField(default=False)), + ], + ), + migrations.CreateModel( + name='CheckinImages', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('checkinimage', models.FileField(blank=True, null=True, upload_to='checkin/%y%m%d')), + ('checkintime', models.DateTimeField(verbose_name='Goal time')), + ('team_name', models.CharField(max_length=255, verbose_name='Team name')), + ('event_code', models.CharField(max_length=255, verbose_name='event code')), + ('cp_number', models.IntegerField(verbose_name='CP numner')), + ], + ), + migrations.CreateModel( + name='Entry', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('date', models.DateTimeField()), + ], + ), + migrations.CreateModel( + name='EntryMember', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('is_temporary', models.BooleanField(default=False)), + ], + ), + migrations.CreateModel( + name='GoalImages', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('goalimage', models.FileField(blank=True, null=True, upload_to='goals/%y%m%d')), + ('goaltime', models.DateTimeField(verbose_name='Goal time')), + ('team_name', models.CharField(max_length=255, verbose_name='Team name')), + ('event_code', models.CharField(max_length=255, verbose_name='event code')), + ('cp_number', models.IntegerField(verbose_name='CP numner')), + ], + ), + migrations.CreateModel( + name='Member', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('is_temporary', models.BooleanField(default=False)), + ], + ), + migrations.CreateModel( + name='NewEvent', + fields=[ + ('event_name', models.CharField(max_length=255, primary_key=True, serialize=False)), + ('start_datetime', models.DateTimeField(default=django.utils.timezone.now)), + ('end_datetime', models.DateTimeField()), + ], + ), + migrations.CreateModel( + name='Team', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('zekken_number', models.CharField(max_length=255, unique=True)), + ('team_name', models.CharField(max_length=255)), + ], + ), + migrations.CreateModel( + name='TempUser', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('email', models.EmailField(max_length=254, unique=True)), + ('password', models.CharField(max_length=128)), + ('is_rogaining', models.BooleanField(default=False)), + ('zekken_number', models.CharField(blank=True, max_length=255, null=True)), + ('event_code', models.CharField(blank=True, max_length=255, null=True)), + ('team_name', models.CharField(blank=True, max_length=255, null=True)), + ('group', models.CharField(max_length=255)), + ('firstname', models.CharField(blank=True, max_length=255, null=True)), + ('lastname', models.CharField(blank=True, max_length=255, null=True)), + ('date_of_birth', models.DateField(blank=True, null=True)), + ('female', models.BooleanField(default=False)), + ('verification_code', models.UUIDField(default=uuid.uuid4, editable=False)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('expires_at', models.DateTimeField()), + ], + ), + migrations.DeleteModel( + name='JpnAdminPerf', + ), + migrations.DeleteModel( + name='JpnSubPerf', + ), + migrations.AddField( + model_name='customuser', + name='date_of_birth', + field=models.DateField(blank=True, null=True), + ), + migrations.AddField( + model_name='customuser', + name='event_code', + field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Event Code'), + ), + migrations.AddField( + model_name='customuser', + name='female', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='customuser', + name='firstname', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='customuser', + name='is_rogaining', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='customuser', + name='lastname', + field=models.CharField(blank=True, max_length=255, null=True), + ), + migrations.AddField( + model_name='customuser', + name='team_name', + field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Team Name'), + ), + migrations.AddField( + model_name='customuser', + name='zekken_number', + field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Zekken Number'), + ), + migrations.AlterField( + model_name='customuser', + name='email', + field=models.CharField(max_length=255, unique=True, verbose_name='Email'), + ), + migrations.AlterField( + model_name='location', + name='category', + field=models.CharField(blank=True, db_index=True, max_length=2048, null=True, verbose_name='Category'), + ), + migrations.AlterField( + model_name='location', + name='event_active', + field=models.BooleanField(db_index=True, default=True, verbose_name='Is Event active'), + ), + migrations.AlterField( + model_name='location', + name='event_name', + field=models.CharField(blank=True, db_index=True, max_length=2048, null=True, verbose_name='Event name'), + ), + migrations.AlterField( + model_name='location', + name='group', + field=models.CharField(blank=True, db_index=True, max_length=2048, null=True, verbose_name='Group'), + ), + migrations.AlterField( + model_name='location', + name='location_id', + field=models.IntegerField(blank=True, db_index=True, null=True, verbose_name='Location id'), + ), + migrations.AlterField( + model_name='templocation', + name='sub_loc_id', + field=models.CharField(blank=True, max_length=2048, null=True, verbose_name='Sub location id'), + ), + migrations.AddIndex( + model_name='location', + index=django.contrib.postgres.indexes.GistIndex(fields=['geom'], name='rog_locatio_geom_4793cc_gist'), + ), + migrations.AddField( + model_name='team', + name='category', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='rog.category'), + ), + migrations.AddField( + model_name='team', + name='owner', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='owned_teams', to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='member', + name='team', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rog.team'), + ), + migrations.AddField( + model_name='member', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='goalimages', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='entrymember', + name='entry', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rog.entry'), + ), + migrations.AddField( + model_name='entrymember', + name='member', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rog.member'), + ), + migrations.AddField( + model_name='entry', + name='category', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rog.category'), + ), + migrations.AddField( + model_name='entry', + name='event', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rog.newevent'), + ), + migrations.AddField( + model_name='entry', + name='owner', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='entry', + name='team', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rog.team'), + ), + migrations.AddField( + model_name='checkinimages', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to=settings.AUTH_USER_MODEL), + ), + migrations.AlterUniqueTogether( + name='category', + unique_together={('category_name', 'category_number')}, + ), + migrations.AlterUniqueTogether( + name='team', + unique_together={('zekken_number', 'category')}, + ), + migrations.AlterUniqueTogether( + name='member', + unique_together={('team', 'user')}, + ), + migrations.AlterUniqueTogether( + name='entrymember', + unique_together={('entry', 'member')}, + ), + migrations.AlterUniqueTogether( + name='entry', + unique_together={('team', 'event', 'date', 'owner')}, + ), + ] diff --git a/rog/migrations/0034_alter_customuser_email.py b/rog/migrations/0034_alter_customuser_email.py deleted file mode 100644 index 0822fbb..0000000 --- a/rog/migrations/0034_alter_customuser_email.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.2.9 on 2022-10-06 10:51 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('rog', '0033_alter_templocation_sub_loc_id'), - ] - - operations = [ - migrations.AlterField( - model_name='customuser', - name='email', - field=models.CharField(max_length=255, verbose_name='user name'), - ), - ] diff --git a/rog/migrations/0034_auto_20240724_1522.py b/rog/migrations/0034_auto_20240724_1522.py new file mode 100644 index 0000000..e822147 --- /dev/null +++ b/rog/migrations/0034_auto_20240724_1522.py @@ -0,0 +1,34 @@ +# Generated by Django 3.2.9 on 2024-07-24 06:22 + +import datetime +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('rog', '0033_auto_20240724_1431'), + ] + + operations = [ + migrations.AlterField( + model_name='category', + name='category_number', + field=models.IntegerField(default=0), + ), + migrations.CreateModel( + name='NewCategory', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('category_name', models.CharField(max_length=255, unique=True)), + ('category_number', models.IntegerField(default=0)), + ('duration', models.DurationField(default=datetime.timedelta(seconds=18000))), + ('num_of_member', models.IntegerField(default=1)), + ('family', models.BooleanField(default=False)), + ('female', models.BooleanField(default=False)), + ], + options={ + 'unique_together': {('category_name', 'category_number')}, + }, + ), + ] diff --git a/rog/migrations/0035_alter_customuser_email.py b/rog/migrations/0035_alter_customuser_email.py deleted file mode 100644 index c5d37e6..0000000 --- a/rog/migrations/0035_alter_customuser_email.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.2.9 on 2022-10-06 10:52 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('rog', '0034_alter_customuser_email'), - ] - - operations = [ - migrations.AlterField( - model_name='customuser', - name='email', - field=models.EmailField(max_length=254, unique=True, verbose_name='user name'), - ), - ] diff --git a/rog/migrations/0035_auto_20240724_1529.py b/rog/migrations/0035_auto_20240724_1529.py new file mode 100644 index 0000000..b975643 --- /dev/null +++ b/rog/migrations/0035_auto_20240724_1529.py @@ -0,0 +1,29 @@ +# Generated by Django 3.2.9 on 2024-07-24 06:29 + +from django.db import migrations, models +import django.db.models.deletion +import django.utils.timezone + + +class Migration(migrations.Migration): + + dependencies = [ + ('rog', '0034_auto_20240724_1522'), + ] + + operations = [ + migrations.CreateModel( + name='NewEvent2', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('event_name', models.CharField(max_length=255, unique=True)), + ('start_datetime', models.DateTimeField(default=django.utils.timezone.now)), + ('end_datetime', models.DateTimeField()), + ], + ), + migrations.AlterField( + model_name='entry', + name='event', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rog.newevent2'), + ), + ] diff --git a/rog/migrations/0036_alter_customuser_email.py b/rog/migrations/0036_alter_customuser_email.py deleted file mode 100644 index 2cab59c..0000000 --- a/rog/migrations/0036_alter_customuser_email.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.2.9 on 2022-10-06 11:01 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('rog', '0035_alter_customuser_email'), - ] - - operations = [ - migrations.AlterField( - model_name='customuser', - name='email', - field=models.CharField(max_length=255, unique=True, verbose_name='Email'), - ), - ] diff --git a/rog/migrations/0036_alter_team_category.py b/rog/migrations/0036_alter_team_category.py new file mode 100644 index 0000000..1aa7076 --- /dev/null +++ b/rog/migrations/0036_alter_team_category.py @@ -0,0 +1,20 @@ +# Generated by Django 3.2.9 on 2024-07-24 06:58 + +from django.db import migrations, models +import django.db.models.deletion +import rog.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('rog', '0035_auto_20240724_1529'), + ] + + operations = [ + migrations.AlterField( + model_name='team', + name='category', + field=models.ForeignKey(default=rog.models.get_default_category, on_delete=django.db.models.deletion.SET_DEFAULT, to='rog.newcategory'), + ), + ] diff --git a/rog/migrations/0037_alter_member_team.py b/rog/migrations/0037_alter_member_team.py new file mode 100644 index 0000000..92f8797 --- /dev/null +++ b/rog/migrations/0037_alter_member_team.py @@ -0,0 +1,19 @@ +# Generated by Django 3.2.9 on 2024-07-24 15:20 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('rog', '0036_alter_team_category'), + ] + + operations = [ + migrations.AlterField( + model_name='member', + name='team', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='members', to='rog.team'), + ), + ] diff --git a/rog/migrations/0037_customuser_is_rogaining.py b/rog/migrations/0037_customuser_is_rogaining.py deleted file mode 100644 index d02707f..0000000 --- a/rog/migrations/0037_customuser_is_rogaining.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 3.2.9 on 2022-10-06 13:42 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('rog', '0036_alter_customuser_email'), - ] - - operations = [ - migrations.AddField( - model_name='customuser', - name='is_rogaining', - field=models.BooleanField(default=False), - ), - ] diff --git a/rog/migrations/0038_auto_20221016_1950.py b/rog/migrations/0038_auto_20221016_1950.py deleted file mode 100644 index 036e160..0000000 --- a/rog/migrations/0038_auto_20221016_1950.py +++ /dev/null @@ -1,28 +0,0 @@ -# Generated by Django 3.2.9 on 2022-10-16 10:50 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('rog', '0037_customuser_is_rogaining'), - ] - - operations = [ - migrations.AddField( - model_name='customuser', - name='event_code', - field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Event Code'), - ), - migrations.AddField( - model_name='customuser', - name='team_name', - field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Team Name'), - ), - migrations.AddField( - model_name='customuser', - name='zekken_number', - field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Zekken Number'), - ), - ] diff --git a/rog/migrations/0039_goalimages.py b/rog/migrations/0039_goalimages.py deleted file mode 100644 index 2838b8b..0000000 --- a/rog/migrations/0039_goalimages.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 3.2.9 on 2022-10-17 11:39 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('rog', '0038_auto_20221016_1950'), - ] - - operations = [ - migrations.CreateModel( - name='GoalImages', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('goalimage', models.FileField(blank=True, null=True, upload_to='%y%m%d')), - ('goaltime', models.DateTimeField(verbose_name='Goal time')), - ('team_name', models.CharField(max_length=255, verbose_name='Team name')), - ('event_code', models.CharField(max_length=255, verbose_name='event code')), - ('cp_number', models.IntegerField(verbose_name='CP numner')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to=settings.AUTH_USER_MODEL)), - ], - ), - ] diff --git a/rog/migrations/0040_auto_20221105_1939.py b/rog/migrations/0040_auto_20221105_1939.py deleted file mode 100644 index 5b78fc0..0000000 --- a/rog/migrations/0040_auto_20221105_1939.py +++ /dev/null @@ -1,32 +0,0 @@ -# Generated by Django 3.2.9 on 2022-11-05 10:39 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('rog', '0039_goalimages'), - ] - - operations = [ - migrations.AlterField( - model_name='goalimages', - name='goalimage', - field=models.FileField(blank=True, null=True, upload_to='goals/%y%m%d'), - ), - migrations.CreateModel( - name='CheckinImages', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('checkinimage', models.FileField(blank=True, null=True, upload_to='checkin/%y%m%d')), - ('checkintime', models.DateTimeField(verbose_name='Goal time')), - ('team_name', models.CharField(max_length=255, verbose_name='Team name')), - ('event_code', models.CharField(max_length=255, verbose_name='event code')), - ('cp_number', models.IntegerField(verbose_name='CP numner')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to=settings.AUTH_USER_MODEL)), - ], - ), - ] diff --git a/rog/migrations/0041_auto_20230526_1724.py b/rog/migrations/0041_auto_20230526_1724.py deleted file mode 100644 index 95d1922..0000000 --- a/rog/migrations/0041_auto_20230526_1724.py +++ /dev/null @@ -1,49 +0,0 @@ -# Generated by Django 3.2.9 on 2023-05-26 08:24 - -import django.contrib.postgres.indexes -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('rog', '0040_auto_20221105_1939'), - ] - - operations = [ - migrations.DeleteModel( - name='JpnAdminPerf', - ), - migrations.DeleteModel( - name='JpnSubPerf', - ), - migrations.AlterField( - model_name='location', - name='category', - field=models.CharField(blank=True, db_index=True, max_length=2048, null=True, verbose_name='Category'), - ), - migrations.AlterField( - model_name='location', - name='event_active', - field=models.BooleanField(db_index=True, default=True, verbose_name='Is Event active'), - ), - migrations.AlterField( - model_name='location', - name='event_name', - field=models.CharField(blank=True, db_index=True, max_length=2048, null=True, verbose_name='Event name'), - ), - migrations.AlterField( - model_name='location', - name='group', - field=models.CharField(blank=True, db_index=True, max_length=2048, null=True, verbose_name='Group'), - ), - migrations.AlterField( - model_name='location', - name='location_id', - field=models.IntegerField(blank=True, db_index=True, null=True, verbose_name='Location id'), - ), - migrations.AddIndex( - model_name='location', - index=django.contrib.postgres.indexes.GistIndex(fields=['geom'], name='rog_locatio_geom_4793cc_gist'), - ), - ] diff --git a/rog/migrations/0042_auto_20240722_1603.py b/rog/migrations/0042_auto_20240722_1603.py deleted file mode 100644 index 2b96c22..0000000 --- a/rog/migrations/0042_auto_20240722_1603.py +++ /dev/null @@ -1,113 +0,0 @@ -# Generated by Django 3.2.9 on 2024-07-22 07:03 - -import datetime -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion -import django.utils.timezone -import uuid - - -class Migration(migrations.Migration): - - dependencies = [ - ('rog', '0041_auto_20230526_1724'), - ] - - operations = [ - migrations.CreateModel( - name='Category', - fields=[ - ('category_name', models.CharField(max_length=255, primary_key=True, serialize=False)), - ('category_number', models.IntegerField(unique=True)), - ('duration', models.DurationField(default=datetime.timedelta(seconds=18000))), - ('num_of_member', models.IntegerField(default=1)), - ('family', models.BooleanField(default=False)), - ('female', models.BooleanField(default=False)), - ], - options={ - 'unique_together': {('category_name', 'category_number')}, - }, - ), - migrations.CreateModel( - name='NewEvent', - fields=[ - ('event_name', models.CharField(max_length=255, primary_key=True, serialize=False)), - ('start_datetime', models.DateTimeField(default=django.utils.timezone.now)), - ('end_datetime', models.DateTimeField()), - ], - ), - migrations.CreateModel( - name='TempUser', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('email', models.EmailField(max_length=254, unique=True)), - ('password', models.CharField(max_length=128)), - ('is_rogaining', models.BooleanField(default=False)), - ('zekken_number', models.CharField(blank=True, max_length=255, null=True)), - ('event_code', models.CharField(blank=True, max_length=255, null=True)), - ('team_name', models.CharField(blank=True, max_length=255, null=True)), - ('group', models.CharField(max_length=255)), - ('firstname', models.CharField(blank=True, max_length=255, null=True)), - ('lastname', models.CharField(blank=True, max_length=255, null=True)), - ('date_of_birth', models.DateField(blank=True, null=True)), - ('female', models.BooleanField(default=False)), - ('verification_code', models.UUIDField(default=uuid.uuid4, editable=False)), - ('created_at', models.DateTimeField(auto_now_add=True)), - ('expires_at', models.DateTimeField()), - ], - ), - migrations.AddField( - model_name='customuser', - name='date_of_birth', - field=models.DateField(blank=True, null=True), - ), - migrations.AddField( - model_name='customuser', - name='female', - field=models.BooleanField(default=False), - ), - migrations.AddField( - model_name='customuser', - name='firstname', - field=models.CharField(blank=True, max_length=255, null=True), - ), - migrations.AddField( - model_name='customuser', - name='lastname', - field=models.CharField(blank=True, max_length=255, null=True), - ), - migrations.CreateModel( - name='Team', - fields=[ - ('zekken_number', models.CharField(max_length=255, primary_key=True, serialize=False)), - ('team_name', models.CharField(max_length=255)), - ('password', models.CharField(max_length=128)), - ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - ), - migrations.CreateModel( - name='Member', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('team', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rog.team')), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - options={ - 'unique_together': {('team', 'user')}, - }, - ), - migrations.CreateModel( - name='Entry', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('date', models.DateTimeField()), - ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rog.category')), - ('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rog.newevent')), - ('team', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rog.team')), - ], - options={ - 'unique_together': {('team', 'event', 'date')}, - }, - ), - ] diff --git a/rog/models.py b/rog/models.py index 2984e66..1bf3efb 100644 --- a/rog/models.py +++ b/rog/models.py @@ -261,6 +261,15 @@ class TempUser(models.Model): def is_valid(self): return timezone.now() <= self.expires_at +class NewEvent2(models.Model): + event_name = models.CharField(max_length=255, unique=True) + start_datetime = models.DateTimeField(default=timezone.now) + end_datetime = models.DateTimeField() + + def __str__(self): + return f"{self.event_name} - From:{self.start_datetime} To:{self.end_datetime}" + + class NewEvent(models.Model): event_name = models.CharField(max_length=255, primary_key=True) start_datetime = models.DateTimeField(default=timezone.now) @@ -269,19 +278,27 @@ class NewEvent(models.Model): def __str__(self): return f"{self.event_name} - From:{self.start_datetime} To:{self.end_datetime}" +def get_default_category(): + return NewCategory.objects.get_or_create(category_name="Default Category", category_number=1)[0].id + + class Team(models.Model): - zekken_number = models.CharField(max_length=255, primary_key=True) + zekken_number = models.CharField(max_length=255, unique=True) team_name = models.CharField(max_length=255) - password = models.CharField(max_length=128) - owner = models.ForeignKey(CustomUser, on_delete=models.CASCADE) + owner = models.ForeignKey(CustomUser, on_delete=models.CASCADE, related_name='owned_teams', blank=True, null=True) + category = models.ForeignKey('NewCategory', on_delete=models.SET_DEFAULT, default=get_default_category) + + class Meta: + unique_together = ('zekken_number', 'category') def __str__(self): return f"{self.zekken_number} - {self.team_name}, owner:{self.owner.lastname} {self.owner.firstname}" class Member(models.Model): - team = models.ForeignKey(Team, on_delete=models.CASCADE) + team = models.ForeignKey(Team, on_delete=models.CASCADE, related_name='members') user = models.ForeignKey(CustomUser, on_delete=models.CASCADE) + is_temporary = models.BooleanField(default=False) # Akira 2024-7-24 class Meta: unique_together = ('team', 'user') @@ -289,10 +306,29 @@ class Member(models.Model): def __str__(self): return f"{self.team.zekken_number} - {self.user.lastname} {self.user.firstname}" -# +# class Category(models.Model): category_name = models.CharField(max_length=255, primary_key=True) - category_number = models.IntegerField(unique=True) + category_number = models.IntegerField(default=0) + duration = models.DurationField(default=timedelta(hours=5)) + num_of_member = models.IntegerField(default=1) + family = models.BooleanField(default=False) + female = models.BooleanField(default=False) + + class Meta: + unique_together = ('category_name','category_number') + + def __str__(self): + hours = self.duration.total_seconds() // 3600 + return f"{self.category_name} - {self.category_number} ({int(hours)}時間)" + + @property + def hours(self): + return self.duration.total_seconds() // 3600 + +class NewCategory(models.Model): + category_name = models.CharField(max_length=255, unique=True) + category_number = models.IntegerField(default=0) duration = models.DurationField(default=timedelta(hours=5)) num_of_member = models.IntegerField(default=1) family = models.BooleanField(default=False) @@ -311,12 +347,13 @@ class Category(models.Model): class Entry(models.Model): team = models.ForeignKey(Team, on_delete=models.CASCADE) - event = models.ForeignKey(NewEvent, on_delete=models.CASCADE) - category = models.ForeignKey(Category, on_delete=models.CASCADE) + event = models.ForeignKey(NewEvent2, on_delete=models.CASCADE) + category = models.ForeignKey(NewCategory, on_delete=models.CASCADE) date = models.DateTimeField() + owner = models.ForeignKey(CustomUser, on_delete=models.CASCADE,blank=True, null=True) # Akira 2024-7-24 class Meta: - unique_together = ('team', 'event', 'date') + unique_together = ('team', 'event', 'date','owner') def __str__(self): return f"{self.team.zekken_number} - {self.event.event_name} - {self.date}" @@ -332,9 +369,13 @@ class Entry(models.Model): }) # メンバーの年齢と性別をチェック - members = Member.objects.filter(team=self.team) - if not members.exists(): - raise ValidationError("チームにメンバーが登録されていません。") + if self.team and not self.team.members.exists(): + raise ValidationError({'team': 'チームにメンバーが登録されていません。'}) + + #members = Member.objects.filter(team=self.team) + #if not members.exists(): + # # ここで、owner をMemberに登録する。 Akira 2024-7-24 + # raise ValidationError("チームにメンバーが登録されていません。") adults = [m for m in members if self.is_adult(m.user.date_of_birth)] children = [m for m in members if self.is_child(m.user.date_of_birth)] @@ -387,6 +428,17 @@ class Entry(models.Model): return 13 <= age <= 17 +class EntryMember(models.Model): + entry = models.ForeignKey(Entry, on_delete=models.CASCADE) + member = models.ForeignKey(Member, on_delete=models.CASCADE) + is_temporary = models.BooleanField(default=False) # Akira 2024-7-24 + + class Meta: + unique_together = ('entry', 'member') + + def __str__(self): + return f"{self.entry.team.zekken_number} - {self.member.user.lastname} {self.member.user.firstname}" + class GoalImages(models.Model): user=models.ForeignKey(CustomUser, on_delete=models.DO_NOTHING) diff --git a/rog/permissions.py b/rog/permissions.py index efcee1d..0fb526c 100644 --- a/rog/permissions.py +++ b/rog/permissions.py @@ -1,6 +1,16 @@ + from rest_framework import permissions +from .models import Team,Member class IsMemberOrTeamOwner(permissions.BasePermission): def has_object_permission(self, request, view, obj): - # obj は Member インスタンス - return request.user == obj.user or request.user == obj.team.owner + # Check if user is a member of the team or the team owner + return request.user in obj.team.members.all() or request.user == obj.team.owner + +class IsTeamOwner(permissions.BasePermission): + def has_object_permission(self, request, view, obj): + if isinstance(obj, Team): + return obj.owner == request.user + elif isinstance(obj, Member): + return obj.team.owner == request.user + return False diff --git a/rog/serializers.py b/rog/serializers.py index b2d02e0..0cb88f8 100644 --- a/rog/serializers.py +++ b/rog/serializers.py @@ -1,15 +1,25 @@ +import uuid +from django.db import IntegrityError +from django.core.mail import send_mail +from django.conf import settings +from django.urls import reverse + +from django.db import transaction from rest_framework import serializers from rest_framework_gis.serializers import GeoFeatureModelSerializer from sqlalchemy.sql.functions import mode -from .models import Location, Location_line, Location_polygon, JpnAdminMainPerf, Useractions, GifuAreas, RogUser, UserTracks, GoalImages, CheckinImages,CustomUser,NewEvent, Team, Category, Entry, Member, TempUser +from .models import Location, Location_line, Location_polygon, JpnAdminMainPerf, Useractions, GifuAreas, RogUser, UserTracks, GoalImages, CheckinImages,CustomUser,NewEvent,NewEvent2, Team, NewCategory, Category, Entry, Member, TempUser,EntryMember from drf_extra_fields.fields import Base64ImageField #from django.contrib.auth.models import User from .models import CustomUser from django.contrib.auth import authenticate +from .utils import send_activation_email from .models import TestModel +import logging +logger = logging.getLogger(__name__) class LocationCatSerializer(serializers.ModelSerializer): class Meta: @@ -194,15 +204,67 @@ class RegistrationSerializer(serializers.ModelSerializer): user.save() return user +class NewCategorySerializer(serializers.ModelSerializer): + class Meta: + model = NewCategory + #fields = ['id','category_name', 'category_number', 'duration', 'num_of_member', 'family', 'female'] + fields = ['id','category_name', 'category_number'] + +class NewEvent2Serializer(serializers.ModelSerializer): + class Meta: + model = NewEvent2 + fields = ['id','event_name', 'start_datetime', 'end_datetime'] + class NewEventSerializer(serializers.ModelSerializer): class Meta: model = NewEvent fields = ['event_name', 'start_datetime', 'end_datetime'] class TeamSerializer(serializers.ModelSerializer): + #category = serializers.PrimaryKeyRelatedField(queryset=NewCategory.objects.all()) + + category = NewCategorySerializer(read_only=True) + #category_id = serializers.PrimaryKeyRelatedField( + # queryset=NewCategory.objects.all(), + # source='category', + # write_only=True + #) + owner = serializers.PrimaryKeyRelatedField(read_only=True) + + class Meta: model = Team - fields = ['zekken_number', 'team_name', 'password', 'owner'] + fields = ['id','zekken_number', 'team_name', 'category', 'owner'] + read_only_fields = ['id', 'owner', 'zekken_number'] + + def to_representation(self, instance): + ret = super().to_representation(instance) + ret['category'] = { + 'id': instance.category.id, + 'category_name': instance.category.category_name, + 'category_number': instance.category.category_number + } + ret['owner'] = CustomUserSerializer(instance.owner).data + return ret + + def validate_category(self, value): + if not NewCategory.objects.filter(id=value.id).exists(): + raise serializers.ValidationError("Invalid category ID.") + return value + + def create(self, validated_data): + category = validated_data.pop('category') + team = Team.objects.create(category=category, **validated_data) + return team + + #logger.debug(f"Creating team with data: {validated_data}") + #validated_data['owner'] = self.context['request'].user + #return super().create(validated_data) + + def update(self, instance, validated_data): + if 'category' in validated_data: + instance.category = validated_data.pop('category') + return super().update(instance, validated_data) class CategorySerializer(serializers.ModelSerializer): class Meta: @@ -210,29 +272,60 @@ class CategorySerializer(serializers.ModelSerializer): fields = ['category_name', 'category_number', 'duration', 'num_of_member', 'family', 'female'] class EntrySerializer(serializers.ModelSerializer): + team = serializers.PrimaryKeyRelatedField(queryset=Team.objects.all()) + event = serializers.PrimaryKeyRelatedField(queryset=NewEvent.objects.all()) + category = serializers.PrimaryKeyRelatedField(queryset=NewCategory.objects.all()) + owner = serializers.PrimaryKeyRelatedField(read_only=True) + class Meta: model = Entry - fields = ['team', 'event', 'category', 'date'] + fields = ['id','team', 'event', 'category', 'date','owner'] -class MemberSerializer(serializers.ModelSerializer): - class Meta: - model = Member - fields = ['team', 'user'] + def validate_team(self, value): + if not value.members.exists(): + raise serializers.ValidationError("チームにメンバーが登録されていません。") + return value -class TempUserSerializer(serializers.ModelSerializer): - class Meta: - model = TempUser - fields = ['email', 'password', 'is_rogaining', 'zekken_number', 'event_code', 'team_name', 'group', 'firstname', 'lastname', 'date_of_birth', 'female', 'verification_code', 'created_at', 'expires_at'] + def to_representation(self, instance): + ret = super().to_representation(instance) + ret['team'] = instance.team.team_name + ret['event'] = instance.event.event_name + ret['category'] = instance.category.category_name + ret['owner'] = instance.owner.email + return ret class CustomUserSerializer(serializers.ModelSerializer): class Meta: model = CustomUser - fields = ['email', 'is_staff', 'is_active', 'is_rogaining', 'zekken_number', 'event_code', 'team_name', 'group', 'firstname', 'lastname', 'date_of_birth', 'female'] + #fields = ['email', 'is_staff', 'is_active', 'is_rogaining', 'zekken_number', 'event_code', 'team_name', 'group', 'firstname', 'lastname', 'date_of_birth', 'female'] + fields = ['id','email', 'firstname', 'lastname', 'date_of_birth', 'female'] + read_only_fields = ['email'] + +class TeamDetailSerializer(serializers.ModelSerializer): + category = NewCategorySerializer(read_only=True) + + class Meta: + model = Team + fields = ['id', 'zekken_number', 'team_name', 'category'] class UserSerializer(serializers.ModelSerializer): class Meta: model = CustomUser - fields = ['email', 'firstname', 'lastname', 'date_of_birth', 'female', 'is_rogaining', 'zekken_number', 'event_code', 'team_name', 'group'] + fields = ['id','email', 'firstname', 'lastname', 'date_of_birth', 'female', 'is_rogaining', 'zekken_number', 'event_code', 'team_name', 'group'] + read_only_fields = ('id', 'email') + +class UserUpdateSerializer(serializers.ModelSerializer): + class Meta: + model = CustomUser + fields = ['firstname', 'lastname', 'date_of_birth', 'female'] + extra_kwargs = {'email': {'read_only': True}} + + def update(self, instance, validated_data): + for attr, value in validated_data.items(): + setattr(instance, attr, value) + instance.save() + return instance + class MemberWithUserSerializer(serializers.ModelSerializer): user = UserSerializer(read_only=True) @@ -240,3 +333,149 @@ class MemberWithUserSerializer(serializers.ModelSerializer): class Meta: model = Member fields = ['user', 'team'] + +class MemberSerializer(serializers.ModelSerializer): + user = CustomUserSerializer() + team = TeamDetailSerializer(read_only=True) + + #email = serializers.EmailField(write_only=True, required=False) + #firstname = serializers.CharField(write_only=True, required=False) + #lastname = serializers.CharField(write_only=True, required=False) + #date_of_birth = serializers.DateField(write_only=True, required=False) + #female = serializers.BooleanField(write_only=True, required=False) + + class Meta: + model = Member + fields = ['id','user','team'] # ,'email','firstname','lastname','date_of_birth','female'] + #read_only_fields = ['id', 'team'] + + def create(self, validated_data): + team = validated_data['team'] + email = validated_data.get('email') + + if email: + user, created = CustomUser.objects.get_or_create(email=email) + if created: + user.firstname = validated_data.get('firstname', '') + user.lastname = validated_data.get('lastname', '') + user.date_of_birth = validated_data.get('date_of_birth') + user.female = validated_data.get('female', False) + user.is_active = False + user.activation_token = str(uuid.uuid4()) + user.save() + + activation_link = self.context['request'].build_absolute_uri( + reverse('user-activation', kwargs={'activation_token': user.activation_token}) + ) + try: + send_activation_email(user, activation_link) + except Exception as e: + logger.error(f"アクティベーションメールの送信中にエラーが発生しました: {str(e)}") + # メール送信に失敗しても、ユーザー作成は続行します + + else: + dummy_email = f"dummy_{uuid.uuid4()}@example.com" + user = CustomUser.objects.create( + email=dummy_email, + firstname=validated_data.get('firstname', ''), + lastname=validated_data.get('lastname', ''), + date_of_birth=validated_data.get('date_of_birth'), + female=validated_data.get('female', False), + is_active=True + ) + + try: + member = Member.objects.create(user=user, team=team) + except IntegrityError: + # ユーザーがすでにチームのメンバーの場合 + raise serializers.ValidationError("このユーザーは既にチームのメンバーです。") + + return member + + def update(self, instance, validated_data): + user_data = validated_data.pop('user', {}) + user = instance.user + + if user.email.startswith('dummy_'): # dummy_ で始まるメールアドレスの場合のみ更新 + for attr, value in user_data.items(): + setattr(user, attr, value) + user.save() + else: + raise serializers.ValidationError("このユーザーの情報は更新できません。") + + return super().update(instance, validated_data) + + + +class EntryMemberSerializer(serializers.ModelSerializer): + class Meta: + model = EntryMember + fields = ['id', 'entry', 'member', 'is_temporary'] + + +class TempUserSerializer(serializers.ModelSerializer): + class Meta: + model = TempUser + fields = ['id','email', 'password', 'is_rogaining', 'zekken_number', 'event_code', 'team_name', 'group', 'firstname', 'lastname', 'date_of_birth', 'female', 'verification_code', 'created_at', 'expires_at'] + +class EntryCreationSerializer(serializers.Serializer): + owner_email = serializers.EmailField() + event_name = serializers.CharField() + category_name = serializers.CharField() + team_name = serializers.CharField() + zekken_number = serializers.CharField() + date = serializers.DateField() + members = serializers.ListField(child=serializers.DictField()) + + def create(self, validated_data): + owner = CustomUser.objects.get(email=validated_data['owner_email']) + event = NewEvent2.objects.get(event_name=validated_data['event_name']) + category = NewCategory.objects.get(category_name=validated_data['category_name']) + + # Create or get team + team, _ = Team.objects.get_or_create( + zekken_number=validated_data['zekken_number'], + category=category, + defaults={'team_name': validated_data['team_name'], 'owner': owner} + ) + + # Create or update entry + entry, _ = Entry.objects.update_or_create( + owner=owner, + team=team, + event=event, + date=validated_data['date'], + defaults={'category': category} + ) + + # Process members + for member_data in validated_data['members']: + user, created = CustomUser.objects.get_or_create( + email=member_data.get('email'), + defaults={ + 'firstname': member_data['firstname'], + 'lastname': member_data['lastname'], + 'date_of_birth': member_data['date_of_birth'] + } + ) + + if created: + TempUser.objects.create( + email=user.email, + firstname=user.firstname, + lastname=user.lastname, + date_of_birth=user.date_of_birth + ) + # Send invitation email here + + member, _ = NewMember.objects.get_or_create( + user=user, + team=team, + defaults={'is_temporary': created} + ) + + EntryMember.objects.get_or_create(entry=entry, member=member) + + return entry + + diff --git a/rog/templates/email/activation_email.txt b/rog/templates/email/activation_email.txt new file mode 100644 index 0000000..67a37a0 --- /dev/null +++ b/rog/templates/email/activation_email.txt @@ -0,0 +1,16 @@ +件名: アカウントのアクティベーション +{name} 様 + +この度は、サービスにご登録いただき、ありがとうございます。 +以下のリンクをクリックしてアカウントをアクティベートしてください: + +{activation_link} + +また、アプリをダウンロードしてください: + +{app_download_link} + +このメールに心当たりがない場合は、お手数ですが破棄してください。 +よろしくお願いいたします。 + +{service_name} チーム diff --git a/rog/templates/email/activation_friend_email.txt b/rog/templates/email/activation_friend_email.txt new file mode 100644 index 0000000..24f0cc7 --- /dev/null +++ b/rog/templates/email/activation_friend_email.txt @@ -0,0 +1,16 @@ +件名: アカウントのアクティベーション + +{name} 様 + +この度は、サービスにご登録いただき、ありがとうございます。 + +以下のリンクをクリックしてアカウントをアクティベートしてください: +{activation_link} + +また、アプリをダウンロードしてください: +{app_download_link} + +このメールに心当たりがない場合は、お手数ですが破棄してください。 +よろしくお願いいたします。 + +{service_name} チーム diff --git a/rog/urls.py b/rog/urls.py index d5a9408..69fb1a8 100644 --- a/rog/urls.py +++ b/rog/urls.py @@ -1,12 +1,13 @@ from sys import prefix from rest_framework import urlpatterns from rest_framework.routers import DefaultRouter -from .views import LocationViewSet, Location_lineViewSet, Location_polygonViewSet, Jpn_Main_PerfViewSet, LocationsInPerf, ExtentForSubPerf, SubPerfInMainPerf, ExtentForMainPerf, LocationsInSubPerf, CatView, RegistrationAPI, LoginAPI, UserAPI, UserActionViewset, UserMakeActionViewset, UserDestinations, UpdateOrder, LocationInBound, DeleteDestination, CustomAreaLocations, GetAllGifuAreas, CustomAreaNames, userDetials, UserTracksViewSet, CatByCity, ChangePasswordView, GoalImageViewSet, CheckinImageViewSet, ExtentForLocations, DeleteAccount, PrivacyView, RegistrationView, TeamViewSet,MemberViewSet,EntryViewSet,RegisterView, VerifyEmailView, NewEventListView,CategoryListView, MemberUserDetailView, TeamMembersWithUserView +from .views import LocationViewSet, Location_lineViewSet, Location_polygonViewSet, Jpn_Main_PerfViewSet, LocationsInPerf, ExtentForSubPerf, SubPerfInMainPerf, ExtentForMainPerf, LocationsInSubPerf, CatView, RegistrationAPI, LoginAPI, UserAPI, UserActionViewset, UserMakeActionViewset, UserDestinations, UpdateOrder, LocationInBound, DeleteDestination, CustomAreaLocations, GetAllGifuAreas, CustomAreaNames, userDetials, UserTracksViewSet, CatByCity, ChangePasswordView, GoalImageViewSet, CheckinImageViewSet, ExtentForLocations, DeleteAccount, PrivacyView, RegistrationView, TeamViewSet,MemberViewSet,EntryViewSet,RegisterView, VerifyEmailView, NewEventListView,NewEvent2ListView,NewCategoryListView,CategoryListView, MemberUserDetailView, TeamMembersWithUserView,MemberAddView,UserActivationView from django.urls import path, include from knox import views as knox_views from .views import TestActionViewSet +from .views import OwnerEntriesView, OwnerTeamsView, OwnerMembersView router = DefaultRouter() @@ -26,9 +27,14 @@ router.register(prefix='checkinimage', viewset=CheckinImageViewSet, basename='ch # /api/entries/ - エントリーの一覧取得と作成 # /api/entries// - 特定のエントリーの取得、更新、削除 # -router.register(r'teams', TeamViewSet) -router.register(r'members', MemberViewSet) -router.register(r'entries', EntryViewSet) +#router.register(r'teams', TeamViewSet) +#router.register(r'members', MemberViewSet) +#router.register(r'entries', EntryViewSet) + +router.register(r'entry', EntryViewSet, basename='entry') +router.register(r'teams', TeamViewSet, basename='team') +router.register(r'members', MemberViewSet, basename='member') + # Akira 追加 # GET /api/members//user/: 特定のメンバーのユーザー情報を取得 @@ -69,9 +75,13 @@ urlpatterns += [ # path('goal-image/', GoalImageViewSet.as_view(), name='goal-image') path('register/', RegisterView.as_view(), name='register'), path('verify-email//', VerifyEmailView.as_view(), name='verify-email'), - path('categories/', CategoryListView.as_view(), name='category-list'), - path('new-events/', NewEventListView.as_view(), name='new-event-list'), + path('categories/', NewCategoryListView.as_view(), name='category-list'), + path('new-events/', NewEvent2ListView.as_view(), name='new-event-list'), path('members//user/', MemberUserDetailView.as_view(), name='member-user-detail'), path('teams//members-with-user/', TeamMembersWithUserView.as_view(), name='team-members-with-user'), + path('teams//members/', MemberViewSet.as_view({'get': 'list', 'post': 'create'}), name='team-members'), + path('teams//members//', MemberViewSet.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'}), name='team-member-detail'), + path('activate//', UserActivationView.as_view(), name='user-activation'), + ] diff --git a/rog/utils.py b/rog/utils.py new file mode 100644 index 0000000..2c46097 --- /dev/null +++ b/rog/utils.py @@ -0,0 +1,34 @@ +import os +from django.template.loader import render_to_string +from django.conf import settings +import logging +from django.core.mail import send_mail + +logger = logging.getLogger(__name__) + +def load_email_template(template_name, context): + template_path = os.path.join('email', template_name) + email_content = render_to_string(template_path, context) + + # 件名と本文を分離 + subject, _, body = email_content.partition('\n\n') + subject = subject.replace('件名: ', '').strip() + + return subject, body + +def send_activation_email(user, activation_link): + context = { + 'name': user.firstname or user.email, + 'activation_link': activation_link, + 'app_download_link': settings.APP_DOWNLOAD_LINK, + 'service_name': settings.SERVICE_NAME, + } + + subject, body = load_email_template('activation_email.txt', context) + + try: + send_mail(subject, body, settings.DEFAULT_FROM_EMAIL, [user.email], fail_silently=False) + logger.info(f"アクティベーションメールを送信しました。 受信者: {user.email}") + except Exception as e: + logger.error(f"アクティベーションメールの送信に失敗しました。 受信者: {user.email}, エラー: {str(e)}") + raise # エラーを再度発生させて、呼び出し元で処理できるようにします diff --git a/rog/views.py b/rog/views.py index 26be12b..28fdbb1 100644 --- a/rog/views.py +++ b/rog/views.py @@ -1,12 +1,28 @@ +from rest_framework import serializers +from django.db import IntegrityError +from django.urls import reverse +from .utils import send_activation_email +import uuid +from rest_framework.exceptions import ValidationError as DRFValidationError + +from django.db import transaction +from django.db.models import F +from rest_framework import viewsets, permissions, status +from rest_framework.decorators import action +from rest_framework.response import Response +from django.shortcuts import get_object_or_404 +from .models import Team, Member, CustomUser, NewCategory +from .serializers import TeamSerializer, MemberSerializer, CustomUserSerializer, TeamDetailSerializer,UserUpdateSerializer +from .permissions import IsTeamOwner + from curses.ascii import NUL from django.core.serializers import serialize -from .models import GoalImages, Location, Location_line, Location_polygon, JpnAdminMainPerf, Useractions, GifuAreas, RogUser, CustomUser, UserTracks, GoalImages, CheckinImages, NewEvent, Team, Category, Entry, Member, TempUser +from .models import GoalImages, Location, Location_line, Location_polygon, JpnAdminMainPerf, Useractions, GifuAreas, RogUser, CustomUser, UserTracks, GoalImages, CheckinImages, NewEvent,NewEvent2, Team, Category, NewCategory,Entry, Member, TempUser,EntryMember from rest_framework import viewsets from .serializers import LocationSerializer, Location_lineSerializer, Location_polygonSerializer, JPN_main_perfSerializer, LocationCatSerializer, CreateUserSerializer, UserSerializer, LoginUserSerializer, UseractionsSerializer, UserDestinationSerializer, GifuAreaSerializer, LocationEventNameSerializer, RogUserSerializer, UserTracksSerializer, ChangePasswordSerializer, GolaImageSerializer, CheckinImageSerializer, RegistrationSerializer, MemberWithUserSerializer from knox.models import AuthToken from rest_framework import viewsets, generics, status -from rest_framework.decorators import action from rest_framework.response import Response from rest_framework.parsers import JSONParser, MultiPartParser from .serializers import LocationSerializer @@ -14,7 +30,7 @@ from django.http import JsonResponse from rest_framework.permissions import IsAuthenticated from django.contrib.gis.db.models import Extent, Union -from .serializers import TestSerialiser,NewEventSerializer, TeamSerializer, CategorySerializer, EntrySerializer, MemberSerializer, TempUserSerializer, CustomUserSerializer +from .serializers import TestSerialiser,NewEventSerializer,NewEvent2Serializer, TeamSerializer, NewCategorySerializer,CategorySerializer, EntrySerializer, MemberSerializer, TempUserSerializer, CustomUserSerializer,EntryMemberSerializer,EntryCreationSerializer from .models import TestModel from django.shortcuts import get_object_or_404 from django.db.models import F @@ -27,13 +43,17 @@ from rest_framework.decorators import api_view from rest_framework.decorators import api_view, permission_classes from rest_framework.parsers import JSONParser, MultiPartParser from django.views.decorators.csrf import csrf_exempt -import uuid from django.shortcuts import render from .permissions import IsMemberOrTeamOwner from django.utils.decorators import method_decorator from django.utils.encoding import force_str +import logging + +logger = logging.getLogger(__name__) + + class LocationViewSet(viewsets.ModelViewSet): queryset=Location.objects.all() serializer_class=LocationSerializer @@ -288,6 +308,25 @@ class LoginAPI(generics.GenericAPIView): "token": AuthToken.objects.create(user)[1] }) +class UserUpdateAPI(generics.UpdateAPIView): + permission_classes = [permissions.IsAuthenticated] + serializer_class = UserUpdateSerializer + + def get_object(self): + return self.request.user + + def update(self, request, *args, **kwargs): + partial = kwargs.pop('partial', False) + instance = self.get_object() + serializer = self.get_serializer(instance, data=request.data, partial=partial) + serializer.is_valid(raise_exception=True) + self.perform_update(serializer) + + if getattr(instance, '_prefetched_objects_cache', None): + instance._prefetched_objects_cache = {} + + return Response(serializer.data) + class UserAPI(generics.RetrieveAPIView): permission_classes = [permissions.IsAuthenticated,] serializer_class = UserSerializer @@ -509,6 +548,17 @@ def CustomAreaNames(request): return JsonResponse(serializer.data, safe=False) +class UserActivationView(APIView): + def get(self, request, activation_token): + try: + user = CustomUser.objects.get(activation_token=activation_token, is_active=False) + user.is_active = True + user.activation_token = None + user.save() + return Response({"message": "アカウントが正常にアクティベートされました。"}, status=status.HTTP_200_OK) + except CustomUser.DoesNotExist: + return Response({"error": "無効なアクティベーショントークンです。"}, status=status.HTTP_400_BAD_REQUEST) + class ChangePasswordView(generics.UpdateAPIView): """ An endpoint for changing password. @@ -562,32 +612,96 @@ class RegistrationView(APIView): # Akira -@method_decorator(csrf_exempt, name='dispatch') +class NewEvent2ViewSet(viewsets.ModelViewSet): + queryset = NewEvent2.objects.all() + serializer_class = NewEvent2Serializer + permission_classes = [IsAuthenticated] + +class NewEvent2ListView(generics.ListAPIView): + queryset = NewEvent2.objects.all() + serializer_class = NewEvent2Serializer + permission_classes = [IsAuthenticated] + class NewEventViewSet(viewsets.ModelViewSet): queryset = NewEvent.objects.all() serializer_class = NewEventSerializer permission_classes = [IsAuthenticated] -@method_decorator(csrf_exempt, name='dispatch') class NewEventListView(generics.ListAPIView): queryset = NewEvent.objects.all() serializer_class = NewEventSerializer permission_classes = [IsAuthenticated] class TeamViewSet(viewsets.ModelViewSet): - queryset = Team.objects.all() serializer_class = TeamSerializer - permission_classes = [permissions.IsAuthenticated] + permission_classes = [permissions.IsAuthenticated, IsTeamOwner] + + def get_queryset(self): + return Team.objects.filter(owner=self.request.user) def perform_create(self, serializer): - serializer.save(owner=self.request.user) + with transaction.atomic(): + category = serializer.validated_data['category'] + category = NewCategory.objects.select_for_update().get(id=category.id) + zekken_number = category.category_number + category.category_number = F('category_number') + 1 + category.save() + category.refresh_from_db() # F() 式の結果を評価 + + serializer.save(owner=self.request.user, zekken_number=zekken_number) + + + def destroy(self, request, *args, **kwargs): + team = self.get_object() + if team.members.exists(): + return Response({"error": "チームにメンバーが残っているため削除できません。"}, status=status.HTTP_400_BAD_REQUEST) + return super().destroy(request, *args, **kwargs) + + def update(self, request, *args, **kwargs): + try: + return super().update(request, *args, **kwargs) + except Exception as e: + return Response({"error": "更新に失敗しました。競合が発生した可能性があります。"}, status=status.HTTP_409_CONFLICT) + + @action(detail=True, methods=['post']) + def copy(self, request, pk=None): + original_team = self.get_object() + with transaction.atomic(): + category = NewCategory.objects.select_for_update().get(id=original_team.category.id) + new_zekken_number = category.category_number + category.category_number = F('category_number') + 1 + category.save() + category.refresh_from_db() # F() 式の結果を評価 + + new_team = Team.objects.create( + zekken_number=new_zekken_number, + team_name=f"{original_team.team_name} (コピー)", + category=category, + owner=request.user + ) + for member in original_team.members.all(): + Member.objects.create( + team=new_team, + user=member.user + ) + return Response(TeamSerializer(new_team).data, status=status.HTTP_201_CREATED) + + +class NewCategoryViewSet(viewsets.ModelViewSet): + queryset = NewCategory.objects.all() + serializer_class = NewCategorySerializer + permission_classes = [IsAuthenticated] + +class NewCategoryListView(generics.ListAPIView): + queryset = NewCategory.objects.all() + serializer_class = NewCategorySerializer + permission_classes = [IsAuthenticated] class CategoryViewSet(viewsets.ModelViewSet): queryset = Category.objects.all() serializer_class = CategorySerializer permission_classes = [IsAuthenticated] -@method_decorator(csrf_exempt, name='dispatch') class CategoryListView(generics.ListAPIView): queryset = Category.objects.all() serializer_class = CategorySerializer @@ -611,16 +725,148 @@ class EntryViewSet(viewsets.ModelViewSet): serializer_class = EntrySerializer permission_classes = [permissions.IsAuthenticated] - def perform_create(self, serializer): - team = Team.objects.get(owner=self.request.user) - serializer.save(team=team) + #def perform_create(self, serializer): + # team = Team.objects.get(owner=self.request.user) + # serializer.save(team=team) + def get_queryset(self): + user = self.request.user + # ユーザーが所属するチームのIDを取得 + team_ids = Member.objects.filter(user=user).values_list('team_id', flat=True) + # そのチームに関連するエントリーを取得 + return Entry.objects.filter(team__id__in=team_ids) + + def create(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + try: + serializer.is_valid(raise_exception=True) + self.perform_create(serializer) + headers = self.get_success_headers(serializer.data) + return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) + except DRFValidationError as e: + return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST) + + #except ValidationError as e: + # return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST) + + def perform_create(self, serializer): + serializer.save(owner=self.request.user) + + def update(self, request, *args, **kwargs): + partial = kwargs.pop('partial', False) + instance = self.get_object() + serializer = self.get_serializer(instance, data=request.data, partial=partial) + serializer.is_valid(raise_exception=True) + self.perform_update(serializer) + return Response(serializer.data) + + def destroy(self, request, *args, **kwargs): + instance = self.get_object() + self.perform_destroy(instance) + return Response(status=status.HTTP_204_NO_CONTENT) class MemberViewSet(viewsets.ModelViewSet): - queryset = Member.objects.all() + serializer_class = MemberSerializer + permission_classes = [permissions.IsAuthenticated,IsTeamOwner] + + def get_queryset(self): + team_id = self.kwargs['team_id'] + return Member.objects.filter(team_id=team_id) + + def create(self, request, *args, **kwargs): + team = Team.objects.get(id=self.kwargs['team_id']) + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + try: + self.perform_create(serializer) + except DRFValidationError as e: + return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST) + #except ValidationError as e: + # return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST) + except IntegrityError: + return Response({"error": "このユーザーは既にチームのメンバーです。"}, status=status.HTTP_400_BAD_REQUEST) + + headers = self.get_success_headers(serializer.data) + return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) + + + def perform_create(self, serializer): + team = Team.objects.get(id=self.kwargs['team_id']) + serializer.save(team=team) + + def update(self, request, *args, **kwargs): + partial = kwargs.pop('partial', False) + instance = self.get_object() + serializer = self.get_serializer(instance, data=request.data, partial=partial) + serializer.is_valid(raise_exception=True) + + if not instance.user.email.startswith('dummy_'): + return Response({"error": "このユーザーの情報は更新できません。"}, status=status.HTTP_400_BAD_REQUEST) + + + self.perform_update(serializer) + + if getattr(instance, '_prefetched_objects_cache', None): + instance._prefetched_objects_cache = {} + + return Response(serializer.data) + + def perform_update(self, serializer): + serializer.save() + + def get_object(self): + queryset = self.get_queryset() + member_id = self.kwargs['pk'] + obj = get_object_or_404(queryset, id=member_id) + self.check_object_permissions(self.request, obj) + return obj + +class OwnerEntriesView(generics.ListAPIView): + serializer_class = EntrySerializer + permission_classes = [permissions.IsAuthenticated] + + def get_queryset(self): + user = self.request.user + return Entry.objects.filter(owner=user) + +class OwnerTeamsView(generics.ListAPIView): + serializer_class = TeamSerializer + permission_classes = [permissions.IsAuthenticated] + + def get_queryset(self): + user = self.request.user + return Team.objects.filter(owner=user) + +class OwnerMembersView(generics.ListAPIView): serializer_class = MemberSerializer permission_classes = [permissions.IsAuthenticated] + def get_queryset(self): + user = self.request.user + return Member.objects.filter(team__owner=user) + +class MemberAddView(APIView): + def post(self, request, team_id): + logger.info(f"Received request to add member to team {team_id}") + logger.debug(f"Request data: {request.data}") + + team = get_object_or_404(Team, id=team_id) + logger.info(f"Found team: {team}") + + serializer = MemberSerializer(data=request.data) + if serializer.is_valid(): + logger.info("Serializer is valid") + try: + member = serializer.save(team=team) + logger.info(f"Member added successfully: {member}") + return Response(serializer.data, status=status.HTTP_201_CREATED) + except Exception as e: + logger.error(f"Error saving member: {str(e)}") + return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST) + else: + logger.error(f"Serializer errors: {serializer.errors}") + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + class TempUserViewSet(viewsets.ModelViewSet): queryset = TempUser.objects.all() @@ -639,9 +885,6 @@ class CustomUserViewSet(viewsets.ModelViewSet): return CustomUser.objects.all() return CustomUser.objects.filter(id=user.id) -# 追加の機能として、チームメンバーを取得するビュー -from rest_framework.decorators import action -from rest_framework.response import Response class TeamMembersView(generics.ListAPIView): serializer_class = MemberSerializer @@ -738,4 +981,3 @@ class TeamMembersWithUserView(generics.ListAPIView): team_id = self.kwargs['team_id'] return Member.objects.filter(team_id=team_id).select_related('user', 'team') -