Compare commits

2 Commits

Author SHA1 Message Date
1c7a6a1f5c Added APIs for team and members 2024-07-20 11:15:33 +09:00
f0114ef33c setup for Akiras Laptop 2024-04-22 00:08:34 +09:00
11 changed files with 420 additions and 35 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -1,5 +1,8 @@
# FROM python:3.9.9-slim-buster
FROM osgeo/gdal:ubuntu-small-3.4.0
# Akira
FROM python:3.10
FROM ubuntu:latest
WORKDIR /app
@ -14,6 +17,13 @@ ARG TZ Asia/Tokyo \
RUN apt-get update -y
# 必要なライブラリのインストール by akira
RUN apt-get update && \
apt-get install -y software-properties-common && \
add-apt-repository ppa:ubuntugis/ppa && \
apt-get update && \
apt-get install -y gdal-bin libgdal-dev python3-gdal
# Install GDAL dependencies
RUN apt-get install -y libgdal-dev g++ --no-install-recommends && \
apt-get clean -y
@ -23,7 +33,7 @@ ENV CPLUS_INCLUDE_PATH=/usr/include/gdal
ENV C_INCLUDE_PATH=/usr/include/gdal
RUN apt-get update \
&& apt-get -y install netcat gcc postgresql \
&& apt-get -y install netcat-openbsd gcc postgresql \
&& apt-get clean
RUN apt-get update \
@ -56,7 +66,9 @@ RUN pip install -r requirements.txt
COPY . /app
# Collect static files
RUN python manage.py collectstatic --noinput
RUN python3 manage.py collectstatic --noinput
# Use Gunicorn as the entrypoint
CMD ["gunicorn", "config.wsgi:application", "--bind", "0.0.0.0:8000"]

6
README.jpn Normal file
View File

@ -0,0 +1,6 @@
デプロイ:
you can just run
docker-compose up -d
will deploy it

View File

@ -174,3 +174,16 @@ REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],
'DEFAULT_AUTHENTICATION_CLASSES': ('knox.auth.TokenAuthentication', ),
}
# Email settings
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp-mail.outlook.com'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = 'akira.miyata@gifuai.net'
EMAIL_HOST_PASSWORD = 'SachikoMiyata@123'
# Optional: Set a default "from" address
DEFAULT_FROM_EMAIL = 'info@gifuai.net'

View File

@ -31,7 +31,7 @@ matplotlib==3.5.0
mccabe==0.6.1
munch==2.5.0
mypy-extensions==0.4.3
numpy==1.21.4
numpy==1.26.2
packaging==21.3
pandas==1.3.4
pathspec==0.9.0
@ -46,11 +46,13 @@ pyparsing==3.0.6
pyproj==3.3.0
python-dateutil==2.8.2
pytz==2021.3
rasterio==1.2.10
#rasterio==1.2.10 Akira
rasterio==1.3.10
regex==2021.11.10
requests==2.26.0
Rtree==0.9.7
scipy==1.7.3
#scipy==1.7.3
scipy==1.10.1
seaborn==0.11.2
setuptools-scm==6.3.2
Shapely==1.8.0

BIN
rog/.DS_Store vendored

Binary file not shown.

View File

@ -0,0 +1,16 @@
from django.core.management.base import BaseCommand
from django.utils import timezone
from rog.models import TempUser # アプリ名 'rog' を適切に変更してください
class Command(BaseCommand):
help = 'Deletes expired temporary user records'
def handle(self, *args, **options):
expired_users = TempUser.objects.filter(expires_at__lt=timezone.now())
count = expired_users.count()
expired_users.delete()
self.stdout.write(self.style.SUCCESS(f'Successfully deleted {count} expired temporary user records'))
# cron job の設定
# 0 3 * * * /path/to/your/python /path/to/your/manage.py cleanup_temp_users

View File

