3764 lines
159 KiB
Python
3764 lines
159 KiB
Python
from django.core.exceptions import ValidationError as DjangoValidationError
|
||
from .models import JpnSubPerf # このインポート文をファイルの先頭に追加
|
||
from django.contrib.auth import get_user_model
|
||
User = get_user_model()
|
||
import traceback
|
||
from django.contrib.auth.hashers import make_password
|
||
|
||
import subprocess # subprocessモジュールを追加
|
||
import tempfile # tempfileモジュールを追加
|
||
import shutil # shutilモジュールを追加
|
||
|
||
from django.contrib.auth.tokens import default_token_generator
|
||
from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
|
||
from django.utils.encoding import force_bytes, force_str
|
||
|
||
import requests
|
||
from rest_framework import serializers
|
||
from django.db import IntegrityError
|
||
from django.urls import reverse
|
||
from .utils import S3Bucket, send_verification_email,send_invitation_email,send_team_join_email,send_reset_password_email
|
||
from django.conf import settings
|
||
import uuid
|
||
from rest_framework.exceptions import ValidationError as DRFValidationError
|
||
|
||
from django.db import transaction
|
||
from django.db.models import F,Sum
|
||
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,UserRegistrationSerializer
|
||
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, 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
|
||
|
||
from rest_framework import viewsets, generics, status
|
||
from rest_framework.response import Response
|
||
from rest_framework.parsers import JSONParser, MultiPartParser
|
||
from .serializers import LocationSerializer
|
||
from django.http import JsonResponse
|
||
from rest_framework.permissions import IsAuthenticated , AllowAny
|
||
#
|
||
# AllowAny: 認証なしで誰でもアクセス可能
|
||
# IsAuthenticated: 認証済みユーザーのみアクセス可能
|
||
# IsAdminUser: 管理者のみアクセス可能
|
||
# IsAuthenticatedOrReadOnly: 読み取りは誰でも可能、書き込みは認証済みユーザーのみ可能
|
||
|
||
from django.contrib.gis.db.models import Extent, Union
|
||
|
||
from .serializers import TestSerialiser,NewEventSerializer,NewEvent2Serializer, TeamSerializer, NewCategorySerializer,CategorySerializer, EntrySerializer, MemberSerializer, TempUserSerializer, CustomUserSerializer,EntryMemberSerializer,MemberCreationSerializer,EntryCreationSerializer
|
||
from .models import TestModel
|
||
from django.shortcuts import get_object_or_404
|
||
from django.db.models import Max
|
||
from django.contrib.gis import geos
|
||
from django.db.models import Q
|
||
|
||
from rest_framework import permissions
|
||
from rest_framework.views import APIView
|
||
from rest_framework.decorators import api_view, permission_classes
|
||
from rest_framework.parsers import JSONParser, MultiPartParser
|
||
from django.views.decorators.csrf import csrf_exempt
|
||
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
|
||
from datetime import datetime,timedelta
|
||
|
||
from django.contrib.gis.measure import D
|
||
from django.contrib.gis.geos import Point
|
||
from django.contrib.gis.db.models.functions import Distance
|
||
|
||
from django.utils.dateparse import parse_date
|
||
from django.utils import timezone
|
||
|
||
import csv
|
||
import io
|
||
from django.contrib import admin
|
||
from django.shortcuts import render
|
||
from django.http import HttpResponseRedirect
|
||
from django.urls import path
|
||
from django import forms
|
||
from .models import NewEvent2, CustomUser, Team, NewCategory, Entry, Member, TempUser
|
||
from django.core.mail import send_mail
|
||
from django.conf import settings
|
||
from django.db import transaction
|
||
from django.core.exceptions import ValidationError
|
||
|
||
import xlsxwriter
|
||
from io import BytesIO
|
||
|
||
from django.urls import get_resolver
|
||
import os
|
||
import json
|
||
from django.http import HttpResponse
|
||
from sumaexcel import SumasenExcel
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
@api_view(['PATCH'])
|
||
@permission_classes([IsAuthenticated])
|
||
def update_entry_status(request, entry_id):
|
||
try:
|
||
entry = Entry.objects.get(id=entry_id)
|
||
except Entry.DoesNotExist:
|
||
return Response({"error": "Entry not found"}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
# エントリーの所有者またはチームのメンバーのみが更新可能
|
||
if entry.owner != request.user and not entry.team.members.filter(user=request.user).exists():
|
||
return Response({"error": "You don't have permission to update this entry"}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
hasParticipated = request.data.get('hasParticipated')
|
||
hasGoaled = request.data.get('hasGoaled')
|
||
|
||
if hasParticipated is not None:
|
||
entry.hasParticipated = hasParticipated
|
||
if hasGoaled is not None:
|
||
entry.hasGoaled = hasGoaled
|
||
|
||
entry.save()
|
||
serializer = EntrySerializer(entry)
|
||
return Response(serializer.data)
|
||
|
||
|
||
def process_csv_upload(csv_file, event):
|
||
decoded_file = csv_file.read().decode('utf-8')
|
||
io_string = io.StringIO(decoded_file)
|
||
reader = csv.reader(io_string)
|
||
next(reader) # ヘッダーをスキップ
|
||
|
||
for row in reader:
|
||
try:
|
||
owner_email, owner_name, owner_grade, owner_gender, team_name, category_name, *member_data = row
|
||
# ここでデータを処理し、必要なオブジェクトを作成します
|
||
# 例: create_or_update_team(owner_email, owner_name, owner_grade, owner_gender, team_name, category_name, event)
|
||
# 例: process_members(member_data, team)
|
||
except ValidationError as e:
|
||
# エラーハンドリング
|
||
pass
|
||
|
||
|
||
class CSVUploadForm(forms.Form):
|
||
event = forms.ModelChoiceField(queryset=NewEvent2.objects.all())
|
||
csv_file = forms.FileField()
|
||
|
||
class NewEvent2Admin(admin.ModelAdmin):
|
||
list_display = ['event_name', 'start_datetime', 'end_datetime', 'deadlineDateTime','public']
|
||
|
||
def get_urls(self):
|
||
urls = super().get_urls()
|
||
my_urls = [
|
||
path('csv-upload/', self.admin_site.admin_view(self.csv_upload_view), name='csv-upload'),
|
||
]
|
||
return my_urls + urls
|
||
|
||
def csv_upload_view(self, request):
|
||
if request.method == 'POST':
|
||
form = CSVUploadForm(request.POST, request.FILES)
|
||
if form.is_valid():
|
||
event = form.cleaned_data['event']
|
||
csv_file = request.FILES['csv_file']
|
||
csv_data = csv_file.read().decode('utf-8')
|
||
csv_reader = csv.reader(io.StringIO(csv_data))
|
||
next(csv_reader) # ヘッダーをスキップ
|
||
|
||
for row in csv_reader:
|
||
self.process_team(event, row)
|
||
|
||
self.message_user(request, "CSV file has been processed successfully.")
|
||
return HttpResponseRedirect("..")
|
||
else:
|
||
form = CSVUploadForm()
|
||
|
||
context = {
|
||
'form': form,
|
||
'title': 'Upload CSV file',
|
||
}
|
||
return render(request, 'admin/csv_upload.html', context)
|
||
|
||
@transaction.atomic
|
||
def process_team(self, event, row):
|
||
owner_data = row[:4]
|
||
team_name = row[4]
|
||
category_name = row[5]
|
||
members_data = row[6:]
|
||
|
||
owner = self.create_or_get_user(*owner_data)
|
||
category = NewCategory.objects.get(category_name=category_name)
|
||
team, _ = Team.objects.get_or_create(owner=owner, team_name=team_name, category=category)
|
||
|
||
entry, _ = Entry.objects.get_or_create(
|
||
team=team,
|
||
event=event,
|
||
category=category,
|
||
owner=owner,
|
||
date=event.start_datetime.date()
|
||
)
|
||
|
||
for i in range(0, len(members_data), 4):
|
||
member_data = members_data[i:i+4]
|
||
if member_data[0]: # メールアドレスがある場合のみ処理
|
||
member = self.create_or_get_user(*member_data)
|
||
Member.objects.get_or_create(team=team, user=member)
|
||
|
||
def create_or_get_user(self, email, name, grade, gender):
|
||
is_dummy = False
|
||
if not email:
|
||
email = f"dummy_{uuid.uuid4()}@example.com"
|
||
is_dummy = True
|
||
|
||
user, created = CustomUser.objects.get_or_create(email=email)
|
||
if created or is_dummy:
|
||
user.firstname, user.lastname = name.split(' ', 1) if ' ' in name else (name, '')
|
||
user.grade = grade
|
||
user.gender = gender
|
||
user.is_active = not is_dummy
|
||
user.save()
|
||
|
||
if not is_dummy:
|
||
self.send_invitation_email(user)
|
||
|
||
return user
|
||
|
||
def send_invitation_email(self, user):
|
||
subject = 'Invitation to join the event'
|
||
message = f'Please click the following link to activate your account: {settings.SITE_URL}/activate/{user.id}/'
|
||
send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [user.email])
|
||
|
||
|
||
class LocationViewSet(viewsets.ModelViewSet):
|
||
queryset=Location.objects.all()
|
||
serializer_class=LocationSerializer
|
||
filter_fields=["prefecture", "location_name"]
|
||
|
||
def get_queryset(self):
|
||
queryset = Location.objects.all()
|
||
logger.info("=== Location API Called ===")
|
||
|
||
# リクエストパラメータの確認
|
||
group_filter = self.request.query_params.get('group__contains')
|
||
logger.info(f"Request params: {dict(self.request.query_params)}")
|
||
logger.info(f"Group filter: {group_filter}")
|
||
|
||
if group_filter:
|
||
# フィルタ適用前のデータ数
|
||
total_count = queryset.count()
|
||
logger.info(f"Total locations before filter: {total_count}")
|
||
|
||
# フィルタの適用
|
||
queryset = queryset.filter(group__contains=group_filter)
|
||
|
||
# フィルタ適用後のデータ数
|
||
filtered_count = queryset.count()
|
||
logger.info(f"Filtered locations count: {filtered_count}")
|
||
|
||
# フィルタされたデータのサンプル(最初の5件)
|
||
sample_data = queryset[:5]
|
||
logger.info("Sample of filtered data:")
|
||
for loc in sample_data:
|
||
logger.info(f"ID: {loc.id}, Name: {loc.location_name}, Group: {loc.group}")
|
||
|
||
return queryset
|
||
|
||
def list(self, request, *args, **kwargs):
|
||
try:
|
||
response = super().list(request, *args, **kwargs)
|
||
logger.info(f"Response data count: {len(response.data['features'])}")
|
||
return response
|
||
except Exception as e:
|
||
logger.error(f"Error in list method: {str(e)}", exc_info=True)
|
||
raise
|
||
|
||
class Location_lineViewSet(viewsets.ModelViewSet):
|
||
queryset=Location_line.objects.all()
|
||
serializer_class=Location_lineSerializer
|
||
|
||
|
||
class Location_polygonViewSet(viewsets.ModelViewSet):
|
||
queryset=Location_polygon.objects.all()
|
||
serializer_class=Location_polygonSerializer
|
||
|
||
|
||
class Jpn_Main_PerfViewSet(viewsets.ModelViewSet):
|
||
queryset=JpnAdminMainPerf.objects.filter(id=9)
|
||
serializer_class=JPN_main_perfSerializer
|
||
filter_fields = ["adm1_ja"]
|
||
|
||
|
||
|
||
class UserTracksViewSet(viewsets.ModelViewSet):
|
||
queryset = UserTracks.objects.all()
|
||
serializer_class = UserTracksSerializer
|
||
|
||
|
||
@api_view(['PUT'])
|
||
@permission_classes([IsAuthenticated])
|
||
def update_user_info(request, user_id):
|
||
try:
|
||
user = CustomUser.objects.get(id=user_id)
|
||
except CustomUser.DoesNotExist:
|
||
return Response({"error": "User not found"}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
if request.user.id != user_id:
|
||
return Response({"error": "You don't have permission to update this user's information"}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
data = request.data
|
||
logger.debug(f"Received data for update: {data}")
|
||
|
||
# CustomUserの更新可能なフィールドを指定
|
||
updateable_fields = ['zekken_number', 'event_code', 'team_name', 'group']
|
||
|
||
#for field in updateable_fields:
|
||
# if field in data:
|
||
# setattr(user, field, data[field])
|
||
|
||
updated_fields = []
|
||
for field in updateable_fields:
|
||
if field in data:
|
||
old_value = getattr(user, field)
|
||
setattr(user, field, data[field])
|
||
new_value = getattr(user, field)
|
||
if old_value != new_value:
|
||
updated_fields.append(f"{field}: {old_value} -> {new_value}")
|
||
|
||
logger.debug(f"Fields to be updated: {updated_fields}")
|
||
|
||
try:
|
||
user.save()
|
||
|
||
logger.info(f"User {user_id} updated. Changed fields: {', '.join(updated_fields)}")
|
||
|
||
serializer = CustomUserSerializer(user)
|
||
|
||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||
except Exception as e:
|
||
logger.error(f"Error updating user {user_id}: {str(e)}")
|
||
return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
@api_view(['PUT'])
|
||
@permission_classes([IsAuthenticated])
|
||
def update_user_detail(request, user_id):
|
||
try:
|
||
user = CustomUser.objects.get(id=user_id)
|
||
except CustomUser.DoesNotExist:
|
||
return Response({"error": "User not found"}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
if request.user.id != user_id:
|
||
return Response({"error": "You don't have permission to update this user's information"}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
data = request.data
|
||
logger.debug(f"Received data for update: {data}")
|
||
|
||
# CustomUserの更新可能なフィールドを指定
|
||
updateable_fields = ['firstname', 'lastname', 'date_of_birth', 'female']
|
||
|
||
#for field in updateable_fields:
|
||
# if field in data:
|
||
# setattr(user, field, data[field])
|
||
|
||
updated_fields = []
|
||
for field in updateable_fields:
|
||
if field in data:
|
||
old_value = getattr(user, field)
|
||
setattr(user, field, data[field])
|
||
new_value = getattr(user, field)
|
||
if old_value != new_value:
|
||
logger.debug(f"{field}: {old_value} -> {new_value}")
|
||
updated_fields.append(f"{field}: {old_value} -> {new_value}")
|
||
|
||
logger.debug(f"Fields to be updated: {updated_fields}")
|
||
|
||
try:
|
||
user.save()
|
||
|
||
logger.info(f"User {user_id} updated. Changed fields: {', '.join(updated_fields)}")
|
||
|
||
# 更新されたUserデータを使用して関連するMemberのデータを更新
|
||
members = Member.objects.filter(user=user)
|
||
for member in members:
|
||
member.is_temporary = False
|
||
member.save()
|
||
|
||
serializer = CustomUserSerializer(user)
|
||
|
||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||
except Exception as e:
|
||
logger.error(f"Error updating user {user_id}: {str(e)}")
|
||
return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
|
||
|
||
def LocationsInPerf(request):
|
||
perfecture = request.GET.get('perf')
|
||
is_rog = request.GET.get('rog')
|
||
cat = request.GET.get('cat')
|
||
grp = request.GET.get('grp')
|
||
perf_geom = JpnAdminMainPerf.objects.get(id=perfecture)
|
||
|
||
if(cat):
|
||
if is_rog:
|
||
if grp:
|
||
locs = Location.objects.filter(~Q(cp=0), geom__within=perf_geom.geom, category=cat, group__contains=grp)
|
||
else:
|
||
locs = Location.objects.filter(~Q(cp=0), geom__within=perf_geom.geom, category=cat)
|
||
else:
|
||
if grp:
|
||
locs = Location.objects.filter(geom__within=perf_geom.geom, category=cat, group__contains=grp, location_name__contains='観光')
|
||
else:
|
||
locs = Location.objects.filter(geom__within=perf_geom.geom, category=cat, location_name__contains='観光')
|
||
else:
|
||
if is_rog:
|
||
if grp:
|
||
locs = Location.objects.filter(~Q(cp=0), geom__within=perf_geom.geom, group__contains=grp)
|
||
else:
|
||
locs = Location.objects.filter(~Q(cp=0), geom__within=perf_geom.geom)
|
||
else:
|
||
if grp:
|
||
locs = Location.objects.filter(geom__within=perf_geom.geom, group__contains=grp, location_name__contains='観光')
|
||
else:
|
||
locs = Location.objects.filter(geom__within=perf_geom.geom, location_name__contains='観光')
|
||
|
||
serializer = LocationSerializer(locs, many=True)
|
||
return JsonResponse(serializer.data, safe=False)
|
||
|
||
def LocationsInSubPerf(request):
|
||
subperfecture = request.GET.get('subperf')
|
||
is_rog = request.GET.get('rog')
|
||
cat = request.GET.get('cat')
|
||
grp = request.GET.get('grp')
|
||
perf_geom = JpnSubPerf.objects.get(id=subperfecture)
|
||
|
||
if(cat):
|
||
if is_rog:
|
||
if grp:
|
||
locs = Location.objects.filter(~Q(cp=0), geom__within=perf_geom.geom, category=cat, group__contains=grp)
|
||
else:
|
||
locs = Location.objects.filter(~Q(cp=0), geom__within=perf_geom.geom, category=cat)
|
||
else:
|
||
if grp:
|
||
locs = Location.objects.filter(geom__within=perf_geom.geom, category=cat, group__contains=grp, location_name__contains='観光')
|
||
else:
|
||
locs = Location.objects.filter(geom__within=perf_geom.geom, category=cat, location_name__contains='観光')
|
||
else:
|
||
if is_rog:
|
||
if grp:
|
||
locs = Location.objects.filter(~Q(cp=0), geom__within=perf_geom.geom, group__contains=grp)
|
||
else:
|
||
locs = Location.objects.filter(~Q(cp=0), geom__within=perf_geom.geom)
|
||
else:
|
||
locs = Location.objects.filter(geom__within=perf_geom.geom, location_name__contains='観光')
|
||
serializer = LocationSerializer(locs, many=True)
|
||
return JsonResponse(serializer.data, safe=False)
|
||
|
||
# この関数LocationInBoundは、地理的な範囲内にある特定の条件を満たす位置情報を検索し、結果をJSON形式で返すものです。主なロジックは以下の通りです:
|
||
#
|
||
# 1.リクエストパラメータの取得:
|
||
# 4つの緯度経度ペア(lat1/lon1からlat4/lon4)
|
||
# カテゴリ(cat)
|
||
# グループ(grp)
|
||
# ROG(Region of Gaze)フラグ(is_rog)
|
||
#
|
||
# 2. 境界ポリゴンの作成:
|
||
# 4つの緯度経度ペアが全て提供された場合、それらを使用してジオメトリポリゴンを作成します。
|
||
#
|
||
# 3.位置情報のフィルタリング:
|
||
# 基本的に、ポリゴン内(geom__within=pl)にある位置情報を検索します。
|
||
# イベント名がない(event_name__isnull=True)位置情報のみを対象とします。
|
||
# カテゴリ、グループ、ROGフラグの有無に応じて、さらにフィルタリングを行います:
|
||
# カテゴリが指定された場合、そのカテゴリに一致する位置情報のみを検索します。
|
||
# ROGフラグがある場合、cp(おそらくcheck point)が0でない位置情報を検索します。
|
||
# ROGフラグがない場合、location_nameに'観光'を含む位置情報のみを検索します。
|
||
# グループが指定された場合、そのグループを含む位置情報のみを検索します。
|
||
#
|
||
# 4.結果の返却:
|
||
# 検索結果が120件を超える場合、"too_many_points"フラグを立てたJSONレスポンスを返します。
|
||
# それ以外の場合、LocationSerializerを使用して位置情報をシリアライズし、JSONレスポンスとして返します。
|
||
#
|
||
# 5.エラーハンドリング:
|
||
# 必要な緯度経度パラメータが不足している場合、空のJSONオブジェクトを返します。
|
||
#
|
||
# この関数は、主に観光関連の位置情報を特定の地理的範囲内で検索し、様々な条件でフィルタリングすることができます。ROGフラグの有無によって検索条件が変わるのが特徴的です。
|
||
#
|
||
|
||
def LocationInBound(request):
|
||
logger.debug(f"Received request parameters: {request.GET}")
|
||
|
||
lat = float(request.GET.get('la1'))
|
||
lon = float(request.GET.get('ln1'))
|
||
cat = request.GET.get('cat')
|
||
grp = request.GET.get('grp')
|
||
is_rog = request.GET.get('rog')
|
||
|
||
logger.debug(f"Parsed parameters: lat={lat}, lon={lon}, "
|
||
f"cat={cat}, grp={grp}, is_rog={is_rog}")
|
||
|
||
if lat is not None and lon is not None:
|
||
current_point = Point(lon, lat, srid=4326)
|
||
|
||
# 10km四方の領域を指定
|
||
distance_from_point = 40000 # 中心から10km(半径)
|
||
|
||
base_query = Location.objects.filter(
|
||
geom__distance_lte=(current_point, D(m=distance_from_point))
|
||
).annotate(distance=Distance('geom', current_point)).order_by('distance')
|
||
|
||
#if cat:
|
||
# base_query = base_query.filter(category=cat)
|
||
|
||
#if is_rog:
|
||
#base_query = base_query.filter(~Q(cp=0))
|
||
#else:
|
||
# base_query = base_query.filter(location_name__contains='観光')
|
||
|
||
if grp:
|
||
base_query = base_query.filter(group__contains=grp)
|
||
|
||
logger.debug(f"Final query: {base_query.query}")
|
||
|
||
locs = base_query
|
||
|
||
logger.debug(f"Number of locations found: {len(locs)}")
|
||
|
||
print(f"====== {len(locs)} check points is loaded.. ======")
|
||
|
||
if len(locs) > 200:
|
||
return JsonResponse({"too_many_points": True}, safe=False, status=500)
|
||
else:
|
||
serializer = LocationSerializer(locs, many=True)
|
||
return JsonResponse(serializer.data, safe=False)
|
||
else:
|
||
return JsonResponse({"error": "Latitude and longitude are required"}, safe=False, status=400)
|
||
|
||
def LocationInBound2(request):
|
||
logger.debug(f"Received request parameters: {request.GET}")
|
||
|
||
lat = float(request.GET.get('la1'))
|
||
lon = float(request.GET.get('ln1'))
|
||
cat = request.GET.get('cat')
|
||
grp = request.GET.get('grp')
|
||
is_rog = request.GET.get('rog')
|
||
|
||
logger.debug(f"Parsed parameters: lat={lat}, lon={lon}, "
|
||
f"cat={cat}, grp={grp}, is_rog={is_rog}")
|
||
|
||
if lat is not None and lon is not None:
|
||
if grp:
|
||
# grpがある場合、最初に絞り込む
|
||
base_query = Location.objects.filter(group__contains=grp)
|
||
else:
|
||
current_point = Point(lon, lat, srid=4326)
|
||
# 10km四方の領域を指定
|
||
distance_from_point = 40000 # 中心から10km(半径)
|
||
|
||
base_query = Location.objects.filter(
|
||
geom__distance_lte=(current_point, D(m=distance_from_point))
|
||
).annotate(distance=Distance('geom', current_point)).order_by('distance')
|
||
|
||
if grp:
|
||
base_query = base_query.filter(group__contains=grp)
|
||
|
||
logger.debug(f"Final query: {base_query.query}")
|
||
locs = base_query
|
||
logger.debug(f"Number of locations found: {len(locs)}")
|
||
print(f"====== {len(locs)} check points is loaded.. ======")
|
||
|
||
if len(locs) > 200:
|
||
return JsonResponse({"too_many_points": True}, safe=False, status=500)
|
||
else:
|
||
serializer = LocationSerializer(locs, many=True)
|
||
return JsonResponse(serializer.data, safe=False)
|
||
|
||
else:
|
||
return JsonResponse({"error": "Latitude and longitude are required"}, safe=False, status=400)
|
||
|
||
|
||
|
||
def LocationInBound_orig(request):
|
||
logger.debug(f"Received request parameters: {request.GET}")
|
||
|
||
lat1 = float(request.GET.get('la1'))
|
||
lon1 = float(request.GET.get('ln1'))
|
||
lat2 = float(request.GET.get('la2'))
|
||
lon2 = float(request.GET.get('ln2'))
|
||
lat3 = float(request.GET.get('la3'))
|
||
lon3 = float(request.GET.get('ln3'))
|
||
lat4 = float(request.GET.get('la4'))
|
||
lon4 = float(request.GET.get('ln4'))
|
||
cat = request.GET.get('cat')
|
||
grp = request.GET.get('grp')
|
||
|
||
is_rog = request.GET.get('rog')
|
||
|
||
logger.debug(f"Parsed parameters: lat1={lat1}, lon1={lon1}, lat2={lat2}, lon2={lon2}, "
|
||
f"lat3={lat3}, lon3={lon3}, lat4={lat4}, lon4={lon4}, "
|
||
f"cat={cat}, grp={grp}, is_rog={is_rog}")
|
||
|
||
print(f"===== cat={cat}, grp={grp}, is_rog={is_rog} =======")
|
||
|
||
|
||
if(lat1 != None and lon1 != None and lat2 != None and lon2 != None and lat3 != None and lon3 != None and lat4 != None and lon4 != None):
|
||
pl = geos.Polygon(((lon1, lat1), (lon2, lat2), (lon3, lat3), (lon4, lat4), (lon1, lat1)), srid=4326)
|
||
logger.debug(f"Created polygon: {pl}")
|
||
|
||
base_query = Location.objects.filter(geom__within=pl, event_name__isnull=True)
|
||
|
||
if cat:
|
||
base_query = base_query.filter(category=cat)
|
||
|
||
if is_rog:
|
||
base_query = base_query.filter(~Q(cp=0))
|
||
else:
|
||
base_query = base_query.filter(location_name__contains='観光')
|
||
|
||
if grp:
|
||
base_query = base_query.filter(group__contains=grp)
|
||
|
||
logger.debug(f"Final query: {base_query.query}")
|
||
|
||
locs = base_query
|
||
|
||
logger.debug(f"Number of locations found: {len(locs)}")
|
||
|
||
|
||
print(f"====== {len(locs)} check points is loaded.. ======")
|
||
'''
|
||
if(cat):
|
||
if is_rog:
|
||
if grp:
|
||
locs = Location.objects.filter(~Q(cp=0), geom__within=pl, category=cat, event_name__isnull=True, group__contains=grp)
|
||
else:
|
||
locs = Location.objects.filter(~Q(cp=0), geom__within=pl, category=cat, event_name__isnull=True)
|
||
else:
|
||
if grp:
|
||
locs = Location.objects.filter(geom__within=pl, category=cat, event_name__isnull=True, group__contains=grp, location_name__contains='観光')
|
||
else:
|
||
locs = Location.objects.filter(geom__within=pl, category=cat, event_name__isnull=True, location_name__contains='観光')
|
||
else:
|
||
if is_rog:
|
||
if grp:
|
||
locs = Location.objects.filter(~Q(cp=0), geom__within=pl, event_name__isnull=True, group__contains=grp)
|
||
else:
|
||
locs = Location.objects.filter(~Q(cp=0), geom__within=pl, event_name__isnull=True)
|
||
else:
|
||
if grp:
|
||
locs = Location.objects.filter(geom__within=pl, event_name__isnull=True, group__contains=grp, location_name__contains='観光')
|
||
else:
|
||
locs = Location.objects.filter(geom__within=pl, event_name__isnull=True, location_name__contains='観光')
|
||
'''
|
||
|
||
if len(locs) > 200:
|
||
return JsonResponse({"too_many_points": True}, safe=False, status=500)
|
||
else:
|
||
serializer = LocationSerializer(locs, many=True)
|
||
return JsonResponse(serializer.data, safe=False)
|
||
else:
|
||
return JsonResponse({}, safe=False)
|
||
|
||
|
||
|
||
|
||
def SubInPerf(request):
|
||
prefecture = request.GET.get('perf')
|
||
perf_geom = JpnAdminMainPerf.objects.get(id=prefecture)
|
||
sub = JpnAdminPerf.objects.filter(geom__within=perf_geom.geom)
|
||
serializer = JPN_perfSerializer(sub, many=True)
|
||
return JsonResponse(serializer.data, safe=False)
|
||
|
||
|
||
def SubPerfInMainPerf(request):
|
||
area = request.GET.get('area')
|
||
perf_geom = GifuAreas.objects.get(id=area)
|
||
sub = JpnSubPerf.objects.filter(geom__contained=perf_geom.geom)
|
||
#serializer = JPN_sub_perSerializer
|
||
#sub = JpnAdminPerf.objects.filter(geom__within=perf_geom.geom)
|
||
serializer = JPN_sub_perSerializer(sub, many=True)
|
||
return JsonResponse(serializer.data, safe=False)
|
||
|
||
def GetAllGifuAreas(request):
|
||
prefecture = request.GET.get('perf')
|
||
perf_geom = JpnAdminMainPerf.objects.get(id=prefecture)
|
||
sub = GifuAreas.objects.filter(geom__contained=perf_geom.geom)
|
||
serializer = GifuAreaSerializer(sub, many=True)
|
||
return JsonResponse(serializer.data, safe=False)
|
||
|
||
|
||
def ExtentForMainPerf(request):
|
||
perf_id = request.GET.get('perf')
|
||
perf = JpnAdminMainPerf.objects.get(id=perf_id)
|
||
ext = perf.geom.extent
|
||
# iata = serializers.serialize("json",ext)
|
||
return JsonResponse(ext, safe=False)
|
||
|
||
@api_view(['POST',])
|
||
@permission_classes((IsAuthenticated,))
|
||
@csrf_exempt
|
||
def ExtentForLocations(request):
|
||
user = request.user
|
||
ec = user.event_code
|
||
print(user.event_code)
|
||
logger.debug(f"ExtentForLocations called for user: {user.email}, event_code: {ec}")
|
||
|
||
if not ec:
|
||
logger.warning(f"ExtentForLocations: No event_code for user {user.email}")
|
||
return JsonResponse({"error": "No event code associated with the user"}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
try:
|
||
locs = Location.objects.filter(group__contains=ec).aggregate(Extent('geom'), Union('geom'))
|
||
|
||
if not locs['geom__extent']:
|
||
logger.info(f"No locations found for event_code: {ec}")
|
||
return JsonResponse({"error": "No locations found for the given event code"}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
logger.info(f"Successfully retrieved extent for event_code: {ec}")
|
||
return JsonResponse(locs['geom__extent'], safe=False)
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error in ExtentForLocations: {str(e)}", exc_info=True)
|
||
return JsonResponse({"error": "An error occurred while processing your request"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
#locs = Location.objects.filter(group__contains=ec).aggregate(Extent('geom'), Union('geom'))
|
||
#return JsonResponse(locs['geom__extent'], safe=False)
|
||
|
||
|
||
def ExtentForSubPerf_old(request):
|
||
sub_perf_id = request.GET.get('sub_perf')
|
||
sub_perf = JpnSubPerf.objects.get(id=sub_perf_id)
|
||
ext = sub_perf.geom.extent
|
||
# iata = serializers.serialize("json",ext)
|
||
return JsonResponse(ext, safe=False)
|
||
|
||
def ExtentForSubPerf(request):
|
||
sub_perf_id = request.GET.get('sub_perf')
|
||
if not sub_perf_id:
|
||
return JsonResponse({"error": "sub_perf parameter is required"}, status=400)
|
||
|
||
try:
|
||
sub_perf = JpnSubPerf.objects.get(id=sub_perf_id)
|
||
ext = sub_perf.geom.extent
|
||
return JsonResponse(ext, safe=False)
|
||
except ObjectDoesNotExist:
|
||
return JsonResponse({"error": "Specified sub_perf does not exist"}, status=404)
|
||
except Exception as e:
|
||
return JsonResponse({"error": "Error on ExtentForSubPerf : {e}"}, status=404)
|
||
|
||
|
||
|
||
def CatView(request):
|
||
lat1 = float(request.GET.get('la1'))
|
||
lon1 = float(request.GET.get('ln1'))
|
||
lat2 = float(request.GET.get('la2'))
|
||
lon2 = float(request.GET.get('ln2'))
|
||
lat3 = float(request.GET.get('la3'))
|
||
lon3 = float(request.GET.get('ln3'))
|
||
lat4 = float(request.GET.get('la4'))
|
||
lon4 = float(request.GET.get('ln4'))
|
||
|
||
if(lat1 != None and lon1 != None and lat2 != None and lon2 != None and lat3 != None and lon3 != None and lat4 != None and lon4 != None):
|
||
pl = geos.Polygon(((lon1, lat1), (lon2, lat2), (lon3, lat3), (lon4, lat4), (lon1, lat1)), srid=4326)
|
||
#locs = Location.objects.filter(geom__within=pl)
|
||
c = Location.objects.filter(geom__within=pl).values('category').distinct()
|
||
serializer = LocationCatSerializer(c, many=True)
|
||
return JsonResponse(serializer.data, safe=False)
|
||
else:
|
||
return null
|
||
|
||
|
||
c = Location.objects.filter().values('category').distinct()
|
||
serializer = LocationCatSerializer(c, many=True)
|
||
return JsonResponse(serializer.data, safe=False)
|
||
|
||
|
||
def CatByCity(request):
|
||
city = request.GET.get('city')
|
||
if(city != None):
|
||
cilt_polygon = JpnSubPerf.objects.filter(adm1_ja=city)
|
||
cats = Location.objects.filter(geom__within=cilt_polygon[0].geom).values('category').distinct()
|
||
serializer = LocationCatSerializer(cats, many=True)
|
||
return JsonResponse(serializer.data, safe=False)
|
||
else:
|
||
return None
|
||
|
||
|
||
|
||
class RegistrationAPI(generics.GenericAPIView):
|
||
#serializer_class = CreateUserSerializer
|
||
serializer_class = UserRegistrationSerializer
|
||
|
||
def post(self, request, *args, **kwargs):
|
||
serializer = self.get_serializer(data=request.data)
|
||
serializer.is_valid(raise_exception=True)
|
||
user = serializer.save()
|
||
return Response({
|
||
"user": UserSerializer(user, context=self.get_serializer_context()).data,
|
||
"token": AuthToken.objects.create(user)[1]
|
||
})
|
||
|
||
|
||
class LoginView(APIView):
|
||
def post(self, request):
|
||
email = request.data.get('email')
|
||
password = request.data.get('password')
|
||
|
||
# デバッグコード
|
||
user = CustomUser.objects.filter(email=email).first()
|
||
if user:
|
||
stored_hash = user.password
|
||
print(f"Stored hashed password: {stored_hash}")
|
||
is_valid = check_password(raw_password, stored_hash)
|
||
print(f"Password is valid during login: {is_valid}")
|
||
|
||
user = authenticate(request, username=email, password=raw_password)
|
||
if user:
|
||
token, _ = Token.objects.get_or_create(user=user)
|
||
return Response({'token': token.key}, status=status.HTTP_200_OK)
|
||
else:
|
||
return Response({'error': 'Invalid credentials'}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
|
||
|
||
#user = authenticate(request, username=email, password=password)
|
||
#if user:
|
||
# token, _ = Token.objects.get_or_create(user=user)
|
||
# return Response({'token': token.key}, status=status.HTTP_200_OK)
|
||
#else:
|
||
# return Response({'error': 'Invalid credentials'}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
class PasswordResetView(APIView):
|
||
def post(self, request):
|
||
email = request.data.get('email')
|
||
user = CustomUser.objects.filter(email=email).first()
|
||
if user:
|
||
token = default_token_generator.make_token(user)
|
||
uid = urlsafe_base64_encode(force_bytes(user.pk))
|
||
reset_link = f"https://yourwebsite.com/reset-password/{uid}/{token}/"
|
||
send_mail(
|
||
'Password Reset',
|
||
f'Click here to reset your password: {reset_link}',
|
||
'noreply@yourwebsite.com',
|
||
[email],
|
||
fail_silently=False,
|
||
)
|
||
return Response({'message': 'Password reset email sent'}, status=status.HTTP_200_OK)
|
||
else:
|
||
return Response({'error': 'User not found'}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
|
||
|
||
|
||
class LoginAPI(generics.GenericAPIView):
|
||
|
||
serializer_class = LoginUserSerializer
|
||
|
||
def post(self, request, *args, **kwargs):
|
||
logger.info(f"Login attempt for identifier: {request.data.get('identifier', 'identifier not provided')}")
|
||
logger.debug(f"Request data: {request.data}")
|
||
|
||
serializer = self.get_serializer(data=request.data)
|
||
try:
|
||
serializer.is_valid(raise_exception=True)
|
||
user = serializer.validated_data
|
||
logger.info(f"User {user.email} logged in successfully")
|
||
|
||
# ユーザー情報をシリアライズ
|
||
user_data = UserSerializer(user, context=self.get_serializer_context()).data
|
||
|
||
# 認証トークンを生成
|
||
token = AuthToken.objects.create(user)[1]
|
||
|
||
return Response({
|
||
"user": user_data,
|
||
"token": token
|
||
})
|
||
|
||
except serializers.ValidationError as e:
|
||
logger.error(f"Login failed for identifier {request.data.get('identifier', 'identifier not provided')}: {str(e)}")
|
||
logger.error(f"Serializer errors: {serializer.errors}")
|
||
|
||
error_msg = serializer.errors.get('non_field_errors', ['ログインに失敗しました。'])[0]
|
||
return Response({
|
||
"error": error_msg,
|
||
"details": serializer.errors
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
except Exception as e:
|
||
logger.error(f"Unexpected error during login for identifier {request.data.get('identifier', 'identifier not provided')}: {str(e)}")
|
||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||
|
||
return Response({
|
||
"error": "予期せぬエラーが発生しました。",
|
||
"details": str(e)
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
|
||
def post_old(self, request, *args, **kwargs):
|
||
logger.info(f"Login attempt for user: {request.data.get('email', 'email not provided')}")
|
||
logger.debug(f"Request data: {request.data}")
|
||
|
||
serializer = self.get_serializer(data=request.data)
|
||
try:
|
||
serializer.is_valid(raise_exception=True)
|
||
user = serializer.validated_data
|
||
logger.info(f"User {user.email} logged in successfully")
|
||
|
||
return Response({
|
||
"user": UserSerializer(user, context=self.get_serializer_context()).data,
|
||
"token": AuthToken.objects.create(user)[1]
|
||
})
|
||
except serializers.ValidationError as e:
|
||
logger.error(f"Login failed for user {request.data.get('email', 'email not provided')}: {str(e)}")
|
||
logger.error(f"Serializer errors: {serializer.errors}")
|
||
|
||
error_msg = serializer.errors.get('non_field_errors', ['ログインに失敗しました。'])[0]
|
||
return Response({
|
||
"error": error_msg,
|
||
"details": serializer.errors
|
||
}, status=status.HTTP_400_BAD_REQUEST)
|
||
except Exception as e:
|
||
logger.error(f"Unexpected error during login for user {request.data.get('email', 'email not provided')}: {str(e)}")
|
||
logger.error(f"Traceback: {traceback.format_exc()}")
|
||
|
||
return Response({
|
||
"error": "予期せぬエラーが発生しました。",
|
||
"details": str(e)
|
||
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
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)
|
||
|
||
# User 情報取得
|
||
class UserAPI(generics.RetrieveAPIView):
|
||
permission_classes = [permissions.IsAuthenticated,]
|
||
serializer_class = UserSerializer
|
||
|
||
def get_object(self):
|
||
return self.request.user
|
||
|
||
@api_view(['POST',])
|
||
@permission_classes((IsAuthenticated,))
|
||
@csrf_exempt
|
||
class GoalImageViewSet_old(APIView):
|
||
permissions_classes = [permissions.IsAuthenticated,]
|
||
# parser_classes = [MultiPartParser, JSONParser]
|
||
|
||
def post(self, request, format=None):
|
||
# print(request.data)
|
||
serializer = GolaImageSerializer(data=request.data)
|
||
if serializer.is_valid():
|
||
serializer.save()
|
||
return Response(serializer.data, status=status.HttP_200_OK)
|
||
else:
|
||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
return Response({"ok":"ok"})
|
||
|
||
class GoalImageViewSet(viewsets.ModelViewSet):
|
||
queryset=GoalImages.objects.all()
|
||
serializer_class=GolaImageSerializer
|
||
# parser_classes = (MultiPartParser, JSONParser)
|
||
|
||
def get_queryset(self):
|
||
queryset = GoalImages.objects.all()
|
||
# dist = self.request.GET.get('dist')
|
||
# if dist != None :
|
||
# queryset = Incident.objects.filter(entity=dist, is_approved=True)
|
||
# else:
|
||
# queryset = Incident.objects.filter(is_approved=True)
|
||
return queryset
|
||
|
||
class CheckinImageViewSet(viewsets.ModelViewSet):
|
||
queryset=CheckinImages.objects.all()
|
||
serializer_class=CheckinImageSerializer
|
||
# parser_classes = (MultiPartParser, JSONParser)
|
||
|
||
def get_queryset(self):
|
||
queryset = CheckinImages.objects.all()
|
||
# dist = self.request.GET.get('dist')
|
||
# if dist != None :
|
||
# queryset = Incident.objects.filter(entity=dist, is_approved=True)
|
||
# else:
|
||
# queryset = Incident.objects.filter(is_approved=True)
|
||
return queryset
|
||
|
||
|
||
class RetrieveUserView(generics.RetrieveAPIView):
|
||
queryset = CustomUser.objects.all()
|
||
serializer_class = UserSerializer
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def get_object(self):
|
||
return self.request.user
|
||
|
||
|
||
def userDetials(request):
|
||
user_id = request.GET.get('user_id')
|
||
user = CustomUser.objects.get(id=user_id)
|
||
rogUser = RogUser.objects.filter(user=user)
|
||
serializer = RogUserSerializer(rogUser, many=True)
|
||
return JsonResponse(serializer.data, safe=False)
|
||
|
||
@api_view(['GET'])
|
||
@permission_classes((IsAuthenticated, ))
|
||
def DeleteAccount(request):
|
||
usr = request.user;
|
||
#print("user is" + usr)
|
||
if(usr):
|
||
#usr.delete()
|
||
usr.email = usr.email + "_res" + str(uuid.uuid4())
|
||
usr.save();
|
||
AuthToken.objects.filter(user=usr).delete()
|
||
return Response({"result":"user deleted"})
|
||
return Response({"result":"user not found"})
|
||
|
||
'''
|
||
def send_verification_email(self, temp_user):
|
||
subject = '仮登録の確認'
|
||
message = f'以下のリンクをクリックして登録を完了してください:\n{settings.FRONTEND_URL}/verify/{temp_user.verification_code}'
|
||
from_email = settings.DEFAULT_FROM_EMAIL
|
||
recipient_list = [temp_user.email]
|
||
|
||
try:
|
||
send_mail(subject, message, from_email, recipient_list)
|
||
logger.info(f"Verification email sent to {temp_user.email}")
|
||
except Exception as e:
|
||
logger.error(f"Failed to send verification email to {temp_user.email}. Error: {str(e)}")
|
||
'''
|
||
|
||
|
||
def UserActionViewset(request):
|
||
user_id = request.GET.get('user_id')
|
||
location_id = request.GET.get('location_id')
|
||
location = Location.objects.get(location_id=location_id)
|
||
user = CustomUser.objects.get(id=user_id)
|
||
action = Useractions.objects.filter(location=location, user=user)
|
||
serializer = UseractionsSerializer(action, many=True)
|
||
return JsonResponse(serializer.data, safe=False)
|
||
|
||
|
||
def UserMakeActionViewset(request):
|
||
user_id = request.GET.get('user_id')
|
||
location_id = request.GET.get('location_id')
|
||
wanttogo = True if request.GET.get('wanttogo') == "true" else False
|
||
like = True if request.GET.get('like') == "true" else False
|
||
checkin = True if request.GET.get('checkin') == "true" else False
|
||
location = Location.objects.get(location_id=location_id)
|
||
user = CustomUser.objects.get(id=user_id)
|
||
#action = Useractions.objects.filter(location__id=location_id, user__id=user_id)
|
||
rec = Useractions.objects.filter(user=user, location=location)
|
||
if(rec):
|
||
obj = rec.update(wanttogo=wanttogo, like=like, checkin=checkin)
|
||
else:
|
||
obj, created = Useractions.objects.update_or_create(user=user, location=location, wanttogo=wanttogo, like=like, checkin=checkin)
|
||
serializer = UseractionsSerializer(obj, many=False)
|
||
return JsonResponse(serializer.data, safe=False)
|
||
|
||
|
||
def UserDestinations(request):
|
||
user_id = request.GET.get('user_id')
|
||
user = CustomUser.objects.get(id=user_id)
|
||
#action = Useractions.objects.filter(location__id=location_id, user__id=user_id)
|
||
rec = Useractions.objects.filter(user=user, wanttogo=True).order_by('order')
|
||
serializer = UserDestinationSerializer(rec, many=True)
|
||
return JsonResponse(serializer.data, safe=False)
|
||
|
||
|
||
def UpdateOrder(request):
|
||
dir = request.GET.get('dir')
|
||
user_action_id = int(request.GET.get('user_action_id'))
|
||
order = int(request.GET.get('order'))
|
||
aorder = int(request.GET.get('order'))
|
||
oorder = int(request.GET.get('order'))
|
||
if(user_action_id):
|
||
#updated = Useractions.objects.filter(order__gte=order).update(order = F('order')+1)
|
||
#res = Useractions.objects.filter(id=user_action_id).update(order=order)
|
||
index = 0
|
||
if dir == "up":
|
||
for id in Useractions.objects.all().order_by('order').values_list('id', flat=True):
|
||
print(id)
|
||
print("----",user_action_id)
|
||
if index == order :
|
||
index += 1
|
||
print("index increated .....")
|
||
if user_action_id == id:
|
||
Useractions.objects.filter(id=id).update(order=order)
|
||
print("updated .....")
|
||
continue
|
||
|
||
Useractions.objects.filter(id=id).update(order=index)
|
||
index += 1
|
||
else :
|
||
for id in Useractions.objects.all().order_by('order').values_list('id', flat=True):
|
||
print(id)
|
||
print("----",user_action_id)
|
||
if index == order :
|
||
index -= 1
|
||
print("index increated .....")
|
||
if user_action_id == id:
|
||
Useractions.objects.filter(id=id).update(order=order)
|
||
print("updated .....")
|
||
continue
|
||
|
||
Useractions.objects.filter(id=id).update(order=index)
|
||
index += 1
|
||
# for id in Useractions.objects.values_list('order', flat=True):
|
||
# aorder -= 1
|
||
# Useractions.objects.filter(order__lt=id).update(order=aorder)
|
||
# res = Useractions.objects.filter(id=user_action_id).update(order=oorder)
|
||
|
||
return JsonResponse(1, safe=False)
|
||
else:
|
||
return JsonResponse({}, safe=False)
|
||
|
||
|
||
def DeleteDestination(request):
|
||
dest_id = int(request.GET.get('dest_id'))
|
||
print("###### dest ")
|
||
print(dest_id)
|
||
if(dest_id != None):
|
||
Useractions.objects.filter(id=dest_id).delete()
|
||
return JsonResponse({"success":1}, safe=False)
|
||
else:
|
||
return JsonResponse({"success":0}, safe=False)
|
||
|
||
|
||
def CustomAreaLocations(request):
|
||
|
||
cat = request.GET.get('cat')
|
||
name = request.GET.get('name')
|
||
is_rog = request.GET.get('rog')
|
||
grp = request.GET.get('grp')
|
||
|
||
if(cat != None):
|
||
if is_rog:
|
||
if grp:
|
||
locs = Location.objects.filter(~Q(cp=0), event_name__isnull=False, category=cat, event_name=name, group__contains=grp)
|
||
else:
|
||
locs = Location.objects.filter(~Q(cp=0), event_name__isnull=False, category=cat, event_name=name)
|
||
else:
|
||
if grp:
|
||
locs = Location.objects.filter(event_name__isnull=False, category=cat, event_name=name, group__contains=grp, location_name__contains='観光')
|
||
else:
|
||
locs = Location.objects.filter(event_name__isnull=False, category=cat, event_name=name, location_name__contains='観光')
|
||
else:
|
||
if is_rog:
|
||
if grp:
|
||
locs = Location.objects.filter(~Q(cp=0), event_name__isnull=False, event_name=name, group__contains=grp)
|
||
else:
|
||
locs = Location.objects.filter(~Q(cp=0), event_name__isnull=False, event_name=name)
|
||
else:
|
||
if grp:
|
||
locs = Location.objects.filter(event_name__isnull=False, event_name=name, group__contains=grp, location_name__contains='観光')
|
||
else:
|
||
locs = Location.objects.filter(event_name__isnull=False, event_name=name, location_name__contains='観光')
|
||
|
||
serializer = LocationSerializer(locs, many=True)
|
||
return JsonResponse(serializer.data, safe=False)
|
||
|
||
|
||
def CustomAreaNames(request):
|
||
locs = Location.objects.filter(event_name__isnull=False).values('event_name').distinct()
|
||
|
||
serializer = LocationEventNameSerializer(locs, many=True)
|
||
return JsonResponse(serializer.data, safe=False)
|
||
|
||
|
||
class UserActivationView(APIView):
|
||
|
||
def get(self, request, activation_token):
|
||
try:
|
||
temp_user = TempUser.objects.get(verification_code=activation_token)
|
||
user = CustomUser.objects.create(
|
||
email=temp_user.email,
|
||
firstname=temp_user.firstname,
|
||
lastname=temp_user.lastname,
|
||
date_of_birth=temp_user.date_of_birth,
|
||
female=temp_user.female,
|
||
is_active=True
|
||
)
|
||
# Here you might want to add the user to the team they were invited to
|
||
temp_user.delete()
|
||
return Response({"message": "アカウントが正常にアクティベートされました。"}, status=status.HTTP_200_OK)
|
||
except TempUser.DoesNotExist:
|
||
return Response({"error": "無効なアクティベーショントークンです。"}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
'''
|
||
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.
|
||
"""
|
||
serializer_class = ChangePasswordSerializer
|
||
model = CustomUser
|
||
permission_classes = (IsAuthenticated,)
|
||
|
||
def get_object(self, queryset=None):
|
||
obj = self.request.user
|
||
return obj
|
||
|
||
def update(self, request, *args, **kwargs):
|
||
self.object = self.get_object()
|
||
serializer = self.get_serializer(data=request.data)
|
||
|
||
if serializer.is_valid():
|
||
# Check old password
|
||
if not self.object.check_password(serializer.data.get("old_password")):
|
||
return Response({"old_password": ["Wrong password."]}, status=status.HTTP_400_BAD_REQUEST)
|
||
# set_password also hashes the password that the user will get
|
||
self.object.set_password(serializer.data.get("new_password"))
|
||
self.object.save()
|
||
response = {
|
||
'status': 'success',
|
||
'code': status.HTTP_200_OK,
|
||
'message': 'Password updated successfully',
|
||
'data': []
|
||
}
|
||
|
||
return Response(response)
|
||
|
||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
|
||
class TestActionViewSet(viewsets.ModelViewSet):
|
||
serializer_class = TestSerialiser
|
||
queryset = TestModel.objects.all()
|
||
|
||
|
||
def PrivacyView(request):
|
||
return render(request, "rog/privacy.html")
|
||
|
||
|
||
class RegistrationView(APIView):
|
||
@transaction.atomic
|
||
def post(self, request):
|
||
serializer = UserRegistrationSerializer(data=request.data)
|
||
if serializer.is_valid():
|
||
try:
|
||
user = serializer.save()
|
||
logger.info(f"New user registered: {user.email}")
|
||
|
||
# パスワードを取得
|
||
password = serializer.validated_data.pop('password')
|
||
# ユーザーを作成するが、まだ保存しない
|
||
user = serializer.save(commit=False)
|
||
# パスワードを明示的に設定
|
||
user.set_password(password)
|
||
|
||
# ユーザーを保存
|
||
user.save()
|
||
logger.info(f"New user registered: {user.email}")
|
||
return Response({"message": "ユーザー登録が完了しました。"}, status=status.HTTP_201_CREATED)
|
||
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error during user registration: {str(e)}")
|
||
return Response({"error": "ユーザー登録中にエラーが発生しました。"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
else:
|
||
logger.warning(f"Invalid registration data: {serializer.errors}")
|
||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
|
||
|
||
|
||
# Akira
|
||
|
||
class NewEvent2ViewSet(viewsets.ModelViewSet):
|
||
queryset = NewEvent2.objects.all()
|
||
serializer_class = NewEvent2Serializer
|
||
|
||
def get_permissions(self):
|
||
"""
|
||
GETメソッドは認証不要、その他のメソッドは認証必要
|
||
"""
|
||
if self.action in ['list', 'retrieve']:
|
||
permission_classes = [permissions.AllowAny]
|
||
else:
|
||
permission_classes = [permissions.IsAuthenticated]
|
||
return [permission() for permission in permission_classes]
|
||
|
||
class NewEvent2ListView(generics.ListAPIView):
|
||
queryset = NewEvent2.objects.all()
|
||
serializer_class = NewEvent2Serializer
|
||
permission_classes = [permissions.IsAuthenticated]
|
||
|
||
class NewEventViewSet(viewsets.ModelViewSet):
|
||
queryset = NewEvent.objects.all()
|
||
serializer_class = NewEventSerializer
|
||
permission_classes = [permissions.IsAuthenticated]
|
||
|
||
class NewEventListView(generics.ListAPIView):
|
||
queryset = NewEvent.objects.all()
|
||
serializer_class = NewEventSerializer
|
||
permission_classes = [permissions.IsAuthenticated]
|
||
|
||
|
||
|
||
|
||
class TeamViewSet(viewsets.ModelViewSet):
|
||
serializer_class = TeamSerializer
|
||
permission_classes = [permissions.IsAuthenticated, IsTeamOwner]
|
||
|
||
def get_queryset(self):
|
||
return Team.objects.filter(owner=self.request.user)
|
||
|
||
def perform_create(self, serializer):
|
||
logger.info(f"Creating new team for user: {self.request.user.email}")
|
||
team = serializer.save(owner=self.request.user)
|
||
logger.info(f"Team created successfully: {team.id}")
|
||
|
||
def update_related_entries(self, team):
|
||
current_entries = Entry.objects.filter(team=team, is_active=True)
|
||
for entry in current_entries:
|
||
self.update_external_system(entry.zekken_number, team.owner.event_code, team.team_name)
|
||
|
||
def update_external_system(self, zekken_number, event_code, team_name):
|
||
api_url = f"{settings.FRONTEND_URL}/gifuroge/update_team_name"
|
||
headers = {"Content-Type": "application/x-www-form-urlencoded"}
|
||
data = {
|
||
#"zekken_number": zekken_number,
|
||
"new_team_name": team_name,
|
||
#"event_code": event_code,
|
||
}
|
||
try:
|
||
response = requests.post(api_url, headers=headers, data=data)
|
||
response.raise_for_status()
|
||
logger.info(f"External system updated for zekken_number: {zekken_number}")
|
||
return True
|
||
except requests.RequestException as e:
|
||
logger.error(f"Failed to update external system. Error: {str(e)}")
|
||
return False
|
||
|
||
|
||
|
||
# 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() 式の結果を評価
|
||
#
|
||
# team = serializer.save(owner=self.request.user, zekken_number=zekken_number)
|
||
# logger.info(f"Team created successfully: {team.id}")
|
||
#
|
||
# # 外部システムの更新
|
||
# success = self.register_team(
|
||
# team.owner,
|
||
# team.zekken_number,
|
||
# team.owner.event_code,
|
||
# team.team_name,
|
||
# team.category.category_name,
|
||
# team.owner.password
|
||
# )
|
||
# if not success:
|
||
# logger.error("Failed to register external system")
|
||
# raise serializers.ValidationError("外部システムの更新に失敗しました。")
|
||
# logger.info("External system register successfully")
|
||
#
|
||
# def register_team(self, owner,zekken_number,event_code,team_name,category_name,password):
|
||
# logger.info(f"register_team ==> zekken_number={zekken_number},event_code={event_code},team_name={team_name},category_name={category_name},password={password}")
|
||
# api_url = f"{settings.FRONTEND_URL}/gifuroge/register_team"
|
||
# user = owner
|
||
#
|
||
# headers = {
|
||
# "Content-Type": "application/x-www-form-urlencoded"
|
||
# }
|
||
# data = {
|
||
# "zekken_number": zekken_number,
|
||
# "event_code": event_code,
|
||
# "team_name": team_name,
|
||
# "class_name": category_name,
|
||
# "password": password # パスワードの扱いに注意が必要です
|
||
# }
|
||
#
|
||
# try:
|
||
# response = requests.post(api_url,headers=headers,data=data)
|
||
# response.raise_for_status()
|
||
# logger.info(f"Team registered successfully for team {team_name}")
|
||
# return True
|
||
#
|
||
# except requests.RequestException as e:
|
||
# logger.error(f"Failed to register team for entry {entry.id}. Error: {str(e)}")
|
||
# # エラーが発生しても、エントリー自体は作成されています
|
||
# # 必要に応じて、ここでエラーハンドリングを追加できます
|
||
# return False
|
||
#
|
||
# def destroy(self, request, *args, **kwargs):
|
||
# team = self.get_object()
|
||
# if team.members.exists():
|
||
# return Response(
|
||
# {
|
||
# "error": "チームにメンバーが残っているため削除できません。",
|
||
# "error_code": "TEAM_HAS_MEMBERS"
|
||
# },
|
||
# status=status.HTTP_400_BAD_REQUEST
|
||
# )
|
||
#
|
||
# #return Response({"error": "チームにメンバーが残っているため削除できません。"}, status=status.HTTP_400_BAD_REQUEST)
|
||
# return super().destroy(request, *args, **kwargs)
|
||
#
|
||
# def update_external_system(zekken_number, event_code, team_name, class_name, password):
|
||
# api_url = f"{settings.FRONTEND_URL}/gifuroge/update_team_name"
|
||
# headers = {
|
||
# "Content-Type": "application/x-www-form-urlencoded"
|
||
# }
|
||
# data = {
|
||
# "zekken_number": zekken_number,
|
||
# "new_team_name": team_name,
|
||
# "event_code": event_code,
|
||
# }
|
||
# try:
|
||
# response = requests.post(api_url,headers=headers, data=data)
|
||
# response.raise_for_status()
|
||
#
|
||
# return True
|
||
# except requests.RequestException as e:
|
||
# logger.error(f"Failed to update external system. Error: {str(e)}")
|
||
# return False
|
||
#
|
||
# def update(self, request, *args, **kwargs):
|
||
# try:
|
||
# 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)
|
||
# except Exception as e:
|
||
# return Response({"error": "更新に失敗しました。競合が発生した可能性があります。"}, status=status.HTTP_409_CONFLICT)
|
||
#
|
||
#
|
||
## 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)
|
||
#
|
||
# def perform_update(self, serializer):
|
||
# with transaction.atomic():
|
||
# team = serializer.save()
|
||
#
|
||
# # 外部システムの更新
|
||
# success = update_external_system(
|
||
# team.zekken_number,
|
||
# team.owner.event_code,
|
||
# team.team_name,
|
||
# team.category.category_name,
|
||
# team.owner.password
|
||
# )
|
||
# if not success:
|
||
# raise serializers.ValidationError("外部システムの更新に失敗しました。")
|
||
# else:
|
||
# print("岐阜ロゲシステム更新に成功")
|
||
#
|
||
# @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]
|
||
|
||
def get_permissions(self):
|
||
"""
|
||
GETメソッドは認証不要、その他のメソッドは認証必要
|
||
"""
|
||
if self.action in ['list', 'retrieve']:
|
||
permission_classes = [permissions.AllowAny]
|
||
else:
|
||
permission_classes = [permissions.IsAuthenticated]
|
||
return [permission() for permission in permission_classes]
|
||
|
||
|
||
@action(detail=True, methods=['POST'])
|
||
def get_zekken_number(self, request, pk=None):
|
||
try:
|
||
with transaction.atomic():
|
||
category = NewCategory.objects.select_for_update().get(pk=pk)
|
||
category.category_number += 1
|
||
category.save()
|
||
serializer = self.get_serializer(category)
|
||
return Response(serializer.data)
|
||
except NewCategory.DoesNotExist:
|
||
return Response({"error": "Category not found"}, status=status.HTTP_404_NOT_FOUND)
|
||
except Exception as e:
|
||
return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
class NewCategoryListView(generics.ListAPIView):
|
||
queryset = NewCategory.objects.all()
|
||
serializer_class = NewCategorySerializer
|
||
#permission_classes = [IsAuthenticated]
|
||
|
||
def get_permissions(self):
|
||
"""
|
||
GETメソッドは認証不要、その他のメソッドは認証必要
|
||
"""
|
||
if self.request.method == 'GET':
|
||
permission_classes = [permissions.AllowAny]
|
||
else:
|
||
permission_classes = [permissions.IsAuthenticated]
|
||
return [permission() for permission in permission_classes]
|
||
|
||
|
||
class CategoryViewSet(viewsets.ModelViewSet):
|
||
queryset = Category.objects.all()
|
||
serializer_class = CategorySerializer
|
||
permission_classes = [permissions.IsAuthenticated]
|
||
|
||
class CategoryListView(generics.ListAPIView):
|
||
queryset = Category.objects.all()
|
||
serializer_class = CategorySerializer
|
||
permission_classes = [permissions.IsAuthenticated]
|
||
|
||
'''
|
||
def get(self, request):
|
||
categories = Category.objects.all()
|
||
data = []
|
||
for category in categories:
|
||
category_name = force_str(category.category_name)
|
||
data.append({
|
||
'category_name': category_name,
|
||
# その他のフィールド
|
||
})
|
||
return Response(data)
|
||
'''
|
||
|
||
class EntryViewSet(viewsets.ModelViewSet):
|
||
#queryset = Entry.objects.all()
|
||
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 get_queryset(self):
|
||
user = self.request.user
|
||
# ユーザーが所有するチームのIDを取得
|
||
owned_team_ids = Team.objects.filter(owner=user).values_list('id', flat=True)
|
||
# ユーザーがメンバーとして所属するチームのIDを取得
|
||
member_team_ids = Member.objects.filter(user=user).values_list('team_id', flat=True)
|
||
# 両方のチームに関連するエントリーを取得
|
||
return Entry.objects.filter(Q(team__id__in=owned_team_ids) | Q(team__id__in=member_team_ids))
|
||
|
||
@transaction.atomic
|
||
def perform_create(self, serializer):
|
||
try:
|
||
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()
|
||
|
||
team = serializer.validated_data['team']
|
||
event = serializer.validated_data['event']
|
||
event_name = event.event_name # イベント名を取得
|
||
|
||
entry = serializer.save(owner=self.request.user, zekken_number=zekken_number)
|
||
|
||
logger.info(f"team.owner = {team.owner}, event_name = {event_name}")
|
||
logger.info(f"team = {team}")
|
||
|
||
# 外部システムの更新
|
||
success = self.register_team(
|
||
entry.zekken_number,
|
||
event_name,
|
||
team.team_name,
|
||
category.category_name,
|
||
team.owner.password
|
||
)
|
||
if not success:
|
||
logger.error("Failed to register external system")
|
||
raise serializers.ValidationError("外部システムの更新に失敗しました。")
|
||
logger.info("External system registered successfully")
|
||
except Exception as e:
|
||
logger.exception(f"Error creating Entry: {str(e)}")
|
||
|
||
|
||
def create(self, request, *args, **kwargs):
|
||
serializer = self.get_serializer(data=request.data)
|
||
serializer.is_valid(raise_exception=True)
|
||
|
||
# イベントの締め切り日時をチェック
|
||
event = serializer.validated_data['event']
|
||
if event.deadlineDateTime and timezone.now() > event.deadlineDateTime:
|
||
return Response(
|
||
{"error": "締め切りを過ぎているため、エントリーを作成できません。"},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
try:
|
||
self.perform_create(serializer)
|
||
headers = self.get_success_headers(serializer.data)
|
||
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
|
||
except DjangoValidationError as e:
|
||
return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
|
||
def register_team(self, zekken_number, event_code, team_name, category_name, password):
|
||
api_url = f"{settings.FRONTEND_URL}/gifuroge/register_team"
|
||
headers = {"Content-Type": "application/x-www-form-urlencoded"}
|
||
data = {
|
||
"zekken_number": zekken_number,
|
||
"event_code": event_code,
|
||
"team_name": team_name,
|
||
"class_name": category_name,
|
||
"password": password
|
||
}
|
||
logger.info(f"data= {data}");
|
||
try:
|
||
response = requests.post(api_url, headers=headers, data=data)
|
||
response.raise_for_status()
|
||
logger.info(f"Team registered successfully for zekken_number {zekken_number}")
|
||
return True
|
||
except requests.RequestException as e:
|
||
logger.error(f"Failed to register team. Error: {str(e)}")
|
||
return False
|
||
|
||
def update(self, request, *args, **kwargs):
|
||
logger.info(f"Update method called for Entry with ID: {kwargs.get('pk')}")
|
||
logger.debug(f"Request data: {request.data}")
|
||
|
||
partial = kwargs.pop('partial', False)
|
||
instance = self.get_object()
|
||
serializer = self.get_serializer(instance, data=request.data, partial=partial)
|
||
|
||
try:
|
||
serializer.is_valid(raise_exception=True)
|
||
logger.debug(f"Serializer validated data: {serializer.validated_data}")
|
||
except serializers.ValidationError as e:
|
||
logger.error(f"Validation error: {e.detail}")
|
||
return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
try:
|
||
self.perform_update(serializer)
|
||
logger.info(f"Entry updated successfully: {serializer.data}")
|
||
except Exception as e:
|
||
logger.exception(f"Error updating Entry: {str(e)}")
|
||
return Response({"error": "An error occurred while updating the entry."}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
#if getattr(instance, '_prefetched_objects_cache', None):
|
||
# instance._prefetched_objects_cache = {}
|
||
|
||
return Response(serializer.data)
|
||
|
||
|
||
# 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': self.format_errors(e.detail)}, status=status.HTTP_400_BAD_REQUEST)
|
||
# # except IntegrityError:
|
||
# # return Response({'error': '既に登録済みです'}, status=status.HTTP_400_BAD_REQUEST)
|
||
# except IntegrityError as e:
|
||
# return Response({'error': f'データベースエラー: {str(e)}'}, status=status.HTTP_400_BAD_REQUEST)
|
||
#
|
||
# except Exception as e:
|
||
# return Response({'error': f"予期せぬエラーが発生しました: {str(e),'type': str(type(e))}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
#
|
||
# def perform_create(self, serializer):
|
||
# serializer.save(owner=self.request.user)
|
||
#
|
||
#
|
||
# def update(self, request, *args, **kwargs):
|
||
# logger.info(f"Update method called for Entry with ID: {kwargs.get('pk')}")
|
||
# logger.debug(f"Request data: {request.data}")
|
||
#
|
||
# partial = kwargs.pop('partial', False)
|
||
# instance = self.get_object()
|
||
# serializer = self.get_serializer(instance, data=request.data, partial=partial)
|
||
#
|
||
# try:
|
||
# serializer.is_valid(raise_exception=True)
|
||
# logger.debug(f"Serializer validated data: {serializer.validated_data}")
|
||
# except serializers.ValidationError as e:
|
||
# logger.error(f"Validation error: {e.detail}")
|
||
# return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
||
#
|
||
# try:
|
||
# self.perform_update(serializer)
|
||
# logger.info(f"Entry updated successfully: {serializer.data}")
|
||
# except Exception as e:
|
||
# logger.exception(f"Error updating Entry: {str(e)}")
|
||
# return Response({"error": "An error occurred while updating the entry."}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
#
|
||
#
|
||
# 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)
|
||
#
|
||
# def get_error_message(self, exception):
|
||
# if hasattr(exception, 'detail'):
|
||
# if isinstance(exception.detail, dict):
|
||
# return '. '.join([f"{key}: {', '.join(value)}" for key, value in exception.detail.items()])
|
||
# elif isinstance(exception.detail, list):
|
||
# return '. '.join(exception.detail)
|
||
# return str(exception)
|
||
#
|
||
# def format_errors(self, errors):
|
||
# if isinstance(errors, list):
|
||
# return '. '.join(errors)
|
||
# elif isinstance(errors, dict):
|
||
# return '. '.join([f"{key}: {value}" if isinstance(value, str) else f"{key}: {', '.join(value)}" for key, value in errors.items()])
|
||
# else:
|
||
# return str(errors)
|
||
|
||
class TeamEntriesView(generics.ListAPIView):
|
||
serializer_class = EntrySerializer
|
||
permission_classes = [IsAuthenticated, IsTeamOwnerOrMember]
|
||
|
||
def get_queryset(self):
|
||
team_id = self.kwargs['team_id']
|
||
team = Team.objects.get(id=team_id)
|
||
return Entry.objects.filter(team=team)
|
||
|
||
def get_serializer_context(self):
|
||
context = super().get_serializer_context()
|
||
context['team_id'] = self.kwargs['team_id']
|
||
return context
|
||
|
||
|
||
class MemberViewSet(viewsets.ModelViewSet):
|
||
serializer_class = MemberSerializer
|
||
permission_classes = [permissions.IsAuthenticated,IsTeamOwner]
|
||
|
||
def get_serializer_class(self):
|
||
if self.action == 'create':
|
||
return MemberCreationSerializer
|
||
return MemberSerializer
|
||
|
||
def get_queryset(self):
|
||
team_id = self.kwargs['team_id']
|
||
return Member.objects.filter(team_id=team_id)
|
||
|
||
@action(detail=False, methods=['DELETE'])
|
||
def destroy_all(self, request, team_id=None):
|
||
team = Team.objects.get(id=team_id)
|
||
|
||
# チームのオーナーかどうかを確認
|
||
if team.owner != request.user:
|
||
return Response({"error": "チームのオーナーのみがメンバーを一括削除できます。"}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
# 確認パラメータをチェック
|
||
confirm = request.query_params.get('confirm', '').lower()
|
||
if confirm != 'true':
|
||
return Response({"error": "確認パラメータが必要です。'?confirm=true'を追加してください。"}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
try:
|
||
with transaction.atomic():
|
||
# オーナー以外のすべてのメンバーを削除
|
||
#deleted_count = Member.objects.filter(team=team).exclude(user=team.owner).delete()[0]
|
||
deleted_count = Member.objects.filter(team=team).delete()[0]
|
||
|
||
return Response({
|
||
"message": f"{deleted_count}人のメンバーが削除されました。",
|
||
"deleted_count": deleted_count
|
||
}, status=status.HTTP_200_OK)
|
||
|
||
except Exception as e:
|
||
return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
def destroy(self, request, *args, **kwargs):
|
||
instance = self.get_object()
|
||
team = instance.team
|
||
|
||
# チームのオーナーかどうかを確認
|
||
if team.owner != request.user:
|
||
return Response({"error": "チームのオーナーのみがメンバーを削除できます。"}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
# オーナー自身は削除できないようにする
|
||
#if instance.user == team.owner:
|
||
# return Response({"error": "チームのオーナーは削除できません。"}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
self.perform_destroy(instance)
|
||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||
|
||
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'])
|
||
#serializer = self.get_serializer(data=request.data)
|
||
serializer = self.get_serializer(data=request.data, context={'team': team})
|
||
|
||
try:
|
||
serializer.is_valid(raise_exception=True)
|
||
except serializers.ValidationError as e:
|
||
logger.error(f"Validation error: {e.detail}")
|
||
return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
email = serializer.validated_data.get('email', '')
|
||
logger.info(f"Processing member with email: {email}")
|
||
|
||
user_data = {
|
||
'firstname': serializer.validated_data.get('firstname', ''),
|
||
'lastname': serializer.validated_data.get('lastname', ''),
|
||
'date_of_birth': serializer.validated_data.get('date_of_birth'),
|
||
'female': serializer.validated_data.get('female', False),
|
||
}
|
||
|
||
# 自分自身を登録する場合
|
||
if request.user.email == email:
|
||
member, created = Member.objects.get_or_create(user=request.user, team=team)
|
||
if created:
|
||
return Response(MemberSerializer(member).data, status=status.HTTP_201_CREATED)
|
||
else:
|
||
return Response({"message": "You are already a member of this team."}, status=status.HTTP_200_OK)
|
||
|
||
if not email or email.startswith('dummy_'):
|
||
logger.info("Processing as direct registration")
|
||
# 直接登録
|
||
try:
|
||
if email:
|
||
user, created = CustomUser.objects.get_or_create(
|
||
email=email,
|
||
defaults={**user_data, 'is_active':True}
|
||
)
|
||
else:
|
||
# emailが空の場合、一意のダミーメールアドレスを生成
|
||
dummy_email = f"dummy_{uuid.uuid4()}@example.com"
|
||
user = CustomUser.objects.create(email=dummy_email, **user_data)
|
||
|
||
member = Member.objects.create(user=user, team=team)
|
||
logger.info(f"Member created successfully: {member.id}")
|
||
return Response(MemberSerializer(member).data, status=status.HTTP_201_CREATED)
|
||
except Exception as e:
|
||
logger.error(f"Error creating member: {str(e)}")
|
||
return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
#member = serializer.save(team=team)
|
||
#return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||
else:
|
||
logger.info("Processing as temporary registration")
|
||
|
||
try:
|
||
# 仮登録
|
||
existing_user = CustomUser.objects.filter(email=email).first()
|
||
if existing_user:
|
||
logger.info(f"Existing user found: {existing_user.id}")
|
||
|
||
# 既存ユーザーの場合、チーム招待メールを送信
|
||
send_team_join_email(request,request.user,existing_user, team)
|
||
return Response({"message": "Invitation for your team sent to existing user."}, status=status.HTTP_200_OK)
|
||
else:
|
||
logger.info("Inviting new temporary user")
|
||
|
||
try:
|
||
print("新規ユーザー")
|
||
#temp_user = TempUser.objects.create(
|
||
# email=email,
|
||
# **user_data,
|
||
# verification_code=str(uuid.uuid4())
|
||
#)
|
||
send_invitation_email(request.user,request,email, team) #仮登録済みでも確認メールを送る。
|
||
logger.info(f"Invitation email sent to: {email}")
|
||
return Response({"message": "Invitation email sent to the user."}, status=status.HTTP_201_CREATED)
|
||
except Exception as e:
|
||
logger.info(f"Error on Invitation email sent to: {email} : {e}")
|
||
return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
||
except Exception as e:
|
||
logger.error(f"Error in create method: {str(e)}")
|
||
return Response({"error": "An unexpected error occurred."}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||
|
||
|
||
'''
|
||
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 send_activation_email(self, temp_user, team):
|
||
# アクティベーションメール送信のロジックをここに実装
|
||
# 注意: 'user-activation' URLパターンが存在しない場合は、適切なURLを生成する方法を検討する必要があります
|
||
|
||
activation_url = "dummy url" #f"{settings.FRONTEND_URL}/activate/{temp_user.verification_code}"
|
||
|
||
# ここでemailを送信するロジックを実装
|
||
print(f"Activation email would be sent to {temp_user.email} with URL: {activation_url}")
|
||
|
||
def send_invitation_email(self, user, team):
|
||
# チーム招待メール送信のロジック
|
||
#invitation_url = self.request.build_absolute_uri(
|
||
# reverse('team-invite', kwargs={'team_id': team.id, 'user_id': user.id})
|
||
#)
|
||
#subject = f"Invitation to join {team.name}"
|
||
#message = f"You have been invited to join the team {team.name}. Click here to accept: {invitation_url}"
|
||
#from_email = settings.DEFAULT_FROM_EMAIL
|
||
#recipient_list = [user.email]
|
||
|
||
#send_mail(subject, message, from_email, recipient_list)
|
||
|
||
# チーム招待メール送信のロジックをここに実装
|
||
# 注意: 'team-invitation' URLパターンが存在しない場合は、適切なURLを生成する方法を検討する必要があります
|
||
# 例えば、フロントエンドのURLを直接指定する方法など
|
||
invitation_url = "dummy url" #f"{settings.FRONTEND_URL}/team/{team.id}/invite"
|
||
# ここでemailを送信するロジックを実装
|
||
print(f"Invitation email would be sent to {user.email} with URL: {invitation_url}")
|
||
'''
|
||
|
||
|
||
|
||
def perform_create(self, serializer):
|
||
team = Team.objects.get(id=self.kwargs['team_id'])
|
||
serializer.save(team=team)
|
||
|
||
@transaction.atomic
|
||
def update(self, request, *args, **kwargs):
|
||
#partial = kwargs.pop('partial', False)
|
||
instance = self.get_object()
|
||
user = instance.user
|
||
logger.debug(f"Updating user: {user.email}")
|
||
logger.debug(f"Received data: {request.data}")
|
||
|
||
if user.email.startswith('dummy_'):
|
||
logger.debug("User has dummy email, proceeding with update")
|
||
|
||
# 直接データを更新
|
||
user.firstname = request.data.get('firstname', user.firstname)
|
||
user.lastname = request.data.get('lastname', user.lastname)
|
||
|
||
# 日付の処理
|
||
date_of_birth = request.data.get('date_of_birth')
|
||
if date_of_birth:
|
||
try:
|
||
date_of_birth = date_of_birth.translate(str.maketrans("0123456789", "0123456789"))
|
||
parsed_date = parse_date(date_of_birth)
|
||
if parsed_date:
|
||
user.date_of_birth = parsed_date
|
||
else:
|
||
raise ValueError("Invalid date format")
|
||
except ValueError:
|
||
logger.error(f"Invalid date format: {date_of_birth}")
|
||
return Response({"error": "Invalid date format. Use YYYY-MM-DD."}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
user.female = request.data.get('female', user.female)
|
||
|
||
user.save()
|
||
logger.debug(f"User updated: firstname={user.firstname}, lastname={user.lastname}, date_of_birth={user.date_of_birth}, female={user.female}")
|
||
|
||
# Memberインスタンスも更新
|
||
serializer = self.get_serializer(instance, data=request.data, partial=True)
|
||
serializer.is_valid(raise_exception=True)
|
||
self.perform_update(serializer)
|
||
|
||
# 更新後のデータを取得
|
||
updated_instance = self.get_object()
|
||
updated_serializer = self.get_serializer(updated_instance)
|
||
|
||
logger.debug(f"Updated user data: {updated_serializer.data}")
|
||
return Response(updated_serializer.data)
|
||
else:
|
||
logger.debug("User does not have dummy email, update not allowed")
|
||
return Response({"error": "このユーザーの情報は更新できません。"}, status=status.HTTP_403_FORBIDDEN)
|
||
|
||
|
||
def perform_update(self, serializer):
|
||
serializer.save()
|
||
logger.debug("perform_update called")
|
||
|
||
|
||
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 ActivateMemberView(APIView):
|
||
def get(self, request, user_id, team_id):
|
||
user = get_object_or_404(CustomUser, id=user_id)
|
||
team = get_object_or_404(Team, id=team_id)
|
||
|
||
# メンバーオブジェクトを取得または作成
|
||
member, created = Member.objects.get_or_create(user=user, team=team)
|
||
|
||
if member.lastname == "dummy":
|
||
# userデータでmemberデータを更新
|
||
member.lastname = user.lastname
|
||
member.firstname = user.firstname
|
||
member.date_of_birth = user.date_of_birth
|
||
member.female = user.female
|
||
member.save()
|
||
|
||
message = f"{team.team_name}に正常に参加し、メンバー情報が更新されました。アプリのチーム管理で確認できます。"
|
||
else:
|
||
message = f"{team.team_name}に正常に参加しました。"
|
||
|
||
return render(request, 'activation_success.html', {'message': message})
|
||
|
||
|
||
class ActivateNewMemberView(APIView):
|
||
def get(self, request, verification_code, team_id):
|
||
try:
|
||
team = Team.objects.get(id=team_id)
|
||
member, created = Member.objects.get_or_create(team=team, user__email=email)
|
||
if created:
|
||
return Response({'message': 'Member activated successfully'}, status=status.HTTP_201_CREATED)
|
||
else:
|
||
return Response({'message': 'Member already exists'}, status=status.HTTP_200_OK)
|
||
except Team.DoesNotExist:
|
||
return Response({'message': 'Invalid team'}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
'''
|
||
temp_user = get_object_or_404(TempUser, verification_code=verification_code)
|
||
team = get_object_or_404(Team, id=team_id)
|
||
|
||
user = CustomUser.objects.create_user(
|
||
email=temp_user.email,
|
||
password=temp_user.password, # Ensure this is securely handled
|
||
**{k: v for k, v in temp_user.__dict__.items() if k in ['firstname', 'lastname', 'date_of_birth', 'female']}
|
||
)
|
||
|
||
Member.objects.create(user=user, team=team)
|
||
temp_user.delete()
|
||
|
||
message = f"アカウントが作成され、{team.team_name}に参加しました。アプリのチーム管理で確認できます。"
|
||
return render(request, 'activation_success.html', {'message': message})
|
||
'''
|
||
|
||
|
||
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()
|
||
serializer_class = TempUserSerializer
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
# CustomUserViewSetの修正
|
||
class CustomUserViewSet(viewsets.ModelViewSet):
|
||
queryset = CustomUser.objects.all()
|
||
serializer_class = CustomUserSerializer
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def get_queryset(self):
|
||
user = self.request.user
|
||
if user.is_staff:
|
||
return CustomUser.objects.all()
|
||
return CustomUser.objects.filter(id=user.id)
|
||
|
||
|
||
class TeamMembersView(generics.ListAPIView):
|
||
serializer_class = MemberSerializer
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def get_queryset(self):
|
||
team_id = self.kwargs['team_id']
|
||
return Member.objects.filter(team_id=team_id)
|
||
|
||
# ユーザーのエントリーを取得するビュー
|
||
class UserEntriesView(generics.ListAPIView):
|
||
serializer_class = EntrySerializer
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def get_queryset(self):
|
||
user = self.request.user
|
||
return Entry.objects.filter(team__owner=user)
|
||
|
||
# イベントのカテゴリーを取得するビュー
|
||
class EventCategoriesView(generics.ListAPIView):
|
||
serializer_class = NewCategorySerializer
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def get_queryset(self):
|
||
event_id = self.kwargs['event_id']
|
||
return NewCategory.objects.filter(entry__event_id=event_id).distinct()
|
||
|
||
# ユーザー仮登録
|
||
class RegisterView(APIView):
|
||
def post(self, request):
|
||
serializer = TempUserSerializer(data=request.data)
|
||
if serializer.is_valid():
|
||
temp_user = serializer.save()
|
||
verification_code = uuid.uuid4()
|
||
temp_user.verification_code = verification_code
|
||
temp_user.save()
|
||
|
||
verification_url = request.build_absolute_uri(
|
||
reverse('verify-email', kwargs={'verification_code': verification_code})
|
||
)
|
||
send_verification_email(temp_user,verifiction_url)
|
||
#send_mail(
|
||
# 'Verify your email',
|
||
# f'Click the link to verify your email: {verification_url}',
|
||
# settings.DEFAULT_FROM_EMAIL,
|
||
# [temp_user.email],
|
||
# fail_silently=False,
|
||
#)
|
||
return Response({'message': 'Verification email sent'}, status=status.HTTP_201_CREATED)
|
||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
class ResendInvitationEmailView(APIView):
|
||
def post(self, request):
|
||
email = request.data.get('email')
|
||
if not email:
|
||
return Response({"error": "メールアドレスを指定してください。"}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
try:
|
||
temp_user = TempUser.objects.get(email=email)
|
||
verification_url = request.build_absolute_uri(
|
||
reverse('verify-email', kwargs={'verification_code': temp_user.verification_code})
|
||
)
|
||
send_verification_email(temp_user, verification_url)
|
||
logger.info(f"招待メールを再送信しました。Email: {email}")
|
||
return Response({"message": "招待メールを再送信しました。"}, status=status.HTTP_200_OK)
|
||
except ObjectDoesNotExist:
|
||
logger.warning(f"仮登録されていないメールアドレスに対して招待メールの再送信が試みられました。Email: {email}")
|
||
return Response({"error": "指定されたメールアドレスは仮登録されていません。"}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
|
||
|
||
|
||
class TempUserRegistrationView(APIView):
|
||
def post(self, request):
|
||
email = request.data.get('email')
|
||
|
||
# 本登録済みのユーザーチェック
|
||
if CustomUser.objects.filter(email=email).exists():
|
||
logger.warning(f"既に本登録されているメールアドレスでの仮登録が試みられました。Email: {email}")
|
||
return Response({"error": "このメールアドレスは既に本登録されています。"}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# 仮登録済みのユーザーチェック
|
||
try:
|
||
temp_user = TempUser.objects.get(email=email)
|
||
verification_url = request.build_absolute_uri(
|
||
reverse('verify-email', kwargs={'verification_code': temp_user.verification_code})
|
||
)
|
||
send_verification_email(temp_user, verification_url)
|
||
logger.info(f"既に仮登録されているユーザーに招待メールを再送信しました。Email: {email}")
|
||
return Response({"message": "既に仮登録は行われていますが、招待メールを再送信しました。"}, status=status.HTTP_200_OK)
|
||
except TempUser.DoesNotExist:
|
||
# 新規仮登録
|
||
serializer = TempUserRegistrationSerializer(data=request.data)
|
||
if serializer.is_valid():
|
||
# シリアライザのvalidated_dataからパスワードを取得
|
||
password = serializer.validated_data.get('password')
|
||
# パスワードをハッシュ化
|
||
hashed_password = make_password(password)
|
||
# ハッシュ化されたパスワードでTempUserを作成
|
||
temp_user = serializer.save(password=hashed_password)
|
||
|
||
verification_code = uuid.uuid4()
|
||
temp_user.verification_code = verification_code
|
||
temp_user.save()
|
||
|
||
verification_url = request.build_absolute_uri(
|
||
reverse('verify-email', kwargs={'verification_code': verification_code})
|
||
)
|
||
send_verification_email(temp_user, verification_url)
|
||
logger.info(f"新規ユーザーを仮登録し、招待メールを送信しました。Email: {email}")
|
||
return Response({"message": "仮登録が完了しました。招待メールを送信しました。"}, status=status.HTTP_201_CREATED)
|
||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
|
||
|
||
#serializer = TempUserRegistrationSerializer(data=request.data)
|
||
#if serializer.is_valid():
|
||
# temp_user = serializer.save()
|
||
# verification_code = uuid.uuid4()
|
||
# temp_user.verification_code = verification_code
|
||
# temp_user.save()
|
||
# verification_url = request.build_absolute_uri(
|
||
# reverse('verify-email', kwargs={'verification_code': verification_code})
|
||
# )
|
||
# send_verification_email(temp_user,verification_url) #招待メールを送る。
|
||
# return Response({"message": "仮登録が完了しました。招待メールを送信しました。"}, status=status.HTTP_201_CREATED)
|
||
#return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
# アクティベーション
|
||
class VerifyEmailView(APIView):
|
||
def get(self, request, verification_code):
|
||
try:
|
||
temp_user = TempUser.objects.get(verification_code=verification_code)
|
||
if temp_user.is_valid():
|
||
user_data = {
|
||
'email': temp_user.email,
|
||
'is_rogaining':True, # ここでis_rogainingをTrueに設定
|
||
'password':temp_user.password,
|
||
'is_rogaining': temp_user.is_rogaining,
|
||
'zekken_number': temp_user.zekken_number,
|
||
'event_code': temp_user.event_code,
|
||
'team_name': temp_user.team_name,
|
||
'group': temp_user.group,
|
||
'firstname': temp_user.firstname,
|
||
'lastname': temp_user.lastname,
|
||
'date_of_birth': temp_user.date_of_birth,
|
||
'female': temp_user.female,
|
||
}
|
||
|
||
# パスワードを安全にハッシュ化
|
||
#hashed_password = make_password(temp_user.password)
|
||
|
||
try:
|
||
# CustomUserを作成
|
||
user = CustomUser.objects.create(**user_data)
|
||
|
||
#user = CustomUser.objects.create_user(
|
||
# email=user_data['email'],
|
||
# password=temp_user.password, # ハッシュ化されたパスワードを直接使用
|
||
# **{k: v for k, v in user_data.items() if k != 'email'}
|
||
#)
|
||
except ValidationError as e:
|
||
# パスワードのバリデーションエラーなどの処理
|
||
return render(request, 'verification_error.html', {'message': str(e), 'title': 'エラー'})
|
||
|
||
# チームへの追加処理(もし必要なら)
|
||
if hasattr(temp_user, 'team_id'):
|
||
team = Team.objects.get(id=temp_user.team_id)
|
||
Member.objects.create(user=user, team=team)
|
||
message = f"メールアドレスが確認され、アカウントが作成されました。{team.team_name}のメンバーとして登録されています。アプリでログインして、必要な情報を入力してください。"
|
||
else:
|
||
message = "メールアドレスが確認され、アカウントが作成されました。アプリでログインして、必要な情報を入力してください。"
|
||
|
||
temp_user.delete()
|
||
return render(request, 'verification_success.html', {'message': message,'title':"認証成功"})
|
||
else:
|
||
message = "確認リンクの有効期限が切れています。アプリから認証コードの再送信をしてください。"
|
||
return render(request, 'verification_success.html', {'message': message,'title':'有効期限切れ'})
|
||
|
||
except TempUser.DoesNotExist:
|
||
message = "無効な確認コードです。"
|
||
return render(request, 'verification_success.html', {'message': message,'title':'無効な確認コードです'})
|
||
|
||
|
||
class MemberUserDetailView(generics.RetrieveAPIView):
|
||
serializer_class = MemberWithUserSerializer
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def get_queryset(self):
|
||
return Member.objects.select_related('user', 'team')
|
||
|
||
class TeamMembersWithUserView(generics.ListAPIView):
|
||
serializer_class = MemberWithUserSerializer
|
||
permission_classes = [IsAuthenticated]
|
||
|
||
def get_queryset(self):
|
||
team_id = self.kwargs['team_id']
|
||
return Member.objects.filter(team_id=team_id).select_related('user', 'team')
|
||
|
||
|
||
class PasswordResetRequestView(APIView):
|
||
def post(self, request):
|
||
serializer = PasswordResetRequestSerializer(data=request.data)
|
||
if serializer.is_valid():
|
||
email = serializer.validated_data['email']
|
||
user = CustomUser.objects.filter(email=email).first()
|
||
if user:
|
||
token = default_token_generator.make_token(user)
|
||
uid = urlsafe_base64_encode(force_bytes(user.pk))
|
||
reset_link = f"{settings.FRONTEND_URL}/api/reset-password/{uid}/{token}/"
|
||
send_reset_password_email(email,reset_link)
|
||
|
||
return Response({"message": "Password reset email sent"}, status=status.HTTP_200_OK)
|
||
return Response({"message": "User not found"}, status=status.HTTP_404_NOT_FOUND)
|
||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
class PasswordResetConfirmView(APIView):
|
||
def get(self, request, uidb64, token):
|
||
try:
|
||
uid = force_str(urlsafe_base64_decode(uidb64))
|
||
user = CustomUser.objects.get(pk=uid)
|
||
except (TypeError, ValueError, OverflowError, CustomUser.DoesNotExist):
|
||
user = None
|
||
|
||
if user is not None and default_token_generator.check_token(user, token):
|
||
return render(request, 'password_reset.html', {'uid': uidb64, 'token': token})
|
||
else:
|
||
return render(request, 'password_reset_invalid.html')
|
||
|
||
if user is not None and default_token_generator.check_token(user, token):
|
||
return Response({"message": "Token is valid"}, status=status.HTTP_200_OK)
|
||
return Response({"message": "Invalid reset link"}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
|
||
def post(self, request, uidb64, token):
|
||
try:
|
||
uid = force_str(urlsafe_base64_decode(uidb64))
|
||
user = CustomUser.objects.get(pk=uid)
|
||
except (TypeError, ValueError, OverflowError, CustomUser.DoesNotExist):
|
||
return Response({"error": "Invalid reset link"}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
if default_token_generator.check_token(user, token):
|
||
serializer = PasswordResetConfirmSerializer(data=request.data)
|
||
if serializer.is_valid():
|
||
user.set_password(serializer.validated_data['new_password'])
|
||
user.save()
|
||
return Response({"message": "Password has been reset successfully"}, status=status.HTTP_200_OK)
|
||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||
return Response({"error": "Invalid reset link"}, status=status.HTTP_400_BAD_REQUEST)
|
||
|
||
|
||
class UserLastGoalTimeView(APIView):
|
||
def get(self, request, user_id):
|
||
try:
|
||
user = CustomUser.objects.get(id=user_id)
|
||
last_goal = GoalImages.objects.filter(
|
||
user=user
|
||
).aggregate(last_goal_time=Max('goaltime'))
|
||
|
||
if last_goal['last_goal_time']:
|
||
return Response({
|
||
'user_email': user.email,
|
||
'last_goal_time': last_goal['last_goal_time']
|
||
})
|
||
else:
|
||
return Response({
|
||
'message': 'No goal records found for this user'
|
||
}, status=status.HTTP_404_NOT_FOUND)
|
||
except CustomUser.DoesNotExist:
|
||
return Response({
|
||
'message': 'User not found'
|
||
}, status=status.HTTP_404_NOT_FOUND)
|
||
|
||
|
||
# ----- for Supervisor -----
|
||
|
||
@api_view(['GET'])
|
||
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,
|
||
is_active=True
|
||
).order_by('zekken_number')
|
||
return Response([entry.zekken_number for entry in entries])
|
||
|
||
@api_view(['GET'])
|
||
def get_team_info(request, zekken_number):
|
||
entry = Entry.objects.select_related('team','event').get(zekken_number=zekken_number)
|
||
members = Member.objects.filter(team=entry.team)
|
||
|
||
start_datetime = entry.event.start_datetime #イベントの規定スタート時刻
|
||
if entry.event.self_rogaining:
|
||
#get_checkinsの中で、
|
||
# start_datetime = -1(ロゲ開始)のcreate_at
|
||
logger.debug(f"self.rogaining={entry.event.self_rogaining} => start_datetime = -1(ロゲ開始)のcreate_at")
|
||
|
||
# チームの最初のゴール時間を取得
|
||
goal_record = GoalImages.objects.filter(
|
||
team_name=entry.team.team_name,
|
||
event_code=entry.event.event_name
|
||
).order_by('goaltime').first()
|
||
|
||
# Nullチェックを追加してからログ出力
|
||
goalimage_url = None
|
||
goaltime = None
|
||
|
||
if goal_record:
|
||
try:
|
||
goaltime = goal_record.goaltime
|
||
if goal_record.goalimage and hasattr(goal_record.goalimage, 'url'):
|
||
goalimage_url = request.build_absolute_uri(goal_record.goalimage.url)
|
||
logger.info(f"get_team_info record.goalimage_url={goalimage_url}")
|
||
else:
|
||
logger.info("Goal record exists but no image found")
|
||
|
||
except ValueError as e:
|
||
logger.warning(f"Error accessing goal image: {str(e)}")
|
||
else:
|
||
logger.info("No goal record found for team")
|
||
|
||
|
||
return Response({
|
||
'team_name': entry.team.team_name,
|
||
'members': ', '.join([f"{m.lastname} {m.firstname}" for m in members]),
|
||
'event_code': entry.event.event_name,
|
||
'start_datetime': entry.event.start_datetime,
|
||
'end_datetime': goaltime, #goal_record.goaltime if goal_record else None,
|
||
'goal_photo': goalimage_url, #goal_record.goalimage if goal_record else None,
|
||
'duration': entry.category.duration.total_seconds()
|
||
})
|
||
|
||
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'])
|
||
|
||
def get_image_url(image_path):
|
||
"""画像URLを生成する補助関数"""
|
||
if not image_path:
|
||
return None
|
||
|
||
# 開発環境用のパス生成
|
||
if settings.DEBUG:
|
||
return os.path.join(settings.MEDIA_URL, str(image_path))
|
||
|
||
# 本番環境用のパス生成
|
||
return f"{settings.MEDIA_URL}{image_path}"
|
||
|
||
|
||
@api_view(['GET'])
|
||
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': get_image_url(c.image_address),
|
||
'receipt_address': get_image_url(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):
|
||
try:
|
||
with transaction.atomic():
|
||
update_base = request.data
|
||
logger.info(f"Processing update data: {update_base}")
|
||
zekken_number = update_base['zekken_number']
|
||
event_code = update_base['event_code']
|
||
|
||
# 既存レコードの更新
|
||
for update in update_base['checkins']:
|
||
if 'id' in update and int(update['id']) > 0:
|
||
try:
|
||
checkin = GpsCheckin.objects.get(id=update['id'])
|
||
logger.info(f"Updating existing checkin: {checkin}")
|
||
|
||
# 既存レコードの更新
|
||
checkin.path_order = update['order']
|
||
checkin.buy_flag = update.get('buy_flag', False)
|
||
checkin.validate_location = update.get('validation', False)
|
||
checkin.points = update.get('points', 0)
|
||
# checkin.update_at = timezone.now() チェックイン時刻は更新しない
|
||
checkin.update_user = request.user.email if request.user.is_authenticated else None
|
||
checkin.save()
|
||
logger.info(f"Updated existing checkin result: {checkin}")
|
||
|
||
except GpsCheckin.DoesNotExist:
|
||
logger.error(f"Checkin with id {update['id']} not found")
|
||
continue # エラーを無視して次のレコードの処理を継続
|
||
|
||
# 新規レコードの作成
|
||
for update in update_base['checkins']:
|
||
if 'id' in update and int(update['id']) == 0:
|
||
logger.info(f"Creating new checkin: {update}")
|
||
try:
|
||
checkin = GpsCheckin.objects.create(
|
||
zekken_number=zekken_number,
|
||
event_code=event_code,
|
||
path_order=update['order'],
|
||
cp_number=update['cp_number'],
|
||
validate_location=update.get('validation', False),
|
||
buy_flag=update.get('buy_flag', False),
|
||
points=update.get('points', 0),
|
||
# create_at=timezone.now(), チェックイン時刻は更新しない
|
||
update_at=timezone.now(),
|
||
create_user=request.user.email if request.user.is_authenticated else None,
|
||
update_user=request.user.email if request.user.is_authenticated else None
|
||
)
|
||
logger.info(f"Created new checkin: {checkin}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error creating new checkin: {str(e)}")
|
||
continue # エラーを無視して次のレコードの処理を継続
|
||
|
||
# 更新後のデータを順序付けて取得
|
||
updated_checkins = GpsCheckin.objects.filter(
|
||
zekken_number=zekken_number,
|
||
event_code=event_code
|
||
).order_by('path_order')
|
||
|
||
return Response({
|
||
'status': 'success',
|
||
'message': 'Checkins updated successfully',
|
||
'data': [{'id': c.id, 'path_order': c.path_order} for c in updated_checkins]
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error in update_checkins: {str(e)}", exc_info=True)
|
||
return Response(
|
||
{"error": "Failed to update checkins", "detail": str(e)},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
|
||
@api_view(['POST'])
|
||
def update_checkins_old(request):
|
||
try:
|
||
with transaction.atomic():
|
||
update_base = request.data
|
||
logger.info(f"Processing update data: {update_base}")
|
||
zekken_number = update_base['zekken_number']
|
||
event_code = update_base['event_code']
|
||
|
||
for update in update_base['checkins']:
|
||
if 'id' in update and int(update['id'])>0:
|
||
# 既存レコードの更新
|
||
logger.info(f"Updating existing checkin : {update}")
|
||
try:
|
||
checkin = GpsCheckin.objects.get(id=update['id'])
|
||
logger.info(f"Updating existing checkin: {checkin}")
|
||
|
||
# 既存レコードの更新
|
||
checkin.path_order = update['order']
|
||
checkin.buy_flag = update.get('buy_flag', False)
|
||
checkin.validate_location = update.get('validation', False)
|
||
checkin.points = update.get('points', 0)
|
||
checkin.save()
|
||
logger.info(f"Updated existing checkin result: {checkin}")
|
||
|
||
except GpsCheckin.DoesNotExist:
|
||
logger.error(f"Checkin with id {update['id']} not found")
|
||
return Response(
|
||
{"error": f"Checkin with id {update['id']} not found"},
|
||
status=status.HTTP_404_NOT_FOUND
|
||
)
|
||
for update in update_base['checkins']:
|
||
if 'id' in update and int(update['id'])==0:
|
||
# 新規レコードの作成
|
||
logger.info("Creating new checkin:{update}")
|
||
try:
|
||
checkin = GpsCheckin.objects.create(
|
||
zekken_number=update_base['zekken_number'],
|
||
event_code=update_base['event_code'],
|
||
|
||
path_order=update['order'],
|
||
cp_number=update['cp_number'],
|
||
validate_location=update.get('validation', False),
|
||
buy_flag=update.get('buy_flag', False),
|
||
points=update.get('points', 0),
|
||
create_at=timezone.now(),
|
||
update_at=timezone.now(),
|
||
create_user=request.user.email if request.user.is_authenticated else None,
|
||
update_user=request.user.email if request.user.is_authenticated else None
|
||
)
|
||
logger.info(f"Updated existing checkin result: {checkin}")
|
||
|
||
except KeyError as e:
|
||
logger.error(f"Missing required field: {str(e)}")
|
||
return Response(
|
||
{"error": f"Missing required field: {str(e)}"},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
return Response({'status': 'success', 'message': 'Checkins updated successfully'})
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error in update_checkins: {str(e)}", exc_info=True)
|
||
return Response(
|
||
{"error": "Failed to update checkins", "detail": str(e)},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
|
||
@api_view(['GET'])
|
||
def get_photo_list(request):
|
||
print("/get_photo_list")
|
||
|
||
zekken = request.GET.get('zekken')
|
||
event = request.GET.get('event')
|
||
|
||
if not zekken or not event:
|
||
return JsonResponse({
|
||
"status": "ERROR",
|
||
"message": "Missing required parameters: zekken and event"
|
||
}, status=400)
|
||
|
||
# イベントコードからダブルクォートを削除
|
||
event = event.strip('"')
|
||
zekken = zekken.strip('"')
|
||
|
||
zekken_conversion = {
|
||
"MF3-160": "MZ3-160",
|
||
"MF3-161": "MZ3-161",
|
||
"MF3-162": "MZ3-162",
|
||
"MF3-163": "MZ3-163",
|
||
"MF5-170": "MZ5-170",
|
||
"MF5-171": "MZ5-171",
|
||
"MF5-172": "MZ5-172",
|
||
"MF5-173": "MZ5-173",
|
||
"MF5-174": "MZ5-174",
|
||
"MF5-175": "MZ5-175",
|
||
"MF5-176": "MZ5-176",
|
||
"MF5-177": "MZ5-177",
|
||
"MF5-178": "MZ5-178",
|
||
"MF5-179": "MZ5-179"
|
||
}
|
||
|
||
if zekken in zekken_conversion:
|
||
zekken = zekken_conversion[zekken]
|
||
|
||
photo_list = []
|
||
print(f"Final photo_list length: {len(photo_list)}")
|
||
|
||
photo_list.extend(_get_photo_list(zekken, event))
|
||
|
||
return JsonResponse({
|
||
"status": "OK",
|
||
"photo_list": photo_list,
|
||
"report": _get_final_report(zekken, event)
|
||
})
|
||
|
||
def _get_final_report( zekken, event_code):
|
||
report = f"https://sumasenrogaining.s3.us-west-2.amazonaws.com/{event_code}/scoreboard/certificate_{zekken}.pdf"
|
||
return report
|
||
|
||
def _get_photo_list(zekken, event_code):
|
||
photos = []
|
||
try:
|
||
print(f"Query parameters - zekken_number: {zekken}, event_code: {event_code}")
|
||
|
||
checkins = GpsCheckin.objects.filter(zekken_number=zekken, event_code=event_code)
|
||
# クエリ結果の件数を出力
|
||
print(f"Found {checkins.count()} checkins")
|
||
|
||
print(f"SQL Query: {checkins.query}")
|
||
|
||
for checkin in checkins:
|
||
print(f"Processing checkin - CP number: {checkin.cp_number}")
|
||
if checkin.image_address:
|
||
# URLオブジェクトかどうかを判定
|
||
if hasattr(checkin.image_address, 'url'):
|
||
photo_url = checkin.image_address.url
|
||
else:
|
||
# 文字列の場合、httpで始まるかどうかを確認
|
||
image_address_str = str(checkin.image_address)
|
||
if image_address_str.lower().startswith('http'):
|
||
photo_url = image_address_str
|
||
else:
|
||
photo_url = f"http://rogaining.sumasen.net/media/{image_address_str}"
|
||
|
||
print(f"Found image for CP {checkin.cp_number}: {photo_url}")
|
||
photos.append({
|
||
"cp_number": checkin.cp_number,
|
||
"photo_url": photo_url
|
||
})
|
||
else:
|
||
print(f"No image found for CP {checkin.cp_number}")
|
||
|
||
print(f"Total photos found: {len(photos)}")
|
||
|
||
except Exception as e:
|
||
print(f"Error in _get_photo_list: {str(e)}")
|
||
return photos
|
||
|
||
@api_view(['GET'])
|
||
def export_excel(request, zekken_number, event_code):
|
||
temp_dir = None
|
||
|
||
try:
|
||
# パラメータを文字列型に変換
|
||
zekken_number = str(zekken_number)
|
||
event_code = str(event_code)
|
||
|
||
logger.info(f"Exporting Excel/PDF for zekken_number: {zekken_number}, event_code: {event_code}")
|
||
|
||
# 入力値の検証
|
||
if not zekken_number or not event_code:
|
||
logger.error("Missing required parameters")
|
||
return Response(
|
||
{"error": "Both zekken_number and event_code are required"},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# docbaseディレクトリのパスを絶対パスで設定
|
||
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||
docbase_path = os.path.join(base_dir, 'docbase')
|
||
|
||
# ディレクトリ存在確認と作成
|
||
os.makedirs(docbase_path, exist_ok=True)
|
||
|
||
# 設定ファイルのパス
|
||
template_path = os.path.join(docbase_path, 'certificate_template.xlsx')
|
||
ini_path = os.path.join(docbase_path, 'certificate.ini')
|
||
|
||
# テンプレートと設定ファイルの存在確認
|
||
if not os.path.exists(template_path):
|
||
logger.error(f"Template file not found: {template_path}")
|
||
return Response(
|
||
{"error": "Excel template file missing"},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
if not os.path.exists(ini_path):
|
||
logger.error(f"INI file not found: {ini_path}")
|
||
return Response(
|
||
{"error": "Configuration file missing"},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
# Docker環境用のデータベース設定を使用
|
||
db_settings = settings.DATABASES['default']
|
||
|
||
# 初期化
|
||
variables = {
|
||
"zekken_number": str(zekken_number),
|
||
"event_code": str(event_code),
|
||
"db": str(db_settings['NAME']),
|
||
"username": str(db_settings['USER']),
|
||
"password": str(db_settings['PASSWORD']),
|
||
"host": str(db_settings['HOST']),
|
||
"port": str(db_settings['PORT']),
|
||
"template_path": template_path
|
||
}
|
||
|
||
try:
|
||
excel = SumasenExcel(document="certificate", variables=variables, docbase=docbase_path)
|
||
ret = excel.make_report(variables=variables)
|
||
|
||
if ret["status"] != True:
|
||
message = ret.get("message", "No message provided")
|
||
logger.error(f"Excelファイル作成失敗 : ret.message={message}")
|
||
return Response(
|
||
{"error": f"Excel generation failed: {message}"},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
excel_path = ret.get("filepath")
|
||
if not excel_path or not os.path.exists(excel_path):
|
||
logger.error(f"Output file not found: {excel_path}")
|
||
return Response(
|
||
{"error": "Generated file not found"},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
|
||
# 一時ディレクトリを作成(ASCII文字のみのパスを使用)
|
||
temp_dir = tempfile.mkdtemp(prefix='lo-')
|
||
logger.debug(f"Created temp directory: {temp_dir}")
|
||
|
||
# ASCII文字のみの作業ディレクトリを作成
|
||
work_dir = os.path.join(temp_dir, 'work')
|
||
output_dir = os.path.join(temp_dir, 'output')
|
||
os.makedirs(work_dir, exist_ok=True)
|
||
os.makedirs(output_dir, exist_ok=True)
|
||
|
||
# すべてのディレクトリに適切な権限を設定
|
||
for directory in [temp_dir, work_dir, output_dir]:
|
||
os.chmod(directory, 0o777)
|
||
logger.debug(f"Set permissions for directory: {directory}")
|
||
|
||
# ASCII文字のみの一時ファイル名を使用
|
||
temp_excel_name = f"certificate_{zekken_number}.xlsx"
|
||
temp_excel_path = os.path.join(work_dir, temp_excel_name)
|
||
|
||
# 元のExcelファイルを作業ディレクトリにコピー
|
||
shutil.copy2(excel_path, temp_excel_path)
|
||
os.chmod(temp_excel_path, 0o666)
|
||
logger.debug(f"Copied Excel file to: {temp_excel_path}")
|
||
|
||
|
||
# LibreOffice設定ディレクトリを作成
|
||
libreoffice_config_dir = os.path.join(temp_dir, 'libreoffice')
|
||
os.makedirs(libreoffice_config_dir, exist_ok=True)
|
||
|
||
# フォント設定ディレクトリを作成
|
||
font_conf_dir = os.path.join(temp_dir, 'fonts')
|
||
os.makedirs(font_conf_dir, exist_ok=True)
|
||
|
||
# フォント設定ファイルを作成
|
||
fonts_conf_content = '''<?xml version="1.0"?>
|
||
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||
<fontconfig>
|
||
<dir>/usr/share/fonts</dir>
|
||
<match target="pattern">
|
||
<test qual="any" name="family">
|
||
<string>sans-serif</string>
|
||
</test>
|
||
<edit name="family" mode="assign" binding="same">
|
||
<string>IPAexGothic</string>
|
||
</edit>
|
||
</match>
|
||
<match target="pattern">
|
||
<test qual="any" name="family">
|
||
<string>serif</string>
|
||
</test>
|
||
<edit name="family" mode="assign" binding="same">
|
||
<string>IPAexMincho</string>
|
||
</edit>
|
||
</match>
|
||
</fontconfig>'''
|
||
|
||
font_conf_path = os.path.join(libreoffice_config_dir, 'fonts.conf')
|
||
with open(font_conf_path, 'w') as f:
|
||
f.write(fonts_conf_content)
|
||
|
||
|
||
# LibreOfficeのフォント設定を作成
|
||
registry_dir = os.path.join(libreoffice_config_dir, 'registry')
|
||
os.makedirs(registry_dir, exist_ok=True)
|
||
|
||
# フォント埋め込み設定を作成
|
||
pdf_export_config = '''<?xml version="1.0" encoding="UTF-8"?>
|
||
<oor:data xmlns:oor="http://openoffice.org/2001/registry">
|
||
<oor:component-data oor:package="org.openoffice.Office.Common" oor:name="Filter">
|
||
<node oor:name="PDF">
|
||
<prop oor:name="EmbedFonts" oor:op="fuse">
|
||
<value>true</value>
|
||
</prop>
|
||
<prop oor:name="ExportFormFields" oor:op="fuse">
|
||
<value>true</value>
|
||
</prop>
|
||
<prop oor:name="UseTaggedPDF" oor:op="fuse">
|
||
<value>true</value>
|
||
</prop>
|
||
</node>
|
||
</oor:component-data>
|
||
</oor:data>'''
|
||
|
||
pdf_config_path = os.path.join(registry_dir, 'pdf_export.xcu')
|
||
with open(pdf_config_path, 'w') as f:
|
||
f.write(pdf_export_config)
|
||
|
||
# すべてのディレクトリに適切な権限を設定
|
||
for directory in [temp_dir, work_dir, output_dir,registry_dir]:
|
||
os.chmod(directory, 0o777)
|
||
logger.debug(f"Set permissions for directory: {directory}")
|
||
|
||
os.chmod(temp_excel_path, 0o666)
|
||
os.chmod(font_conf_path, 0o666)
|
||
os.chmod(pdf_config_path, 0o666)
|
||
|
||
# フォーマット指定(excel or pdf)
|
||
format_type = request.query_params.get('format', 'pdf')
|
||
|
||
if format_type.lower() == 'pdf':
|
||
try:
|
||
# パスとファイル名に分離
|
||
file_dir = os.path.dirname(temp_excel_path) # パス部分の取得
|
||
file_name = os.path.basename(temp_excel_path) # ファイル名部分の取得
|
||
|
||
# ファイル名の拡張子をpdfに変更
|
||
base_name = os.path.splitext(file_name)[0] # 拡張子を除いたファイル名
|
||
new_file_name = base_name + ".pdf" # 新しい拡張子を追加
|
||
|
||
# 新しいパスを作成
|
||
pdf_path = os.path.join(file_dir, new_file_name)
|
||
|
||
logger.info(f"Converting Excel to PDF in directory: {file_dir}")
|
||
|
||
# LibreOfficeを使用してExcelをPDFに変換
|
||
conversion_command = [
|
||
'soffice', # LibreOfficeの代替コマンド
|
||
'--headless',
|
||
'--convert-to', 'pdf:writer_pdf_Export',
|
||
'--outdir',file_dir,
|
||
'-env:UserInstallation=file://' + libreoffice_config_dir,
|
||
temp_excel_path
|
||
]
|
||
|
||
logger.debug(f"Running conversion command: {' '.join(conversion_command)}")
|
||
|
||
# 環境変数を設定
|
||
env = os.environ.copy()
|
||
#env['HOME'] = temp_dir # LibreOfficeの設定ディレクトリを一時ディレクトリに設定
|
||
env['HOME'] = temp_dir
|
||
env['LANG'] = 'ja_JP.UTF-8' # 日本語環境を設定
|
||
env['LC_ALL'] = 'ja_JP.UTF-8'
|
||
env['FONTCONFIG_FILE'] = font_conf_path
|
||
env['FONTCONFIG_PATH'] = font_conf_dir
|
||
|
||
# 変換プロセスを実行
|
||
process = subprocess.run(
|
||
conversion_command,
|
||
env=env,
|
||
capture_output=True,
|
||
text=True,
|
||
cwd=work_dir,
|
||
check=True,
|
||
timeout=30
|
||
)
|
||
|
||
logger.debug(f"Conversion output: {process.stdout}")
|
||
|
||
# PDFファイルの存在確認
|
||
if not os.path.exists(pdf_path):
|
||
logger.error("PDF conversion failed - output file not found")
|
||
return Response(
|
||
{"error": "PDF conversion failed - output file not found"},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
s3 = S3Bucket('sumasenrogaining')
|
||
s3.upload_file(pdf_path, f'{event_code}/scoreboard/certificate_{zekken_number}.pdf')
|
||
s3.upload_file(excel_path, f'{event_code}/scoreboard_excel/certificate_{zekken_number}.xlsx')
|
||
|
||
os.remove(temp_excel_path)
|
||
os.remove(excel_path)
|
||
os.remove(pdf_path)
|
||
|
||
return Response( status=status.HTTP_200_OK )
|
||
|
||
|
||
except subprocess.CalledProcessError as e:
|
||
logger.error(f"Error converting to PDF: {str(e)}\nSTDOUT: {e.stdout}\nSTDERR: {e.stderr}")
|
||
return Response(
|
||
{"error": f"PDF conversion failed: {str(e)}"},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
finally:
|
||
# 一時ディレクトリの削除
|
||
if temp_dir and os.path.exists(temp_dir):
|
||
try:
|
||
shutil.rmtree(temp_dir)
|
||
logger.debug(f"Temporary directory removed: {temp_dir}")
|
||
except Exception as e:
|
||
logger.warning(f"Failed to remove temporary directory: {str(e)}")
|
||
|
||
else: # Excel形式の場合
|
||
with open(excel_path, 'rb') as excel_file:
|
||
response = HttpResponse(
|
||
excel_file.read(),
|
||
content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||
)
|
||
response['Content-Disposition'] = f'attachment; filename="certificate_{zekken_number}_{event_code}.xlsx"'
|
||
return response
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error in Excel/PDF generation: {str(e)}", exc_info=True)
|
||
return Response(
|
||
{"error": f"File generation failed: {str(e)}"},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error in export_excel: {str(e)}", exc_info=True)
|
||
return Response(
|
||
{"error": "Failed to export file", "detail": str(e)},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
finally:
|
||
# 確実に一時ディレクトリを削除
|
||
if temp_dir and os.path.exists(temp_dir):
|
||
try:
|
||
shutil.rmtree(temp_dir)
|
||
except Exception as e:
|
||
logger.warning(f"Failed to remove temporary directory in finally block: {str(e)}")
|
||
|
||
@api_view(['GET'])
|
||
def export_exceli_old2(request,zekken_number, event_code):
|
||
|
||
try:
|
||
# パラメータを文字列型に変換
|
||
zekken_number = str(zekken_number)
|
||
event_code = str(event_code)
|
||
|
||
logger.info(f"Exporting Excel for zekken_number: {zekken_number}, event_code: {event_code}")
|
||
|
||
# 入力値の検証
|
||
if not zekken_number or not event_code:
|
||
logger.error("Missing required parameters")
|
||
return Response(
|
||
{"error": "Both zekken_number and event_code are required"},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# docbaseディレクトリのパスを絶対パスで設定
|
||
base_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||
docbase_path = os.path.join(base_dir, 'docbase')
|
||
|
||
# ディレクトリ存在確認と作成
|
||
os.makedirs(docbase_path, exist_ok=True)
|
||
|
||
# 設定ファイルのパス
|
||
template_path = os.path.join(docbase_path, 'certificate_template.xlsx')
|
||
ini_path = os.path.join(docbase_path, 'certificate.ini')
|
||
|
||
# テンプレートと設定ファイルの存在確認
|
||
if not os.path.exists(template_path):
|
||
logger.error(f"Template file not found: {template_path}")
|
||
return Response(
|
||
{"error": "Excel template file missing"},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
if not os.path.exists(ini_path):
|
||
logger.error(f"INI file not found: {ini_path}")
|
||
return Response(
|
||
{"error": "Configuration file missing"},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
# Docker環境用のデータベース設定を使用
|
||
db_settings = settings.DATABASES['default']
|
||
|
||
|
||
|
||
# 初期化
|
||
variables = {
|
||
"zekken_number":str(zekken_number),
|
||
"event_code":str(event_code),
|
||
"db":str(db_settings['NAME']), #"rogdb",
|
||
"username":str(db_settings['USER']), #"admin",
|
||
"password":str(db_settings['PASSWORD']), #"admin123456",
|
||
"host":str(db_settings['HOST']), # Docker Composeのサービス名を使用 # "localhost",
|
||
"port":str(db_settings['PORT']), #"5432",
|
||
"template_path": template_path
|
||
}
|
||
|
||
# データベース接続情報のログ出力(パスワードは除く)
|
||
logger.info(f"Attempting database connection to {variables['host']}:{variables['port']} "
|
||
f"with user {variables['username']} and database {variables['db']}")
|
||
|
||
try:
|
||
excel = SumasenExcel(document="certificate", variables=variables, docbase=docbase_path)
|
||
# ./docbase/certificate.ini の定義をベースに、
|
||
# ./docbase/certificate_template.xlsxを読み込み
|
||
# ./docbase/certificate_(zekken_number).xlsxを作成する
|
||
|
||
# シート初期化
|
||
logger.info("Generating report with variables: %s",
|
||
{k: v for k, v in variables.items() if k != 'password'}) # パスワードを除外
|
||
|
||
ret = excel.make_report(variables=variables)
|
||
if ret["status"]==True:
|
||
filepath=ret["filepath"]
|
||
logging.info(f"Excelファイル作成 : ret.filepath={filepath}")
|
||
else:
|
||
message = ret.get("message", "No message provided")
|
||
logging.error(f"Excelファイル作成失敗 : ret.message={message}")
|
||
|
||
output_path = ret.get("filepath")
|
||
if not output_path or not os.path.exists(output_path):
|
||
logger.error(f"Output file not found: {output_path}")
|
||
return Response(
|
||
{"error": "Generated file not found"},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
excel_path = output_path
|
||
|
||
# PDFのファイル名を生成
|
||
pdf_filename = f'certificate_{zekken_number}_{event_code}.pdf'
|
||
pdf_path = os.path.join(docbase_path, pdf_filename)
|
||
|
||
# フォーマット指定(excel or pdf)
|
||
format_type = request.query_params.get('format', 'pdf') # 'excel'
|
||
|
||
if format_type.lower() == 'pdf':
|
||
try:
|
||
# 一時ディレクトリを作成
|
||
temp_dir = tempfile.mkdtemp()
|
||
temp_excel = os.path.join(temp_dir, f'certificate_{zekken_number}.xlsx')
|
||
temp_pdf = os.path.join(temp_dir, f'certificate_{zekken_number}.pdf')
|
||
|
||
# Excelファイルを一時ディレクトリにコピー
|
||
shutil.copy2(excel_path, temp_excel)
|
||
|
||
# 一時ディレクトリのパーミッションを設定
|
||
os.chmod(temp_dir, 0o777)
|
||
os.chmod(temp_excel, 0o666)
|
||
|
||
logger.info(f"Converting Excel to PDF in temp directory: {temp_dir}")
|
||
|
||
|
||
# LibreOfficeを使用してExcelをPDFに変換
|
||
conversion_command = [
|
||
'soffice',
|
||
'--headless',
|
||
'--convert-to',
|
||
'pdf',
|
||
'--outdir',
|
||
temp_dir,
|
||
temp_excel
|
||
]
|
||
|
||
logger.debug(f"Running conversion command: {' '.join(conversion_command)}")
|
||
|
||
# 環境変数を設定
|
||
env = os.environ.copy()
|
||
env['HOME'] = temp_dir # LibreOfficeの設定ディレクトリを一時ディレクトリに設定
|
||
|
||
# 変換プロセスを実行
|
||
process = subprocess.run(
|
||
conversion_command,
|
||
env=env,
|
||
capture_output=True,
|
||
text=True,
|
||
check=True
|
||
)
|
||
|
||
logger.debug(f"Conversion output: {process.stdout}")
|
||
|
||
# PDFファイルの存在確認
|
||
if not os.path.exists(temp_pdf):
|
||
logger.error("PDF conversion failed - output file not found")
|
||
return Response(
|
||
{"error": "PDF conversion failed - output file not found"},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
# PDFファイルを読み込んでレスポンスを返す
|
||
with open(temp_pdf, 'rb') as pdf_file:
|
||
pdf_content = pdf_file.read()
|
||
|
||
response = HttpResponse(pdf_content, content_type='application/pdf')
|
||
response['Content-Disposition'] = f'attachment; filename="certificate_{zekken_number}_{event_code}.pdf"'
|
||
return response
|
||
|
||
|
||
except subprocess.CalledProcessError as e:
|
||
logger.error(f"Error converting to PDF: {str(e)}\nSTDOUT: {e.stdout}\nSTDERR: {e.stderr}")
|
||
return Response(
|
||
{"error": "PDF conversion failed"},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
finally:
|
||
# 一時ディレクトリの削除
|
||
if temp_dir and os.path.exists(temp_dir):
|
||
try:
|
||
shutil.rmtree(temp_dir)
|
||
logger.debug(f"Temporary directory removed: {temp_dir}")
|
||
except Exception as e:
|
||
logger.warning(f"Failed to remove temporary directory: {str(e)}")
|
||
|
||
|
||
else: # Excel形式の場合
|
||
with open(excel_path, 'rb') as excel_file:
|
||
response = HttpResponse(
|
||
excel_file.read(),
|
||
content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
||
)
|
||
excel_filename = f'certificate_{zekken_number}_{event_code}.xlsx'
|
||
response['Content-Disposition'] = f'attachment; filename="{excel_filename}"'
|
||
return response
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error in Excel/PDF generation: {str(e)}", exc_info=True)
|
||
return Response(
|
||
{"error": f"File generation failed: {str(e)}"},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error in export_excel: {str(e)}", exc_info=True)
|
||
return Response(
|
||
{"error": "Failed to export file", "detail": str(e)},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
finally:
|
||
# 確実に一時ディレクトリを削除
|
||
if temp_dir and os.path.exists(temp_dir):
|
||
try:
|
||
shutil.rmtree(temp_dir)
|
||
except Exception as e:
|
||
logger.warning(f"Failed to remove temporary directory in finally block: {str(e)}")
|
||
|
||
|
||
# ----- for Supervisor -----
|
||
|
||
@api_view(['GET'])
|
||
def test_api(request):
|
||
logger.debug("Test API endpoint called")
|
||
return JsonResponse({"status": "API is working"})
|
||
|
||
@api_view(['GET'])
|
||
#@authentication_classes([TokenAuthentication])
|
||
#@permission_classes([IsAuthenticated])
|
||
def get_goalimage(request):
|
||
"""
|
||
ゼッケン番号とイベントコードに基づいてゴール画像情報を取得するエンドポイント
|
||
|
||
Parameters:
|
||
zekken_number (str): ゼッケン番号
|
||
event_code (str): イベントコード
|
||
|
||
Returns:
|
||
Response: ゴール画像情報を含むJSONレスポンス
|
||
"""
|
||
try:
|
||
logger.debug(f"get_goalimage called with params: {request.GET}")
|
||
|
||
# リクエストパラメータを取得
|
||
zekken_number = request.GET.get('zekken_number')
|
||
event_code = request.GET.get('event_code')
|
||
|
||
logger.debug(f"Searching for goal records with zekken_number={zekken_number}, event_code={event_code}")
|
||
|
||
# パラメータの検証
|
||
if not zekken_number or not event_code:
|
||
return Response(
|
||
{"error": "zekken_number and event_code are required"},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# ゴール画像レコードを検索(最も早いゴール時間のレコードを取得)
|
||
goal_records = GoalImages.objects.filter(
|
||
zekken_number=zekken_number,
|
||
event_code=event_code
|
||
).order_by('goaltime')
|
||
|
||
logger.debug(f"Found {goal_records.count()} goal records")
|
||
|
||
if not goal_records.exists():
|
||
return Response(
|
||
{"message": "No goal records found"},
|
||
status=status.HTTP_404_NOT_FOUND
|
||
)
|
||
|
||
# 最も早いゴール時間のレコード(cp_number = 0)を探す
|
||
valid_goal = goal_records.filter(cp_number=0).first()
|
||
|
||
if not valid_goal:
|
||
return Response(
|
||
{"message": "No valid goal record found"},
|
||
status=status.HTTP_404_NOT_FOUND
|
||
)
|
||
|
||
# シリアライザでデータを整形
|
||
serializer = GolaImageSerializer(valid_goal)
|
||
|
||
# レスポンスデータの構築
|
||
response_data = {
|
||
"goal_record": serializer.data,
|
||
"total_records": goal_records.count(),
|
||
"has_multiple_records": goal_records.count() > 1
|
||
}
|
||
|
||
logger.info(f"Retrieved goal record for zekken_number {zekken_number} in event {event_code}")
|
||
return Response(response_data)
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error retrieving goal record: {str(e)}", exc_info=True)
|
||
return Response(
|
||
{"error": f"Failed to retrieve goal record: {str(e)}"},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
|
||
@api_view(['POST'])
|
||
#@authentication_classes([TokenAuthentication])
|
||
#@permission_classes([IsAuthenticated])
|
||
def update_goal_time(request):
|
||
try:
|
||
logger.info(f"update_goal_time:{request}")
|
||
# リクエストからデータを取得
|
||
zekken_number = request.data.get('zekken_number')
|
||
event_code = request.data.get('event_code')
|
||
team_name = request.data.get('team_name')
|
||
goal_time_str = request.data.get('goaltime')
|
||
|
||
logger.info(f"zekken_number={zekken_number},event_code={event_code},team_name={team_name},goal_time={goal_time_str}")
|
||
|
||
# 入力バリデーション
|
||
#if not all([zekken_number, event_code, team_name, goal_time_str]):
|
||
# return Response(
|
||
# {"error": "Missing required fields"},
|
||
# status=status.HTTP_400_BAD_REQUEST
|
||
# )
|
||
|
||
try:
|
||
# 文字列からdatetimeオブジェクトに変換
|
||
goal_time = datetime.strptime(goal_time_str, '%Y-%m-%dT%H:%M:%S')
|
||
except ValueError:
|
||
return Response(
|
||
{"error": "Invalid goal time format"},
|
||
status=status.HTTP_400_BAD_REQUEST
|
||
)
|
||
|
||
# 既存のゴール記録を探す
|
||
goal_record = GoalImages.objects.filter(
|
||
team_name=team_name,
|
||
event_code=event_code
|
||
).first()
|
||
|
||
if goal_record:
|
||
# 既存の記録を更新
|
||
goal_record.goaltime = goal_time
|
||
goal_record.zekken_number = zekken_number
|
||
goal_record.cp_number = -2
|
||
goal_record.save()
|
||
logger.info(f"Updated goal time as {goal_time} for team {team_name} in event {event_code}")
|
||
else:
|
||
# 新しい記録を作成
|
||
entry = Entry.objects.get(zekken_number=zekken_number, event__event_name=event_code)
|
||
GoalImages.objects.create(
|
||
user=entry.owner,
|
||
goaltime=goal_time,
|
||
team_name=team_name,
|
||
event_code=event_code,
|
||
cp_number=-2, # ゴール地点を表すCP番号
|
||
zekken_number = zekken_number
|
||
)
|
||
logger.info(f"Created new goal time record for team {team_name} in event {event_code}")
|
||
|
||
return Response({"message": "Goal time updated successfully"})
|
||
|
||
except Entry.DoesNotExist:
|
||
logger.error(f"Entry not found for zekken_number {zekken_number} in event {event_code}")
|
||
return Response(
|
||
{"error": "Entry not found"},
|
||
status=status.HTTP_404_NOT_FOUND
|
||
)
|
||
except Exception as e:
|
||
logger.error(f"Error updating goal time: {str(e)}")
|
||
return Response(
|
||
{"error": f"Failed to update goal time: {str(e)}"},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
|
||
|
||
def get_team_status(last_checkin_time, goal_time, event_end_time):
|
||
"""
|
||
チームの状態を判定する
|
||
"""
|
||
now = timezone.now()
|
||
|
||
if goal_time:
|
||
return "ゴール"
|
||
|
||
if not last_checkin_time:
|
||
if now > event_end_time + timedelta(minutes=30):
|
||
return "棄権"
|
||
return "競技中"
|
||
|
||
# 最終チェックインから30分以上経過
|
||
if now > last_checkin_time + timedelta(minutes=30):
|
||
return "棄権"
|
||
|
||
return "競技中"
|
||
|
||
def calculate_late_points(goal_time, event_end_time):
|
||
"""遅刻による減点を計算する"""
|
||
if not goal_time or not event_end_time:
|
||
return 0
|
||
|
||
minutes_late = max(0, int((goal_time - event_end_time).total_seconds() / 60))
|
||
return minutes_late * -50
|
||
|
||
def is_disqualified(start_time, goal_time, duration):
|
||
"""失格判定を行う"""
|
||
if not goal_time or not start_time or not duration:
|
||
return False # ゴール時間がない場合は失格としない(競技中の可能性)
|
||
|
||
# duration(timedelta)に15分を加算
|
||
max_time = start_time + duration + timedelta(minutes=15)
|
||
return goal_time > max_time
|
||
|
||
@api_view(['GET'])
|
||
def get_ranking(request, event_code, category_name):
|
||
"""特定のイベントとクラスのランキングを取得する"""
|
||
try:
|
||
# イベントの情報を取得
|
||
event = NewEvent2.objects.get(event_name=event_code)
|
||
|
||
# 有効なエントリーを取得
|
||
entries = Entry.objects.filter(
|
||
event=event,
|
||
category__category_name=category_name,
|
||
is_active=True
|
||
).select_related('team', 'category')
|
||
|
||
rankings = []
|
||
disqualified = [] # 失格チームのリスト
|
||
|
||
for entry in entries:
|
||
# チェックインポイントを集計
|
||
checkins = GpsCheckin.objects.filter(
|
||
zekken_number=entry.zekken_number,
|
||
event_code=event_code
|
||
).aggregate(
|
||
total_points=Sum('points')
|
||
)
|
||
|
||
# 最後のチェックイン時刻を取得
|
||
last_checkin = GpsCheckin.objects.filter(
|
||
zekken_number=entry.zekken_number,
|
||
event_code=event_code
|
||
).order_by('-create_at').first()
|
||
|
||
last_checkin_time = last_checkin.create_at if last_checkin else None
|
||
|
||
# ゴール時間を取得 (最も早いゴール時間)
|
||
goal_record = GoalImages.objects.filter(
|
||
zekken_number=entry.zekken_number,
|
||
event_code=event_code
|
||
).order_by('goaltime').first()
|
||
|
||
goal_time = goal_record.goaltime if goal_record else None
|
||
total_points = checkins['total_points'] or 0
|
||
|
||
# イベントの終了予定時刻を計算
|
||
expected_end_time = event.start_datetime + entry.category.duration
|
||
|
||
# チーム状態の判定
|
||
team_status = get_team_status(last_checkin_time, goal_time, expected_end_time)
|
||
|
||
# 失格判定
|
||
if is_disqualified(event.start_datetime, goal_time, entry.category.duration):
|
||
disqualified.append({
|
||
'team_name': entry.team.team_name,
|
||
'zekken_number': entry.zekken_number,
|
||
'point': total_points,
|
||
'late_point': 0,
|
||
'goal_time': goal_time,
|
||
'reason': '制限時間超過',
|
||
'status': team_status
|
||
})
|
||
continue
|
||
|
||
# 遅刻減点を計算
|
||
late_points = calculate_late_points(goal_time, expected_end_time)
|
||
|
||
rankings.append({
|
||
'team_name': entry.team.team_name,
|
||
'zekken_number': entry.zekken_number,
|
||
'point': total_points,
|
||
'late_point': abs(late_points),
|
||
'final_point': total_points + late_points,
|
||
'goal_time': goal_time,
|
||
'status': team_status,
|
||
'last_checkin': last_checkin_time
|
||
})
|
||
|
||
# ポイントの高い順(同点の場合はゴール時間が早い順)にソート
|
||
# 棄権チームを最後に
|
||
rankings.sort(key=lambda x: (
|
||
-1 if x['status'] != '棄権' else 0, # 棄権でないチームを優先
|
||
-x['final_point'], # 得点の高い順
|
||
x['goal_time'] or datetime.max # ゴール時間の早い順
|
||
))
|
||
|
||
# 有効なランキングと失格チームを結合
|
||
final_rankings = {
|
||
'rankings': rankings,
|
||
'disqualified': disqualified
|
||
}
|
||
|
||
return Response(final_rankings)
|
||
|
||
except NewEvent2.DoesNotExist:
|
||
return Response(
|
||
{"error": "Specified event not found"},
|
||
status=status.HTTP_404_NOT_FOUND
|
||
)
|
||
except Exception as e:
|
||
logger.error(f"Error in get_ranking: {str(e)}", exc_info=True)
|
||
return Response(
|
||
{"error": str(e)},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
)
|
||
|
||
@api_view(['GET'])
|
||
def all_ranking_top3(request, event_code):
|
||
"""特定のイベントの全クラスのTOP3を取得する"""
|
||
try:
|
||
# イベントの情報を取得
|
||
event = NewEvent2.objects.get(event_name=event_code)
|
||
|
||
# イベントの有効なカテゴリーを取得
|
||
categories = NewCategory.objects.filter(
|
||
entry__event=event,
|
||
entry__is_active=True
|
||
).distinct()
|
||
|
||
rankings = {}
|
||
for category in categories:
|
||
# カテゴリーごとのエントリーを取得
|
||
entries = Entry.objects.filter(
|
||
event=event,
|
||
category=category,
|
||
is_active=True
|
||
).select_related('team')
|
||
|
||
category_rankings = []
|
||
disqualified = [] # カテゴリーごとの失格チーム
|
||
|
||
for entry in entries:
|
||
# チェックインポイントを集計
|
||
checkins = GpsCheckin.objects.filter(
|
||
zekken_number=entry.zekken_number,
|
||
event_code=event_code
|
||
).aggregate(
|
||
total_points=Sum('points')
|
||
)
|
||
|
||
# 最後のチェックイン時刻を取得
|
||
last_checkin = GpsCheckin.objects.filter(
|
||
zekken_number=entry.zekken_number,
|
||
event_code=event_code
|
||
).order_by('-create_at').first()
|
||
|
||
last_checkin_time = last_checkin.create_at if last_checkin else None
|
||
|
||
# ゴール時間を取得
|
||
goal_record = GoalImages.objects.filter(
|
||
zekken_number=entry.zekken_number,
|
||
event_code=event_code
|
||
).order_by('goaltime').first()
|
||
|
||
goal_time = goal_record.goaltime if goal_record else None
|
||
total_points = checkins['total_points'] or 0
|
||
|
||
# イベントの終了予定時刻を計算
|
||
expected_end_time = event.start_datetime + category.duration
|
||
|
||
# チーム状態の判定
|
||
team_status = get_team_status(last_checkin_time, goal_time, expected_end_time)
|
||
|
||
# 失格判定
|
||
if is_disqualified(event.start_datetime, goal_time, entry.category.duration):
|
||
disqualified.append({
|
||
'team_name': entry.team.team_name,
|
||
'zekken_number': entry.zekken_number,
|
||
'point': total_points,
|
||
'late_point': 0,
|
||
'goal_time': goal_time,
|
||
'reason': '制限時間超過',
|
||
'status': team_status
|
||
})
|
||
continue
|
||
|
||
# 遅刻減点を計算
|
||
late_points = calculate_late_points(goal_time, expected_end_time)
|
||
|
||
category_rankings.append({
|
||
'team_name': entry.team.team_name,
|
||
'zekken_number': entry.zekken_number,
|
||
'point': total_points,
|
||
'late_point': abs(late_points),
|
||
'final_point': total_points + late_points,
|
||
'goal_time': goal_time,
|
||
'status': team_status,
|
||
'last_checkin': last_checkin_time
|
||
})
|
||
|
||
# ポイントの高い順(同点の場合はゴール時間が早い順)にソート
|
||
# 棄権チームを最後に
|
||
category_rankings.sort(key=lambda x: (
|
||
-1 if x['status'] != '棄権' else 0, # 棄権でないチームを優先
|
||
-x['final_point'], # 得点の高い順
|
||
x['goal_time'] or datetime.max # ゴール時間の早い順
|
||
))
|
||
|
||
# TOP3のみを保持(棄権を除く)
|
||
top_rankings = [r for r in category_rankings if r['status'] != '棄権'][:3]
|
||
|
||
rankings[category.category_name] = {
|
||
'rankings': top_rankings,
|
||
'disqualified': disqualified
|
||
}
|
||
|
||
return Response(rankings)
|
||
|
||
except NewEvent2.DoesNotExist:
|
||
return Response(
|
||
{"error": "Specified event not found"},
|
||
status=status.HTTP_404_NOT_FOUND
|
||
)
|
||
except Exception as e:
|
||
logger.error(f"Error in all_ranking_top3: {str(e)}", exc_info=True)
|
||
return Response(
|
||
{"error": str(e)},
|
||
status=status.HTTP_500_INTERNAL_SERVER_ERROR
|
||
) |