diff --git a/Dockerfile.gdal b/Dockerfile.gdal index 98097a6..c30d958 100644 --- a/Dockerfile.gdal +++ b/Dockerfile.gdal @@ -53,6 +53,7 @@ RUN pip install gunicorn # xlsxwriterを追加 RUN pip install -r requirements.txt \ + && pip install django-cors-headers \ && pip install xlsxwriter gunicorn COPY . /app diff --git a/config/settings.py b/config/settings.py index 69991cf..44ad25b 100644 --- a/config/settings.py +++ b/config/settings.py @@ -53,10 +53,14 @@ INSTALLED_APPS = [ 'leaflet', 'leaflet_admin_list', 'rog.apps.RogConfig', + 'corsheaders', # added 'django_filters' ] MIDDLEWARE = [ + 'corsheaders.middleware.CorsMiddleware', # できるだけ上部に + 'django.middleware.common.CommonMiddleware', + 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', @@ -68,6 +72,43 @@ MIDDLEWARE = [ ROOT_URLCONF = 'config.urls' +CORS_ALLOW_ALL_ORIGINS = True # 開発環境のみ +CORS_ALLOW_CREDENTIALS = True + +CORS_ALLOWED_METHODS = [ + 'GET', + 'POST', + 'PUT', + 'PATCH', + 'DELETE', + 'OPTIONS' +] +CORS_ALLOWED_HEADERS = [ + 'accept', + 'accept-encoding', + 'authorization', + 'content-type', + 'dnt', + 'origin', + 'user-agent', + 'x-csrftoken', + 'x-requested-with', +] + +# 本番環境では以下のように制限する +CORS_ALLOWED_ORIGINS = [ + "https://rogaining.sumasen.net", + "http://rogaining.sumasen.net", +] + +# CSRFの設定 +CSRF_TRUSTED_ORIGINS = [ + "http://rogaining.sumasen.net", + "https://rogaining.sumasen.net", +] + + + TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', @@ -231,11 +272,15 @@ LOGGING = { 'level': 'DEBUG', }, 'loggers': { - #'django': { - # 'handlers': ['console'], - # 'level': 'INFO', - # 'propagate': False, - #}, + 'django': { + 'handlers': ['console'], + 'level': 'INFO', + 'propagate': False, + }, + 'django.request': { + 'handlers': ['console'], + 'level': 'DEBUG', + }, 'rog': { #'handlers': ['file','console'], 'handlers': ['console'], diff --git a/config/urls.py b/config/urls.py index b693be9..13fe9f8 100644 --- a/config/urls.py +++ b/config/urls.py @@ -18,6 +18,21 @@ from django.urls import path, include from django.conf import settings from django.conf.urls.static import static + +# debug_urlsビューをrogアプリケーションのviewsからインポート +from rog import views as rog_views + +DEBUG = True +ALLOWED_HOSTS = ['rogaining.sumasen.net', 'localhost', '127.0.0.1'] + +# CORSの設定 +CORS_ALLOW_ALL_ORIGINS = True +CORS_ALLOWED_ORIGINS = [ + "http://rogaining.sumasen.net", + "http://localhost", + "http://127.0.0.1", +] + urlpatterns = [ path('admin/', admin.site.urls), path('auth/', include('knox.urls')), @@ -27,3 +42,8 @@ urlpatterns = [ admin.site.site_header = "ROGANING" admin.site.site_title = "Roganing Admin Portal" admin.site.index_title = "Welcome to Roganing Portal" + +# 開発環境での静的ファイル配信 +if settings.DEBUG: + urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/docker-compose.yaml b/docker-compose.yaml index 3b3247e..3f59ebb 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -30,40 +30,37 @@ services: env_file: - .env restart: "on-failure" - # depends_on: - # - postgres-db networks: - rog-api - #entrypoint: ["/app/wait-for.sh", "postgres-db:5432", "--", ""] - #command: python3 manage.py runserver 0.0.0.0:8100 supervisor-web: - build: - context: . - dockerfile: Dockerfile.supervisor - volumes: - - type: bind - source: ./supervisor/html - target: /usr/share/nginx/html - read_only: true - - type: bind - source: ./supervisor/nginx/default.conf - target: /etc/nginx/conf.d/default.conf - read_only: true - - type: volume - source: static_volume - target: /app/static - read_only: true - - type: volume - source: nginx_logs - target: /var/log/nginx - ports: - - "80:80" - depends_on: - - api - networks: - - rog-api - restart: always + build: + context: . + dockerfile: Dockerfile.supervisor + volumes: + - type: bind + source: ./supervisor/html + target: /usr/share/nginx/html/supervisor + read_only: true + - type: bind + source: ./supervisor/nginx/default.conf + target: /etc/nginx/conf.d/default.conf + read_only: true + - type: volume + source: static_volume + target: /app/static + read_only: true + - type: volume + source: nginx_logs + target: /var/log/nginx + ports: + - "80:80" + depends_on: + - api + networks: + - rog-api + restart: always + networks: diff --git a/docker-compose.yaml.ssl b/docker-compose.yaml.ssl new file mode 100644 index 0000000..51fee69 --- /dev/null +++ b/docker-compose.yaml.ssl @@ -0,0 +1,81 @@ +version: "3.9" + +services: + # postgres-db: + # image: kartoza/postgis:12.0 + # ports: + # - 5432:5432 + # volumes: + # - postgres_data:/var/lib/postgresql + # - ./custom-postgresql.conf:/etc/postgresql/12/main/postgresql.conf + # environment: + # - POSTGRES_USER=${POSTGRES_USER} + # - POSTGRES_PASS=${POSTGRES_PASS} + # - POSTGRES_DBNAME=${POSTGRES_DBNAME} + # - POSTGRES_MAX_CONNECTIONS=600 + + # restart: "on-failure" + # networks: + # - rog-api + + api: + build: + context: . + dockerfile: Dockerfile.gdal + command: python3 manage.py runserver 0.0.0.0:8100 + volumes: + - .:/app + ports: + - 8100:8100 + env_file: + - .env + restart: "on-failure" + # depends_on: + # - postgres-db + networks: + - rog-api + #entrypoint: ["/app/wait-for.sh", "postgres-db:5432", "--", ""] + #command: python3 manage.py runserver 0.0.0.0:8100 + + supervisor-web: + build: + context: . + dockerfile: Dockerfile.supervisor + volumes: + - type: bind + source: /etc/letsencrypt + target: /etc/nginx/ssl + read_only: true + - type: bind + source: ./supervisor/html + target: /usr/share/nginx/html + read_only: true + - type: bind + source: ./supervisor/nginx/default.conf + target: /etc/nginx/conf.d/default.conf + read_only: true + - type: volume + source: static_volume + target: /app/static + read_only: true + - type: volume + source: nginx_logs + target: /var/log/nginx + ports: + - "80:80" + depends_on: + - api + networks: + - rog-api + restart: always + + +networks: + rog-api: + driver: bridge + +volumes: + postgres_data: + geoserver-data: + static_volume: + nginx_logs: diff --git a/requirements.txt b/requirements.txt index ea2b7f3..5d311e0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -65,3 +65,4 @@ django-extra-fields==3.0.2 django-phonenumber-field==6.1.0 django-rest-knox==4.2.0 dj-database-url==2.0.0 +django-cors-headers==4.3.0 diff --git a/rog/urls.py b/rog/urls.py index a455bec..1cb6e8e 100644 --- a/rog/urls.py +++ b/rog/urls.py @@ -1,7 +1,8 @@ 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,NewEvent2ListView,NewCategoryListView,CategoryListView, MemberUserDetailView, TeamMembersWithUserView,MemberAddView,UserActivationView,RegistrationView,TempUserRegistrationView,ResendInvitationEmailView,update_user_info,update_user_detail,ActivateMemberView, ActivateNewMemberView, PasswordResetRequestView, PasswordResetConfirmView, NewCategoryViewSet,LocationInBound2,UserLastGoalTimeView,TeamEntriesView,update_entry_status,get_events,get_zekken_numbers,get_team_info,get_checkins,update_checkins,export_excel +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,RegistrationView,TempUserRegistrationView,ResendInvitationEmailView,update_user_info,update_user_detail,ActivateMemberView, ActivateNewMemberView, PasswordResetRequestView, PasswordResetConfirmView, NewCategoryViewSet,LocationInBound2,UserLastGoalTimeView,TeamEntriesView,update_entry_status,get_events,get_zekken_numbers,get_team_info,get_checkins,update_checkins,export_excel,debug_urls + from django.urls import path, include from knox import views as knox_views @@ -51,7 +52,7 @@ router.register(r'newevent2', views.NewEvent2ViewSet) # GET /api/teams//members-with-user/: 特定のチームの全メンバーとそのユーザー情報を取得 - +app_name = 'rog' # 名前空間を追加 urlpatterns = router.urls @@ -113,11 +114,13 @@ urlpatterns += [ # for Supervisor Web app - path('api/events/', get_events, name='get_events'), - path('api/zekken_numbers//', get_zekken_numbers, name='get_zekken_numbers'), - path('api/team_info//', get_team_info, name='get_team_info'), - path('api/checkins//', get_checkins, name='get_checkins'), - path('api/update_checkins/', update_checkins, name='update_checkins'), - path('api/export_excel//', export_excel, name='export_excel'), + path('events/', views.get_events, name='get_events'), + path('debug/urls/', views.debug_urls, name='debug_urls'), + path('zekken_numbers//', views.get_zekken_numbers, name='get_zekken_numbers'), + path('team_info//', views.get_team_info, name='get_team_info'), + path('checkins///', views.get_checkins, name='get_checkins'), + path('update_checkins/', views.update_checkins, name='update_checkins'), + path('export_excel///', views.export_excel, name='export_excel'), # for Supervisor Web app + path('test/', views.test_api, name='test_api'), ] diff --git a/rog/views.py b/rog/views.py index b25d609..d58c5b7 100644 --- a/rog/views.py +++ b/rog/views.py @@ -30,7 +30,7 @@ from .permissions import IsTeamOwner,IsTeamOwnerOrMember 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,NewEvent2, Team, Category, NewCategory,Entry, Member, TempUser,EntryMember +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, GpsCheckin from rest_framework import viewsets from .serializers import LocationSerializer, Location_lineSerializer, Location_polygonSerializer, JPN_main_perfSerializer, LocationCatSerializer, UserSerializer, LoginUserSerializer, UseractionsSerializer, UserDestinationSerializer, GifuAreaSerializer, LocationEventNameSerializer, RogUserSerializer, UserTracksSerializer, ChangePasswordSerializer, GolaImageSerializer, CheckinImageSerializer, RegistrationSerializer, MemberWithUserSerializer,TempUserRegistrationSerializer, PasswordResetRequestSerializer, PasswordResetConfirmSerializer from knox.models import AuthToken @@ -52,7 +52,6 @@ from django.db.models import Q from rest_framework import permissions from rest_framework.views import APIView -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 @@ -63,7 +62,7 @@ from django.utils.decorators import method_decorator from django.utils.encoding import force_str import logging -from datetime import datetime +from datetime import datetime,timedelta from django.contrib.gis.measure import D from django.contrib.gis.geos import Point @@ -88,6 +87,8 @@ from django.core.exceptions import ValidationError import xlsxwriter from io import BytesIO +from django.urls import get_resolver + logger = logging.getLogger(__name__) @api_view(['PATCH']) @@ -2316,14 +2317,38 @@ class UserLastGoalTimeView(APIView): # ----- for Supervisor ----- @api_view(['GET']) -def get_events(request): - events = NewEvent2.objects.filter(public=True) - return Response([{ - 'code': event.event_name, - 'name': event.event_name - } for event in events]) +def debug_urls(request): + """デバッグ用:利用可能なURLパターンを表示""" + resolver = get_resolver() + urls = [] + for pattern in resolver.url_patterns: + try: + urls.append(str(pattern.pattern)) + except: + urls.append(str(pattern)) + return JsonResponse({'urls': urls}) @api_view(['GET']) +def get_events(request): + logger.debug(f"get_events was called. Path: {request.path}") + try: + events = NewEvent2.objects.filter(public=True) + logger.debug(f"Found {events.count()} events") # デバッグ用ログ + return Response([{ + 'code': event.event_name, + 'name': event.event_name, + 'start_datetime': event.start_datetime, + 'end_datetime': event.end_datetime, + } for event in events]) + logger.debug(f"Returning data: {data}") # デバッグ用ログ + return JsonResponse(data, safe=False) + except Exception as e: + print(f"Error in get_events: {str(e)}") # デバッグ用 + return Response( + {"error": "Failed to retrieve events"}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR + ) +@api_view(['GET']) def get_zekken_numbers(request, event_code): entries = Entry.objects.filter( event__event_name=event_code, @@ -2333,31 +2358,101 @@ def get_zekken_numbers(request, event_code): @api_view(['GET']) def get_team_info(request, zekken_number): - entry = Entry.objects.select_related('team').get(zekken_number=zekken_number) + entry = Entry.objects.select_related('team','event').get(zekken_number=zekken_number) members = Member.objects.filter(team=entry.team) + # チームのゴール時間を取得 + goal_record = GoalImages.objects.filter( + team_name=entry.team.team_name, + event_code=entry.event.event_name + ).order_by('-goaltime').first() + return Response({ 'team_name': entry.team.team_name, 'members': ', '.join([f"{m.lastname} {m.firstname}" for m in members]), - 'start_time': entry.start_time.strftime('%Y-%m-%d %H:%M:%S') if entry.start_time else None, - 'goal_time': entry.goal_time.strftime('%Y-%m-%d %H:%M:%S') if entry.goal_time else None, - 'late_points': entry.late_point or 0 + 'event_code': entry.event.event_name, + 'start_datetime': entry.event.start_datetime, + 'end_datetime': goal_record.goaltime if goal_record else None }) + def create(self, request, *args, **kwargs): + logger.info(f"Attempting to create new member for team: {self.kwargs['team_id']}") + logger.debug(f"Received data: {request.data}") + + team = Team.objects.get(id=self.kwargs['team_id']) + @api_view(['GET']) -def get_checkins(request, zekken_number): - checkins = GpsCheckin.objects.filter( - zekken_number=zekken_number - ).order_by('path_order') - return Response([{ - 'id': c.id, - 'path_order': c.path_order, - 'cp_number': c.cp_number, - 'create_at': c.create_at, - 'validate_location': c.validate_location, - 'points': c.points, - 'buy_flag': c.buy_flag - } for c in checkins]) +def get_checkins(request, *args, **kwargs): +#def get_checkins(request, zekken_number, event_code): + try: + # イベントコードをクエリパラメータから取得 + + zekken_number = kwargs['zekken_number'] + if not zekken_number: + logger.debug(f"=== Zekken_number is required.") + return Response({"error": "zekken_number is required"}, status=400) + + event_code = kwargs['event_code'] + if not event_code: + logger.debug(f"=== event_code is required.") + return Response({"error": "event_code is required"}, status=400) + + # チェックインデータの取得とCP情報の結合 + checkins = GpsCheckin.objects.filter( + zekken_number=zekken_number, + event_code=event_code + ).order_by('path_order') + + # すべてのフィールドを確実に取得できるようにデバッグログを追加 + logger.debug(f"Found {checkins.count()} checkins for zekken_number {zekken_number} and event_code {event_code}") + + data = [] + for c in checkins: + location = Location.objects.filter(cp=c.cp_number,group=event_code).first() + if location: + formatted_time = None + if c.create_at: + try: + # create_atが文字列の場合はdatetimeに変換 + if isinstance(c.create_at, str): + c.create_at = datetime.strptime(c.create_at, '%Y-%m-%d %H:%M:%S') + + # JST(+9時間)に変換して時刻フォーマット + jst_time = c.create_at + timedelta(hours=9) + formatted_time = jst_time.strftime('%H:%M:%S') + + except (ValueError, TypeError, AttributeError) as e: + logger.error(f"Error formatting create_at for checkin {c.id}: {str(e)}") + logger.error(f"Raw create_at value: {c.create_at}") + formatted_time = None + + data.append({ + 'id': c.id, + 'path_order': c.path_order, + 'cp_number': c.cp_number, + 'sub_loc_id': location.sub_loc_id if location else f"#{c.cp_number}", + 'location_name': location.location_name if location else None, + 'create_at': formatted_time, #(c.create_at + timedelta(hours=9)).strftime('%H:%M:%S') if c.create_at else None, + 'validate_location': c.validate_location, + 'points': c.points or 0, + 'buy_flag': c.buy_flag, + 'photos': location.photos if location else None, + 'image_address': c.image_address, + 'receipt_address': c.image_receipt, + 'location_name': location.location_name if location else None, + 'checkin_point': location.checkin_point if location else None, + 'buy_point': location.buy_point + }) + + logger.debug(f"data={data}") + return Response(data) + + except Exception as e: + logger.error(f"Error in get_checkins: {str(e)}", exc_info=True) + return Response( + {"error": f"Failed to retrieve checkins: {str(e)}"}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR + ) @api_view(['POST']) def update_checkins(request): @@ -2418,4 +2513,8 @@ def export_excel(request, zekken_number): # ----- for Supervisor ----- +@api_view(['GET']) +def test_api(request): + logger.debug("Test API endpoint called") + return JsonResponse({"status": "API is working"}) diff --git a/supervisor/html/index.html b/supervisor/html/index.html index cfa6ac3..2d5b64f 100755 --- a/supervisor/html/index.html +++ b/supervisor/html/index.html @@ -13,7 +13,7 @@

スーパーバイザーパネル

-
+
-
- - -
チーム名
@@ -38,6 +34,10 @@
メンバー
+
+ + +
スタート時刻
@@ -46,15 +46,23 @@
ゴール時刻
-
+
+
ゴール時計
+
+
+
+
判定
+
+
+
-
チェックインポイント合計
+
CP合計
-
買い物ポイント合計
+
買物合計
@@ -62,7 +70,7 @@
-
総合計ポイント
+
総合計
@@ -72,11 +80,14 @@ - - - - - + + + + + + + + @@ -87,6 +98,9 @@
+ @@ -98,6 +112,10 @@
順序CP番号チェックイン時刻検証ポイント走行順規定写真撮影写真CP名称通過時刻通過審査買物審査獲得点数