@ -23,6 +23,11 @@ from django.apps import apps
from django.db import transaction
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, BaseUserManager
from django.contrib.postgres.indexes import GistIndex
from django.db import models
from django.contrib.auth.hashers import make_password
from django.utils import timezone
from datetime import timedelta
import csv
import codecs
@ -61,35 +66,123 @@ def remove_bom_inplace(path):
fp.seek(-bom_length, os.SEEK_CUR)
fp.truncate()
#========== Akira ここから
class TempUser(models.Model):
email = models.EmailField(unique=True)
password = models.CharField(max_length=128)
is_rogaining = models.BooleanField(default=False)
zekken_number = models.CharField(max_length=255, blank=True, null=True)
event_code = models.CharField(max_length=255, blank=True, null=True)
team_name = models.CharField(max_length=255, blank=True, null=True)
group = models.CharField(max_length=255)
verification_code = models.UUIDField(default=uuid.uuid4, editable=False)
created_at = models.DateTimeField(auto_now_add=True)
expires_at = models.DateTimeField()
def __str__(self):
return self.email
def save(self, *args, **kwargs):
if not self.expires_at:
self.expires_at = timezone.now() + timedelta(hours=24) # 24時間の有効期限
super().save(*args, **kwargs)
def is_valid(self):
return timezone.now() <= self.expires_at
class Team(models.Model):
zekken_number = models.CharField(max_length=255, primary_key=True)
team_name = models.CharField(max_length=255)
password = models.CharField(max_length=128)
def __str__(self):
return f"{self.zekken_number} - {self.team_name}"
class Member(models.Model):
zekken_number = models.ForeignKey(Team, on_delete=models.CASCADE)
userid = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
class Meta:
unique_together = ('zekken_number', 'userid')
def __str__(self):
return f"{self.zekken_number} - {self.userid}"
class Entry(models.Model):
zekken_number = models.ForeignKey(Team, on_delete=models.CASCADE)
event_code = models.CharField(max_length=255)
date = models.DateField()
class Meta:
unique_together = ('zekken_number', 'event_code', 'date')
def __str__(self):
return f"{self.zekken_number} - {self.event_code} - {self.date}"
#============= Akira ここまで
class CustomUserManager(BaseUserManager):
def create_user(self, email, password, group, event_code, team_name, **other_fields):
def create_user(self, email, firstname, lastname, date_of_birth, password=None):
if not email:
raise ValueError(_("You must provide an email address"))
# email = self.normalize_email(email)
user=self.model(email=email, group=group, event_code=event_code, team_name=team_name, zekken_number=email, is_rogaining=True, **other_fields)
user.set_password(password)
user.save()
# ユニークなuseridを生成
userid = str(uuid.uuid4())
user = self.model(
email=self.normalize_email(email),
firstname=firstname,
lastname=lastname,
userid=userid,
date_of_birth=date_of_birth,
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, password, group, **other_fields):
other_fields.setdefault('is_staff', True)
other_fields.setdefault('is_superuser', True)
other_fields.setdefault('is_active', True)
def create_superuser(self, email, firstname, lastname, date_of_birth, password):
user = self.create_user(
email,
firstname=firstname,
lastname=lastname,
date_of_birth=date_of_birth,
password=password,
)
user.is_staff = True
user.is_superuser = True
user.is_active = True
user.save(using=self._db)
return user
if other_fields.get('is_staff') is not True:
raise ValueError(_('Supperuser must assigned to staff'))
if other_fields.get('is_superuser') is not True:
raise ValueError(_('Supperuser must assigned to superuser=True'))
class CustomUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True)
firstname = models.CharField(max_length=255)
lastname = models.CharField(max_length=255)
userid = models.CharField(max_length=255, unique=True)
date_of_birth = models.DateField()
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
event_code = models.CharField(max_length=255, blank=True, null=True)
team_name = models.CharField(max_length=255, blank=True, null=True)
zekken_number = models.CharField(max_length=255, blank=True, null=True)
return self.create_user(email, password, group, **other_fields)
objects = CustomUserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['firstname', 'lastname', 'userid', 'date_of_birth']
def __str__(self):
return self.email
class JpnAdminMainPerf(models.Model):
geom = models.MultiPolygonField(blank=True, null=True)
@ -195,6 +288,26 @@ class UserUploadUser(models.Model):
class CustomUser(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True)
firstname = models.CharField(max_length=255)
lastname = models.CharField(max_length=255)
userid = models.CharField(max_length=255, unique=True, editable=False)
date_of_birth = models.DateField()
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
event_code = models.CharField(max_length=255, blank=True, null=True)
team_name = models.CharField(max_length=255, blank=True, null=True)
zekken_number = models.CharField(max_length=255, blank=True, null=True)
objects = CustomUserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['firstname', 'lastname', 'date_of_birth']
def __str__(self):
return self.email
class CustomUser_old(AbstractBaseUser, PermissionsMixin):
class Groups(models.TextChoices):
GB1 = '大垣-初心者', '大垣-初心者'
GB2 = '大垣-3時間', '大垣-3時間'

View File

@ -1,14 +1,35 @@
from rest_framework import serializers
from rest_framework_gis.serializers import GeoFeatureModelSerializer
from sqlalchemy.sql.functions import mode
from .models import Location, Location_line, Location_polygon, JpnAdminMainPerf, Useractions, GifuAreas, RogUser, UserTracks, GoalImages, CheckinImages,CustomUser
from .models import Location, Location_line, Location_polygon, JpnAdminMainPerf, Useractions, GifuAreas, RogUser, UserTracks, GoalImages, CheckinImages,CustomUser,Team, Member, Entry
from drf_extra_fields.fields import Base64ImageField
from django.contrib.auth.hashers import make_password
#from django.contrib.auth.models import User
from .models import CustomUser
from django.contrib.auth import authenticate
from .models import TestModel
from .models import TempUser
class RegistrationSerializer(serializers.ModelSerializer):
password2 = serializers.CharField(style={"input_type": "password"}, write_only=True)
class Meta:
model = TempUser
fields = ['email', 'is_rogaining', 'zekken_number', 'event_code', 'team_name', 'group', 'password', 'password2']
extra_kwargs = {
'password': {'write_only': True}
}
def validate(self, attrs):
if attrs['password'] != attrs['password2']:
raise serializers.ValidationError({"password": "Password fields didn't match."})
return attrs
def create(self, validated_data):
validated_data.pop('password2')
return TempUser.objects.create(**validated_data)
class LocationCatSerializer(serializers.ModelSerializer):
@ -53,6 +74,38 @@ class JPN_main_perfSerializer(serializers.ModelSerializer):
# model=JpnAdminPerf
# fields=['id','et_id', 'et_right', 'et_left', 'adm2_l', 'adm1_l', 'adm0_l', 'adm0_r', 'adm1_r', 'adm2_r', 'admlevel']
#============= Akira ここから
class TeamSerializer(serializers.ModelSerializer):
class Meta:
model = Team
fields = ['zekken_number', 'team_name', 'password']
extra_kwargs = {'password': {'write_only': True}}
class MemberSerializer(serializers.ModelSerializer):
class Meta:
model = Member
fields = ['zekken_number', 'userid']
class EntrySerializer(serializers.ModelSerializer):
class Meta:
model = Entry
fields = ['zekken_number', 'event_code', 'date']
class CustomUserSerializer(serializers.ModelSerializer):
class Meta:
model = CustomUser
fields = ['email', 'firstname', 'lastname', 'userid', 'date_of_birth', 'password']
extra_kwargs = {
'password': {'write_only': True},
'userid': {'read_only': True}
}
def create(self, validated_data):
validated_data['password'] = make_password(validated_data.get('password'))
return super(CustomUserSerializer, self).create(validated_data)
#============= Akira ここまで
class GifuAreaSerializer(serializers.ModelSerializer):
class Meta:

View File

@ -1,7 +1,7 @@
from sys import prefix
from rest_framework import urlpatterns
from rest_framework.routers import DefaultRouter
from .views import LocationViewSet, Location_lineViewSet, Location_polygonViewSet, Jpn_Main_PerfViewSet, LocationsInPerf, ExtentForSubPerf, SubPerfInMainPerf, ExtentForMainPerf, LocationsInSubPerf, CatView, RegistrationAPI, LoginAPI, UserAPI, UserActionViewset, UserMakeActionViewset, UserDestinations, UpdateOrder, LocationInBound, DeleteDestination, CustomAreaLocations, GetAllGifuAreas, CustomAreaNames, userDetials, UserTracksViewSet, CatByCity, ChangePasswordView, GoalImageViewSet, CheckinImageViewSet, ExtentForLocations, DeleteAccount, PrivacyView, RegistrationView
from .views import LocationViewSet, Location_lineViewSet, Location_polygonViewSet, Jpn_Main_PerfViewSet, LocationsInPerf, ExtentForSubPerf, SubPerfInMainPerf, ExtentForMainPerf, LocationsInSubPerf, CatView, RegistrationAPI, LoginAPI, UserAPI, UserActionViewset, UserMakeActionViewset, UserDestinations, UpdateOrder, LocationInBound, DeleteDestination, CustomAreaLocations, GetAllGifuAreas, CustomAreaNames, userDetials, UserTracksViewSet, CatByCity, ChangePasswordView, GoalImageViewSet, CheckinImageViewSet, ExtentForLocations, DeleteAccount, PrivacyView, RegistrationView, VerifyEmailView, TeamViewSet, MemberViewSet, EntryViewSet
from django.urls import path, include
from knox import views as knox_views
@ -18,6 +18,17 @@ router.register(prefix='track', viewset=UserTracksViewSet, basename='track')
router.register(prefix='goalimage', viewset=GoalImageViewSet, basename='goalimage')
router.register(prefix='checkinimage', viewset=CheckinImageViewSet, basename='checkinimage')
#Akira 追加
# /api/teams/ - チームの一覧取得と作成
# /api/teams/<pk>/ - 特定のチームの取得、更新、削除
# /api/members/ - メンバーの一覧取得と作成
# /api/members/<pk>/ - 特定のメンバーの取得、更新、削除
# /api/entries/ - エントリーの一覧取得と作成
# /api/entries/<pk>/ - 特定のエントリーの取得、更新、削除
#
router.register(r'teams', TeamViewSet)
router.register(r'members', MemberViewSet)
router.register(r'entries', EntryViewSet)
urlpatterns = router.urls
@ -48,5 +59,7 @@ urlpatterns += [
path('delete-account/', DeleteAccount, name="delete-account"),
path('privacy/', PrivacyView, name='privacy-view'),
path('register', RegistrationView.as_view(), name='register'),
path('verify-email/<uuid:verification_code>/', VerifyEmailView.as_view(), name='verify_email'),
# path('goal-image/', GoalImageViewSet.as_view(), name='goal-image')
]

View File

@ -1,15 +1,15 @@
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
from rest_framework import viewsets
from .serializers import LocationSerializer, Location_lineSerializer, Location_polygonSerializer, JPN_main_perfSerializer, LocationCatSerializer, CreateUserSerializer, UserSerializer, LoginUserSerializer, UseractionsSerializer, UserDestinationSerializer, GifuAreaSerializer, LocationEventNameSerializer, RogUserSerializer, UserTracksSerializer, ChangePasswordSerializer, GolaImageSerializer, CheckinImageSerializer, RegistrationSerializer
from .models import GoalImages, Location, Location_line, Location_polygon, JpnAdminMainPerf, Useractions, GifuAreas, RogUser, CustomUser, UserTracks, GoalImages, CheckinImages, TempUser
from rest_framework import viewsets,status
from .serializers import LocationSerializer, Location_lineSerializer, Location_polygonSerializer, JPN_main_perfSerializer, LocationCatSerializer, CreateUserSerializer, UserSerializer, LoginUserSerializer, UseractionsSerializer, UserDestinationSerializer, GifuAreaSerializer, LocationEventNameSerializer, RogUserSerializer, UserTracksSerializer, ChangePasswordSerializer, GolaImageSerializer, CheckinImageSerializer, RegistrationSerializer, Team, Member, Entry, CustomUserSerializer
from knox.models import AuthToken
from rest_framework import viewsets, generics, status
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.parsers import JSONParser, MultiPartParser
from .serializers import LocationSerializer
from .serializers import LocationSerializer, TeamSerializer, MemberSerializer, EntrySerializer
from django.http import JsonResponse
from rest_framework.permissions import IsAuthenticated
from django.contrib.gis.db.models import Extent, Union
@ -29,8 +29,9 @@ from rest_framework.parsers import JSONParser, MultiPartParser
from django.views.decorators.csrf import csrf_exempt
import uuid
from django.shortcuts import render
from django.utils import timezone
from django.db import transaction
class LocationViewSet(viewsets.ModelViewSet):
queryset=Location.objects.all()
@ -55,6 +56,114 @@ class Jpn_Main_PerfViewSet(viewsets.ModelViewSet):
#===== AKira ここから
class CustomUserViewSet(viewsets.ModelViewSet):
queryset = CustomUser.objects.all()
serializer_class = CustomUserSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
return CustomUser.objects.filter(id=self.request.user.id)
class TeamViewSet(viewsets.ModelViewSet):
queryset = Team.objects.all()
serializer_class = TeamSerializer
@transaction.atomic
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
headers = self.get_success_headers(serializer.data)
# チーム登録後にエントリー登録と外部APIコールを行う
self.register_entry_and_call_external_api(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
def register_entry_and_call_external_api(self, team_data):
# エントリーの登録
entry_data = {
'zekken_number': team_data['zekken_number'],
'event_code': request.data.get('event_code'), # エントリー用のevent_codeを取得
'date': request.data.get('date') # エントリー用の日付を取得
}
entry_serializer = EntrySerializer(data=entry_data)
if entry_serializer.is_valid():
entry_serializer.save()
# 外部APIへのコール
self.call_external_api(team_data, entry_data)
else:
# エントリー登録に失敗した場合のエラーハンドリング
raise serializers.ValidationError(entry_serializer.errors)
def call_external_api(self, team_data, entry_data):
external_api_url = "https://rogaining.sumasen.net/gifuroge/register_team"
payload = {
'zekken_number': team_data['zekken_number'],
'event_code': entry_data['event_code'],
'team_name': team_data['team_name'],
'class_name': team_data.get('class_name', 'Default'), # class_nameがない場合はデフォルト値を設定
'password': team_data['password']
}
try:
response = requests.post(external_api_url, data=payload)
response.raise_for_status() # エラーレスポンスの場合は例外を発生させる
# レスポンスの処理(必要に応じて)
print(f"External API response: {response.json()}")
except requests.RequestException as e:
# 外部APIコールに失敗した場合のエラーハンドリング
print(f"Failed to call external API: {str(e)}")
# ここでエラーをログに記録したり、管理者に通知したりすることができます
def get_queryset(self):
user = self.request.user
return Team.objects.filter(member__userid=user)
class MemberViewSet(viewsets.ModelViewSet):
queryset = Member.objects.all()
serializer_class = MemberSerializer
permission_classes = [IsAuthenticated]
def create(self, request):
serializer = self.get_serializer(data=request.data)
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)
def get_queryset(self):
user = self.request.user
return Member.objects.filter(userid=user)
class EntryViewSet(viewsets.ModelViewSet):
queryset = Entry.objects.all()
serializer_class = EntrySerializer
permission_classes = [IsAuthenticated]
def create(self, request):
serializer = self.get_serializer(data=request.data)
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)
def get_queryset(self):
user = self.request.user
return Entry.objects.filter(zekken_number__member__userid=user)
#===== AKira ここまで
class UserTracksViewSet(viewsets.ModelViewSet):
queryset = UserTracks.objects.all()
serializer_class = UserTracksSerializer
@ -143,9 +252,10 @@ def LocationInBound(request):
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)
#locs = Location.objects.filter(~Q(cp=0), geom__within=pl, category=cat, event_name__isnull=True, group__contains=grp)
locs = Location.objects.filter(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)
locs = Location.objects.filter(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='観光')
@ -154,9 +264,9 @@ def LocationInBound(request):
else:
if is_rog:
if grp:
locs = Location.objects.filter(~Q(cp=0), geom__within=pl, event_name__isnull=True, group__contains=grp)
locs = Location.objects.filter(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)
locs = Location.objects.filter(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='観光')
@ -540,10 +650,57 @@ class TestActionViewSet(viewsets.ModelViewSet):
def PrivacyView(request):
return render(request, "rog/privacy.html")
class RegistrationView(APIView):
class RegistrationView_old(APIView):
def post(self, request):
serializer = RegistrationSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class RegistrationView(APIView):
def post(self, request):
serializer = RegistrationSerializer(data=request.data)
if serializer.is_valid():
temp_user = serializer.save()
verification_url = request.build_absolute_uri(
reverse('verify_email', kwargs={'verification_code': temp_user.verification_code})
)
send_mail(
'Verify your email',
f'Please click the link to verify your email: {verification_url}',
settings.DEFAULT_FROM_EMAIL,
[temp_user.email],
fail_silently=False,
)
return Response({"message": "Please check your email to complete registration."}, 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 not temp_user.is_valid():
temp_user.delete()
return Response({"error": "Verification link has expired. Please register again."}, status=status.HTTP_400_BAD_REQUEST)
user = CustomUser.objects.create_user(
email=temp_user.email,
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
)
temp_user.delete()
return Response({"message": "Email verified. Registration complete."}, status=status.HTTP_200_OK)
except TempUser.DoesNotExist:
return Response({"error": "Invalid verification code."}, status=status.HTTP_400_BAD_REQUEST)