23 Commits
exdb ... exdb-2

Author SHA1 Message Date
e01d2e7ea6 Supervisor update3 2024-10-30 06:57:51 +09:00
d017da17d4 supervisor step3 2024-10-29 14:07:31 +00:00
b872f377b2 Supervisor update 2 2024-10-29 00:56:12 +00:00
a6b816c9f2 new supervisor step2 2024-10-28 20:25:05 +00:00
2913a435c1 initialize supervisor...still has bugs 2024-10-28 02:20:28 +00:00
051916f9f6 永栄コードのマージ開始 2024-10-27 18:22:01 +00:00
b8d7029965 イベントにpublicや対応クラスなどを追加 2024-10-21 12:48:42 +00:00
6f0d8d15fd pre release 20240903 2024-09-03 13:19:30 +00:00
80ccaace3d Fixed FC-Gifu 2024-08-26 09:02:01 +00:00
95b787c819 version 4.8.17 -- カレンダーで今日以降しか選べないようにした 2024-08-09 23:49:36 +00:00
3d195973fc Stop tracking migrations and release 4.8.9 2024-08-04 18:56:11 +00:00
d851e7e4ad Release 4-8-6 2024-08-02 14:21:50 +00:00
9d0d3ea102 temp update 2024-08-02 07:01:32 +00:00
37a253e63a temp update 2024-08-01 17:50:15 +00:00
bc74b14cbc Basic release 1-Aug-2024 2024-08-01 07:51:52 +00:00
49b3ee7342 debug temp 2024-07-31 00:56:23 +00:00
26e8e68dbd initial update by Akira -- need to update email templates 2024-07-29 03:26:33 +00:00
44ad30093c API 95%完成。メール未テスト、早野API未テスト、ユーザー情報・削除は動いてない。 2024-07-26 14:54:24 +00:00
bcfcceb068 temporary update to debug email 2024-07-26 12:34:54 +00:00
9215ba8f9f API debugging 1 まだ問題あり 2024-07-26 04:03:15 +00:00
c0fb177d02 debugging entry 2024-07-25 00:57:48 +00:00
09e39987e2 temporary update 2024-07-24 00:38:32 +00:00
6f79d9a4be temp update 2024-07-22 06:53:48 +00:00
334 changed files with 111414 additions and 621 deletions

BIN
.DS_Store vendored

Binary file not shown.

6
.gitignore vendored
View File

@ -157,6 +157,10 @@ dmypy.json
# Cython debug symbols
cython_debug/
# migration files
rog/migrations/
# PyCharm
# JetBrains specific template is maintainted in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
@ -165,4 +169,4 @@ cython_debug/
#.idea/
# End of https://www.toptal.com/developers/gitignore/api/django
.DS_Store
.DS_Store

View File

@ -51,7 +51,10 @@ RUN pip install gunicorn
#RUN ["chmod", "+x", "wait-for.sh"]
RUN pip install -r requirements.txt
# xlsxwriterを追加
RUN pip install -r requirements.txt \
&& pip install django-cors-headers \
&& pip install xlsxwriter gunicorn
COPY . /app

35
Dockerfile.supervisor Normal file
View File

@ -0,0 +1,35 @@
FROM nginx:alpine
# Create necessary directories and set permissions
RUN mkdir -p /usr/share/nginx/html \
&& mkdir -p /var/log/nginx \
&& mkdir -p /var/cache/nginx \
&& chown -R nginx:nginx /usr/share/nginx/html \
&& chown -R nginx:nginx /var/log/nginx \
&& chown -R nginx:nginx /var/cache/nginx \
&& chmod -R 755 /usr/share/nginx/html
# Copy files - notice the change in the source path
COPY supervisor/html/* /usr/share/nginx/html/
COPY supervisor/nginx/default.conf /etc/nginx/conf.d/default.conf
# メディアディレクトリを作成
RUN mkdir -p /app/media && chmod 755 /app/media
# 静的ファイルをコピー
#COPY ./static /usr/share/nginx/html/static
# 権限の設定
RUN chown -R nginx:nginx /app/media
# Set final permissions
RUN chown -R nginx:nginx /usr/share/nginx/html \
&& chmod -R 755 /usr/share/nginx/html \
&& touch /var/log/nginx/access.log \
&& touch /var/log/nginx/error.log \
&& chown -R nginx:nginx /var/log/nginx \
&& chown -R nginx:nginx /etc/nginx/conf.d
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

View File

@ -48,15 +48,20 @@ INSTALLED_APPS = [
'django.contrib.staticfiles',
'django.contrib.gis',
'rest_framework',
'rest_framework.authtoken',
'rest_framework_gis',
'knox',
'leaflet',
'leaflet_admin_list',
'rog.apps.RogConfig',
'corsheaders', # added
'django_filters'
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware', # できるだけ上部に
'django.middleware.common.CommonMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
@ -68,10 +73,47 @@ MIDDLEWARE = [
ROOT_URLCONF = 'config.urls'
CORS_ALLOW_ALL_ORIGINS = True # 開発環境のみ
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOWED_METHODS = [
'GET',
'POST',
'PUT',
'PATCH',
'DELETE',
'OPTIONS'
]
CORS_ALLOWED_HEADERS = [
'accept',
'accept-encoding',
'authorization',
'content-type',
'dnt',
'origin',
'user-agent',
'x-csrftoken',
'x-requested-with',
]
# 本番環境では以下のように制限する
CORS_ALLOWED_ORIGINS = [
"https://rogaining.sumasen.net",
"http://rogaining.sumasen.net",
]
# CSRFの設定
CSRF_TRUSTED_ORIGINS = [
"http://rogaining.sumasen.net",
"https://rogaining.sumasen.net",
]
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
@ -138,10 +180,12 @@ USE_TZ = True
STATIC_URL = '/static/'
#STATIC_URL = '/static2/'
STATIC_ROOT = BASE_DIR / "static"
#STATIC_ROOT = BASE_DIR / "static"
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / "media/"
#MEDIA_ROOT = BASE_DIR / "media/"
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
#STATICFILES_DIRS = (os.path.join(BASE_DIR, "static2"),os.path.join(BASE_DIR, "media"))
@ -172,5 +216,92 @@ LEAFLET_CONFIG = {
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],
'DEFAULT_AUTHENTICATION_CLASSES': ('knox.auth.TokenAuthentication', ),
}
'DEFAULT_AUTHENTICATION_CLASSES': ['knox.auth.TokenAuthentication','rest_framework.authentication.TokenAuthentication', ],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
],
}
#FRONTEND_URL = 'https://rogaining.intranet.sumasen.net' # フロントエンドのURLに適宜変更してください
FRONTEND_URL = 'https://rogaining.sumasen.net' # フロントエンドのURLに適宜変更してください
# この設定により、メールは実際には送信されず、代わりにコンソールに出力されます。
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.outlook.com'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_HOST_USER = 'rogaining@gifuai.net'
EMAIL_HOST_PASSWORD = 'ctcpy9823"x~'
DEFAULT_FROM_EMAIL = 'rogaining@gifuai.net'
APP_DOWNLOAD_LINK = 'https://apps.apple.com/jp/app/%E5%B2%90%E9%98%9C%E3%83%8A%E3%83%93/id6444221792'
ANDROID_DOWNLOAD_LINK = 'https://play.google.com/store/apps/details?id=com.dvox.gifunavi&hl=ja'
SERVICE_NAME = '岐阜ナビ(岐阜ロゲのアプリ)'
# settings.py
DEFAULT_CHARSET = 'utf-8'
#REST_FRAMEWORK = {
# 'DEFAULT_RENDERER_CLASSES': [
# 'rest_framework.renderers.JSONRenderer',
# ],
# 'JSON_UNICODE_ESCAPE': False,
#}
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '{levelname} {asctime} {module} {message}',
'style': '{',
},
},
'handlers': {
#'file': {
# 'level': 'DEBUG',
# 'class': 'logging.FileHandler',
# 'filename': os.path.join(BASE_DIR, 'logs/debug.log'),
# 'formatter': 'verbose',
#},
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'verbose',
},
},
'root': {
'handlers': ['console'],
'level': 'DEBUG',
},
'loggers': {
'django': {
'handlers': ['console'],
'level': 'INFO',
'propagate': False,
},
'django.request': {
'handlers': ['console'],
'level': 'DEBUG',
},
'rog': {
#'handlers': ['file','console'],
'handlers': ['console'],
'level': 'DEBUG',
'propagate': True,
},
},
}
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
'django.contrib.auth.hashers.Argon2PasswordHasher',
'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
]
BLACKLISTED_IPS = ['44.230.58.114'] # ブロックしたい IP アドレスをここにリストとして追加

View File

@ -18,6 +18,21 @@ from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
# debug_urlsビューをrogアプリケーションのviewsからインポート
from rog import views as rog_views
DEBUG = True
ALLOWED_HOSTS = ['rogaining.sumasen.net', 'localhost', '127.0.0.1']
# CORSの設定
CORS_ALLOW_ALL_ORIGINS = True
CORS_ALLOWED_ORIGINS = [
"http://rogaining.sumasen.net",
"http://localhost",
"http://127.0.0.1",
]
urlpatterns = [
path('admin/', admin.site.urls),
path('auth/', include('knox.urls')),
@ -27,3 +42,8 @@ urlpatterns = [
admin.site.site_header = "ROGANING"
admin.site.site_title = "Roganing Admin Portal"
admin.site.index_title = "Welcome to Roganing Portal"
# 開発環境での静的ファイル配信
if settings.DEBUG:
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

View File

@ -30,12 +30,41 @@ services:
env_file:
- .env
restart: "on-failure"
# depends_on:
# - postgres-db
networks:
- rog-api
#entrypoint: ["/app/wait-for.sh", "postgres-db:5432", "--", ""]
#command: python3 manage.py runserver 0.0.0.0:8100
supervisor-web:
build:
context: .
dockerfile: Dockerfile.supervisor
volumes:
- type: bind
source: ./supervisor/html
target: /usr/share/nginx/html/supervisor
read_only: true
- type: bind
source: ./supervisor/nginx/default.conf
target: /etc/nginx/conf.d/default.conf
read_only: true
- type: volume
source: static_volume
target: /app/static
read_only: true
- type: volume
source: nginx_logs
target: /var/log/nginx
- type: bind
source: ./media
target: /usr/share/nginx/html/media
ports:
- "80:80"
depends_on:
- api
networks:
- rog-api
restart: always
networks:
rog-api:
@ -44,3 +73,5 @@ networks:
volumes:
postgres_data:
geoserver-data:
static_volume:
nginx_logs:

81
docker-compose.yaml.ssl Normal file
View File

@ -0,0 +1,81 @@
version: "3.9"
services:
# postgres-db:
# image: kartoza/postgis:12.0
# ports:
# - 5432:5432
# volumes:
# - postgres_data:/var/lib/postgresql
# - ./custom-postgresql.conf:/etc/postgresql/12/main/postgresql.conf
# environment:
# - POSTGRES_USER=${POSTGRES_USER}
# - POSTGRES_PASS=${POSTGRES_PASS}
# - POSTGRES_DBNAME=${POSTGRES_DBNAME}
# - POSTGRES_MAX_CONNECTIONS=600
# restart: "on-failure"
# networks:
# - rog-api
api:
build:
context: .
dockerfile: Dockerfile.gdal
command: python3 manage.py runserver 0.0.0.0:8100
volumes:
- .:/app
ports:
- 8100:8100
env_file:
- .env
restart: "on-failure"
# depends_on:
# - postgres-db
networks:
- rog-api
#entrypoint: ["/app/wait-for.sh", "postgres-db:5432", "--", ""]
#command: python3 manage.py runserver 0.0.0.0:8100
supervisor-web:
build:
context: .
dockerfile: Dockerfile.supervisor
volumes:
- type: bind
source: /etc/letsencrypt
target: /etc/nginx/ssl
read_only: true
- type: bind
source: ./supervisor/html
target: /usr/share/nginx/html
read_only: true
- type: bind
source: ./supervisor/nginx/default.conf
target: /etc/nginx/conf.d/default.conf
read_only: true
- type: volume
source: static_volume
target: /app/static
read_only: true
- type: volume
source: nginx_logs
target: /var/log/nginx
ports:
- "80:80"
depends_on:
- api
networks:
- rog-api
restart: always
networks:
rog-api:
driver: bridge
volumes:
postgres_data:
geoserver-data:
static_volume:
nginx_logs:

1
dump_rog_data.sql Normal file
View File

@ -0,0 +1 @@
pg_dump: error: connection to database "rogdb" failed: FATAL: Peer authentication failed for user "postgres"

10
entrypoint.sh Normal file
View File

@ -0,0 +1,10 @@
#!/bin/sh
# Collect static files
python manage.py collectstatic --noinput
# Apply database migrations
python manage.py migrate
# Start Gunicorn
exec "$@"

View File

@ -26,29 +26,33 @@ http {
#gzip on;
server {
listen 80;
server_name localhost;
listen 80;
server_name localhost;
# 静的ファイルの提供
location /static/ {
alias /app/static/;
alias /app/static/;
}
location /media/ {
alias /app/media/;
}
# スーパーバイザー Web アプリケーション
location / {
proxy_pass http://app:8000;
root /usr/share/nginx/html;
index index.html;
try_files $uri $uri/ /index.html;
}
# Django API プロキシ
location /api/ {
proxy_pass http://api:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
error_page 500 502 503 504 /50x.html;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
root /usr/share/nginx/html;
}
}
}

View File

@ -65,3 +65,4 @@ django-extra-fields==3.0.2
django-phonenumber-field==6.1.0
django-rest-knox==4.2.0
dj-database-url==2.0.0
django-cors-headers==4.3.0

BIN
rog/.DS_Store vendored

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -3,12 +3,18 @@ from django.conf import settings
from .models import CustomUser
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
from django.contrib.auth.hashers import check_password
import logging
logger = logging.getLogger(__name__)
class EmailOrUsernameModelBackend(ModelBackend):
"""
This is a ModelBacked that allows authentication
with either a username or an email address.
"""
"""
def authenticate(self, username=None, password=None):
if '@' in username:
@ -26,4 +32,35 @@ class EmailOrUsernameModelBackend(ModelBackend):
try:
return CustomUser.objects.get(pk=username)
except get_user_model().DoesNotExist:
return None
return None
"""
def authenticate(self, request, username=None, password=None, **kwargs):
if '@' in username:
kwargs = {'email': username}
else:
kwargs = {'username': username}
try:
user = CustomUser.objects.get(**kwargs)
if check_password(password, user.password):
logger.info(f"User authenticated successfully: {username}")
return user
else:
logger.warning(f"Password mismatch for user: {username}")
except CustomUser.DoesNotExist:
logger.warning(f"User does not exist: {username}")
except Exception as e:
logger.error(f"Authentication error for {username}: {str(e)}")
return None
def get_user(self, user_id):
try:
user = CustomUser.objects.get(pk=user_id)
logger.info(f"User retrieved: {user.username or user.email}")
return user
except CustomUser.DoesNotExist:
logger.warning(f"User with id {user_id} does not exist")
return None
except Exception as e:
logger.error(f"Error retrieving user with id {user_id}: {str(e)}")
return None

7
rog/forms.py Normal file
View File

@ -0,0 +1,7 @@
from django import forms
from .models import NewEvent2
class CSVUploadForm(forms.Form):
event = forms.ModelChoiceField(queryset=NewEvent2.objects.all(), label="イベント選択")
csv_file = forms.FileField(label="CSVファイル")

40
rog/gifuroge_team.csv Normal file
View File

@ -0,0 +1,40 @@
4019,関ケ原2,Best Wishes,ソロ女子-5時間,pbkdf2_sha256$260000$RPvncicp11ENXxwpcpMXi1$9e/fKcfwaX3sJ91q9S70KWQcrNlraliguiHjF/UCW/I=
4010,関ケ原2,まつげん,ソロ女子-5時間,pbkdf2_sha256$260000$LMvH0KtHeHbCuuUZ5n88VZ$Lnsqs/u45QKoFN6lUdqC79nIMz5LwaKWMpmX/0aEXa8=
4021,大垣3,まつげん,ソロ女子-5時間,pbkdf2_sha256$260000$LMvH0KtHeHbCuuUZ5n88VZ$Lnsqs/u45QKoFN6lUdqC79nIMz5LwaKWMpmX/0aEXa8=
5,関ケ原2,てすとあきら1,ソロ男子-5時間,pbkdf2_sha256$260000$0GY5pt5V127jGd8HkkEort$8ZL0eY2qTZHydyzUUN5LNKZnmmibfu1x3QQ/7rJX1Vc=
3003,関ケ原2,てすとあきら1,ソロ男子-5時間,pbkdf2_sha256$260000$0GY5pt5V127jGd8HkkEort$8ZL0eY2qTZHydyzUUN5LNKZnmmibfu1x3QQ/7rJX1Vc=
3115,関ケ原2,Best Wishes,ソロ男子-5時間,pbkdf2_sha256$260000$tlNrgHyqDtfbM9f3GLv5G1$jRcR/ieTB174TZ9jW7obCBUMpyz86aywqDKw3VmhVQQ=
1010,大垣3,ハヤノテスト,一般-5時間,pbkdf2_sha256$260000$IeGmRkkUkwXXc1zO9oxvCe$ijnJTH7xhwidit+uCggSgjj/7g/vMK539IpOMA5GlnM=
1012,大垣3,てすとあきら1,一般-5時間,pbkdf2_sha256$260000$0GY5pt5V127jGd8HkkEort$8ZL0eY2qTZHydyzUUN5LNKZnmmibfu1x3QQ/7rJX1Vc=
1014,各務原2,てすとあきら1,一般-5時間,pbkdf2_sha256$260000$0GY5pt5V127jGd8HkkEort$8ZL0eY2qTZHydyzUUN5LNKZnmmibfu1x3QQ/7rJX1Vc=
1018,下呂2,てすとあきら1,一般-5時間,pbkdf2_sha256$260000$0GY5pt5V127jGd8HkkEort$8ZL0eY2qTZHydyzUUN5LNKZnmmibfu1x3QQ/7rJX1Vc=
1024,関ケ原2,てすとあきら1,一般-5時間,pbkdf2_sha256$260000$0GY5pt5V127jGd8HkkEort$8ZL0eY2qTZHydyzUUN5LNKZnmmibfu1x3QQ/7rJX1Vc=
1026,美濃加茂2,てすとあきら1,一般-5時間,pbkdf2_sha256$260000$0GY5pt5V127jGd8HkkEort$8ZL0eY2qTZHydyzUUN5LNKZnmmibfu1x3QQ/7rJX1Vc=
1028,多治見2,てすとあきら1,一般-5時間,pbkdf2_sha256$260000$0GY5pt5V127jGd8HkkEort$8ZL0eY2qTZHydyzUUN5LNKZnmmibfu1x3QQ/7rJX1Vc=
3006,関ケ原2,山本哲也,ソロ男子-5時間,pbkdf2_sha256$260000$EkYrRHZwKunjO4jiHvxyB2$kYGN0STzV9c70IKAIxK1Ija3K1y90+ote0HDTP+iSPw=
3009,養老2,山本哲也,ソロ男子-5時間,pbkdf2_sha256$260000$EkYrRHZwKunjO4jiHvxyB2$kYGN0STzV9c70IKAIxK1Ija3K1y90+ote0HDTP+iSPw=
3011,郡上2,山本哲也,ソロ男子-5時間,pbkdf2_sha256$260000$EkYrRHZwKunjO4jiHvxyB2$kYGN0STzV9c70IKAIxK1Ija3K1y90+ote0HDTP+iSPw=
3013,大垣3,山本哲也,ソロ男子-5時間,pbkdf2_sha256$260000$EkYrRHZwKunjO4jiHvxyB2$kYGN0STzV9c70IKAIxK1Ija3K1y90+ote0HDTP+iSPw=
3015,各務原2,山本哲也,ソロ男子-5時間,pbkdf2_sha256$260000$EkYrRHZwKunjO4jiHvxyB2$kYGN0STzV9c70IKAIxK1Ija3K1y90+ote0HDTP+iSPw=
3017,多治見2,山本哲也,ソロ男子-5時間,pbkdf2_sha256$260000$EkYrRHZwKunjO4jiHvxyB2$kYGN0STzV9c70IKAIxK1Ija3K1y90+ote0HDTP+iSPw=
3019,下呂2,山本哲也,ソロ男子-5時間,pbkdf2_sha256$260000$EkYrRHZwKunjO4jiHvxyB2$kYGN0STzV9c70IKAIxK1Ija3K1y90+ote0HDTP+iSPw=
3021,高山2,山本哲也,ソロ男子-5時間,pbkdf2_sha256$260000$EkYrRHZwKunjO4jiHvxyB2$kYGN0STzV9c70IKAIxK1Ija3K1y90+ote0HDTP+iSPw=
3023,美濃加茂2,山本哲也,ソロ男子-5時間,pbkdf2_sha256$260000$EkYrRHZwKunjO4jiHvxyB2$kYGN0STzV9c70IKAIxK1Ija3K1y90+ote0HDTP+iSPw=
4008,下呂2,GO!GO!YOKO,ソロ女子-5時間,pbkdf2_sha256$260000$tuv8ajw2VSmCooIxNHJhdD$m7q0fqPIsAs7L9uubt+PUVsmexwpJPXPCgVs9GjY12c=
3121,関ケ原2,yamadeus,ソロ男子-5時間,pbkdf2_sha256$260000$sCLRTCAxQIClyDmvfbMDm0$cU3dSGTPwKHl8T3EBZ6R19oZJGkadD48pKqywAhtJOk=
3126,大垣3,yamadeus,ソロ男子-5時間,pbkdf2_sha256$260000$7KsSngw2Ho719jpXsOrC8v$jfHFxglG/L0htA13t01LAy91dS+FnlAZubg6Lmd/m2Y=
3128,多治見2,MASA,ソロ男子-5時間,pbkdf2_sha256$260000$qpaSbqryD4f5bZaY893Ug4$Gk8XuqsJbSkX9Hxrl/xg9LtjM8JQkpgNkpbbNzTmhzY=
3124,関ケ原2,yamadeus,ソロ男子-5時間,pbkdf2_sha256$260000$7KsSngw2Ho719jpXsOrC8v$jfHFxglG/L0htA13t01LAy91dS+FnlAZubg6Lmd/m2Y=
3132,各務原2,岐阜市イイとこあるある探検隊,ソロ男子-5時間,pbkdf2_sha256$260000$QWc5BpSBUbkUwP9UlIzyE5$do+VKkH8mNibg6PJDsm6AJ/VMFh3NWdzwZ9IQW/26xA=
3135,大垣3,akira,ソロ男子-5時間,pbkdf2_sha256$260000$mmM2N8sSE84YaNNuDzQKxb$ox9U6rdgZq4ANzi4NizskphZWIrf7o2+JEfvC4wcn7U=
3137,関ケ原2,akira,ソロ男子-5時間,pbkdf2_sha256$260000$mmM2N8sSE84YaNNuDzQKxb$ox9U6rdgZq4ANzi4NizskphZWIrf7o2+JEfvC4wcn7U=
3139,養老2,akira,ソロ男子-5時間,pbkdf2_sha256$260000$mmM2N8sSE84YaNNuDzQKxb$ox9U6rdgZq4ANzi4NizskphZWIrf7o2+JEfvC4wcn7U=
3073,養老2,yamadeus,ソロ男子-5時間,pbkdf2_sha256$260000$sCLRTCAxQIClyDmvfbMDm0$cU3dSGTPwKHl8T3EBZ6R19oZJGkadD48pKqywAhtJOk=
3075,高山2,yamadeus,ソロ男子-5時間,pbkdf2_sha256$260000$sCLRTCAxQIClyDmvfbMDm0$cU3dSGTPwKHl8T3EBZ6R19oZJGkadD48pKqywAhtJOk=
3077,郡上2,yamadeus,ソロ男子-5時間,pbkdf2_sha256$260000$sCLRTCAxQIClyDmvfbMDm0$cU3dSGTPwKHl8T3EBZ6R19oZJGkadD48pKqywAhtJOk=
3081,美濃加茂2,yamadeus,ソロ男子-5時間,pbkdf2_sha256$260000$sCLRTCAxQIClyDmvfbMDm0$cU3dSGTPwKHl8T3EBZ6R19oZJGkadD48pKqywAhtJOk=
3083,多治見2,yamadeus,ソロ男子-5時間,pbkdf2_sha256$260000$sCLRTCAxQIClyDmvfbMDm0$cU3dSGTPwKHl8T3EBZ6R19oZJGkadD48pKqywAhtJOk=
3085,各務原2,yamadeus,ソロ男子-5時間,pbkdf2_sha256$260000$sCLRTCAxQIClyDmvfbMDm0$cU3dSGTPwKHl8T3EBZ6R19oZJGkadD48pKqywAhtJOk=
3079,下呂2,yamadeus,ソロ男子-5時間,pbkdf2_sha256$260000$sCLRTCAxQIClyDmvfbMDm0$cU3dSGTPwKHl8T3EBZ6R19oZJGkadD48pKqywAhtJOk=
3093,関ケ原2,岐阜愛,ソロ男子-5時間,pbkdf2_sha256$260000$LFOINdd30aKaXoT9CNYY8A$eoAzV10+gp+tufabtcFOx6uoOktZUngzzDJ0WWs/v24=
3099,高山2,岐阜愛,ソロ男子-5時間,pbkdf2_sha256$260000$LFOINdd30aKaXoT9CNYY8A$eoAzV10+gp+tufabtcFOx6uoOktZUngzzDJ0WWs/v24=
1 4019 関ケ原2 Best Wishes ソロ女子-5時間 pbkdf2_sha256$260000$RPvncicp11ENXxwpcpMXi1$9e/fKcfwaX3sJ91q9S70KWQcrNlraliguiHjF/UCW/I=
2 4010 関ケ原2 まつげん ソロ女子-5時間 pbkdf2_sha256$260000$LMvH0KtHeHbCuuUZ5n88VZ$Lnsqs/u45QKoFN6lUdqC79nIMz5LwaKWMpmX/0aEXa8=
3 4021 大垣3 まつげん ソロ女子-5時間 pbkdf2_sha256$260000$LMvH0KtHeHbCuuUZ5n88VZ$Lnsqs/u45QKoFN6lUdqC79nIMz5LwaKWMpmX/0aEXa8=
4 5 関ケ原2 てすとあきら1 ソロ男子-5時間 pbkdf2_sha256$260000$0GY5pt5V127jGd8HkkEort$8ZL0eY2qTZHydyzUUN5LNKZnmmibfu1x3QQ/7rJX1Vc=
5 3003 関ケ原2 てすとあきら1 ソロ男子-5時間 pbkdf2_sha256$260000$0GY5pt5V127jGd8HkkEort$8ZL0eY2qTZHydyzUUN5LNKZnmmibfu1x3QQ/7rJX1Vc=
6 3115 関ケ原2 Best Wishes ソロ男子-5時間 pbkdf2_sha256$260000$tlNrgHyqDtfbM9f3GLv5G1$jRcR/ieTB174TZ9jW7obCBUMpyz86aywqDKw3VmhVQQ=
7 1010 大垣3 ハヤノテスト 一般-5時間 pbkdf2_sha256$260000$IeGmRkkUkwXXc1zO9oxvCe$ijnJTH7xhwidit+uCggSgjj/7g/vMK539IpOMA5GlnM=
8 1012 大垣3 てすとあきら1 一般-5時間 pbkdf2_sha256$260000$0GY5pt5V127jGd8HkkEort$8ZL0eY2qTZHydyzUUN5LNKZnmmibfu1x3QQ/7rJX1Vc=
9 1014 各務原2 てすとあきら1 一般-5時間 pbkdf2_sha256$260000$0GY5pt5V127jGd8HkkEort$8ZL0eY2qTZHydyzUUN5LNKZnmmibfu1x3QQ/7rJX1Vc=
10 1018 下呂2 てすとあきら1 一般-5時間 pbkdf2_sha256$260000$0GY5pt5V127jGd8HkkEort$8ZL0eY2qTZHydyzUUN5LNKZnmmibfu1x3QQ/7rJX1Vc=
11 1024 関ケ原2 てすとあきら1 一般-5時間 pbkdf2_sha256$260000$0GY5pt5V127jGd8HkkEort$8ZL0eY2qTZHydyzUUN5LNKZnmmibfu1x3QQ/7rJX1Vc=
12 1026 美濃加茂2 てすとあきら1 一般-5時間 pbkdf2_sha256$260000$0GY5pt5V127jGd8HkkEort$8ZL0eY2qTZHydyzUUN5LNKZnmmibfu1x3QQ/7rJX1Vc=
13 1028 多治見2 てすとあきら1 一般-5時間 pbkdf2_sha256$260000$0GY5pt5V127jGd8HkkEort$8ZL0eY2qTZHydyzUUN5LNKZnmmibfu1x3QQ/7rJX1Vc=
14 3006 関ケ原2 山本哲也 ソロ男子-5時間 pbkdf2_sha256$260000$EkYrRHZwKunjO4jiHvxyB2$kYGN0STzV9c70IKAIxK1Ija3K1y90+ote0HDTP+iSPw=
15 3009 養老2 山本哲也 ソロ男子-5時間 pbkdf2_sha256$260000$EkYrRHZwKunjO4jiHvxyB2$kYGN0STzV9c70IKAIxK1Ija3K1y90+ote0HDTP+iSPw=
16 3011 郡上2 山本哲也 ソロ男子-5時間 pbkdf2_sha256$260000$EkYrRHZwKunjO4jiHvxyB2$kYGN0STzV9c70IKAIxK1Ija3K1y90+ote0HDTP+iSPw=
17 3013 大垣3 山本哲也 ソロ男子-5時間 pbkdf2_sha256$260000$EkYrRHZwKunjO4jiHvxyB2$kYGN0STzV9c70IKAIxK1Ija3K1y90+ote0HDTP+iSPw=
18 3015 各務原2 山本哲也 ソロ男子-5時間 pbkdf2_sha256$260000$EkYrRHZwKunjO4jiHvxyB2$kYGN0STzV9c70IKAIxK1Ija3K1y90+ote0HDTP+iSPw=
19 3017 多治見2 山本哲也 ソロ男子-5時間 pbkdf2_sha256$260000$EkYrRHZwKunjO4jiHvxyB2$kYGN0STzV9c70IKAIxK1Ija3K1y90+ote0HDTP+iSPw=
20 3019 下呂2 山本哲也 ソロ男子-5時間 pbkdf2_sha256$260000$EkYrRHZwKunjO4jiHvxyB2$kYGN0STzV9c70IKAIxK1Ija3K1y90+ote0HDTP+iSPw=
21 3021 高山2 山本哲也 ソロ男子-5時間 pbkdf2_sha256$260000$EkYrRHZwKunjO4jiHvxyB2$kYGN0STzV9c70IKAIxK1Ija3K1y90+ote0HDTP+iSPw=
22 3023 美濃加茂2 山本哲也 ソロ男子-5時間 pbkdf2_sha256$260000$EkYrRHZwKunjO4jiHvxyB2$kYGN0STzV9c70IKAIxK1Ija3K1y90+ote0HDTP+iSPw=
23 4008 下呂2 GO!GO!YOKO ソロ女子-5時間 pbkdf2_sha256$260000$tuv8ajw2VSmCooIxNHJhdD$m7q0fqPIsAs7L9uubt+PUVsmexwpJPXPCgVs9GjY12c=
24 3121 関ケ原2 yamadeus ソロ男子-5時間 pbkdf2_sha256$260000$sCLRTCAxQIClyDmvfbMDm0$cU3dSGTPwKHl8T3EBZ6R19oZJGkadD48pKqywAhtJOk=
25 3126 大垣3 yamadeus ソロ男子-5時間 pbkdf2_sha256$260000$7KsSngw2Ho719jpXsOrC8v$jfHFxglG/L0htA13t01LAy91dS+FnlAZubg6Lmd/m2Y=
26 3128 多治見2 MASA ソロ男子-5時間 pbkdf2_sha256$260000$qpaSbqryD4f5bZaY893Ug4$Gk8XuqsJbSkX9Hxrl/xg9LtjM8JQkpgNkpbbNzTmhzY=
27 3124 関ケ原2 yamadeus ソロ男子-5時間 pbkdf2_sha256$260000$7KsSngw2Ho719jpXsOrC8v$jfHFxglG/L0htA13t01LAy91dS+FnlAZubg6Lmd/m2Y=
28 3132 各務原2 岐阜市イイとこあるある探検隊 ソロ男子-5時間 pbkdf2_sha256$260000$QWc5BpSBUbkUwP9UlIzyE5$do+VKkH8mNibg6PJDsm6AJ/VMFh3NWdzwZ9IQW/26xA=
29 3135 大垣3 akira ソロ男子-5時間 pbkdf2_sha256$260000$mmM2N8sSE84YaNNuDzQKxb$ox9U6rdgZq4ANzi4NizskphZWIrf7o2+JEfvC4wcn7U=
30 3137 関ケ原2 akira ソロ男子-5時間 pbkdf2_sha256$260000$mmM2N8sSE84YaNNuDzQKxb$ox9U6rdgZq4ANzi4NizskphZWIrf7o2+JEfvC4wcn7U=
31 3139 養老2 akira ソロ男子-5時間 pbkdf2_sha256$260000$mmM2N8sSE84YaNNuDzQKxb$ox9U6rdgZq4ANzi4NizskphZWIrf7o2+JEfvC4wcn7U=
32 3073 養老2 yamadeus ソロ男子-5時間 pbkdf2_sha256$260000$sCLRTCAxQIClyDmvfbMDm0$cU3dSGTPwKHl8T3EBZ6R19oZJGkadD48pKqywAhtJOk=
33 3075 高山2 yamadeus ソロ男子-5時間 pbkdf2_sha256$260000$sCLRTCAxQIClyDmvfbMDm0$cU3dSGTPwKHl8T3EBZ6R19oZJGkadD48pKqywAhtJOk=
34 3077 郡上2 yamadeus ソロ男子-5時間 pbkdf2_sha256$260000$sCLRTCAxQIClyDmvfbMDm0$cU3dSGTPwKHl8T3EBZ6R19oZJGkadD48pKqywAhtJOk=
35 3081 美濃加茂2 yamadeus ソロ男子-5時間 pbkdf2_sha256$260000$sCLRTCAxQIClyDmvfbMDm0$cU3dSGTPwKHl8T3EBZ6R19oZJGkadD48pKqywAhtJOk=
36 3083 多治見2 yamadeus ソロ男子-5時間 pbkdf2_sha256$260000$sCLRTCAxQIClyDmvfbMDm0$cU3dSGTPwKHl8T3EBZ6R19oZJGkadD48pKqywAhtJOk=
37 3085 各務原2 yamadeus ソロ男子-5時間 pbkdf2_sha256$260000$sCLRTCAxQIClyDmvfbMDm0$cU3dSGTPwKHl8T3EBZ6R19oZJGkadD48pKqywAhtJOk=
38 3079 下呂2 yamadeus ソロ男子-5時間 pbkdf2_sha256$260000$sCLRTCAxQIClyDmvfbMDm0$cU3dSGTPwKHl8T3EBZ6R19oZJGkadD48pKqywAhtJOk=
39 3093 関ケ原2 岐阜愛 ソロ男子-5時間 pbkdf2_sha256$260000$LFOINdd30aKaXoT9CNYY8A$eoAzV10+gp+tufabtcFOx6uoOktZUngzzDJ0WWs/v24=
40 3099 高山2 岐阜愛 ソロ男子-5時間 pbkdf2_sha256$260000$LFOINdd30aKaXoT9CNYY8A$eoAzV10+gp+tufabtcFOx6uoOktZUngzzDJ0WWs/v24=

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

@ -0,0 +1,3 @@
from .ip_blocking import IPBlockingMiddleware
__all__ = ['IPBlockingMiddleware']

View File

@ -0,0 +1,42 @@
from django.core.exceptions import PermissionDenied
from django.core.cache import cache
from django.conf import settings
class IPBlockingMiddleware:
def __init__(self, get_response):
self.get_response = get_response
# 事前にブロックする IP アドレスのリスト
self.blacklisted_ips = getattr(settings, 'BLACKLISTED_IPS', [])
def __call__(self, request):
ip = self.get_client_ip(request)
# キャッシュからブロックリストを取得
blocked_ips = cache.get('blocked_ips', set())
# 事前にブロックされた IP またはキャッシュ内のブロックされた IP をチェック
if ip in self.blacklisted_ips or ip in blocked_ips:
raise PermissionDenied
# 不正アクセスの検出ロジックをここに実装
if self.is_suspicious(ip):
blocked_ips.add(ip)
cache.set('blocked_ips', blocked_ips, timeout=3600) # 1時間ブロック
raise PermissionDenied
response = self.get_response(request)
return response
def is_suspicious(self, ip):
request_count = cache.get(f'request_count_{ip}', 0)
cache.set(f'request_count_{ip}', request_count + 1, timeout=60)
return request_count > 100 # 1分間に100回以上のリクエストがあれば不審と判断
def get_client_ip(self, request):
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = request.META.get('REMOTE_ADDR')
return ip

148
rog/migration_scripts.py Normal file
View File

@ -0,0 +1,148 @@
"""
このコードは永栄コードをNoufferコードに統合するための一時変換コードです。
一旦、完全にマイグレーションでき、ランキングや走行履歴が完成したら、不要になります。
"""
import psycopg2
from PIL import Image
import PIL.ExifTags
from datetime import datetime
import os
def get_gps_from_image(image_path):
"""
画像ファイルからGPS情報を抽出する
Returns: (latitude, longitude) または取得できない場合は (None, None)
"""
try:
with Image.open(image_path) as img:
exif = {
PIL.ExifTags.TAGS[k]: v
for k, v in img._getexif().items()
if k in PIL.ExifTags.TAGS
}
if 'GPSInfo' in exif:
gps_info = exif['GPSInfo']
# 緯度の計算
lat = gps_info[2]
lat = lat[0] + lat[1]/60 + lat[2]/3600
if gps_info[1] == 'S':
lat = -lat
# 経度の計算
lon = gps_info[4]
lon = lon[0] + lon[1]/60 + lon[2]/3600
if gps_info[3] == 'W':
lon = -lon
return lat, lon
except Exception as e:
print(f"GPS情報の抽出に失敗: {e}")
return None, None
def migrate_data():
# コンテナ環境用の接続情報
source_db = {
'dbname': 'gifuroge',
'user': 'admin', # 環境に合わせて変更
'password': 'admin123456', # 環境に合わせて変更
'host': 'localhost', # Dockerのサービス名
'port': '5432'
}
target_db = {
'dbname': 'rogdb',
'user': 'admin', # 環境に合わせて変更
'password': 'admin123456', # 環境に合わせて変更
'host': 'localhost', # Dockerのサービス名
'port': '5432'
}
source_conn = None
target_conn = None
source_cur = None
target_cur = None
try:
print("ソースDBへの接続を試みています...")
source_conn = psycopg2.connect(**source_db)
source_cur = source_conn.cursor()
print("ソースDBへの接続が成功しました")
print("ターゲットDBへの接続を試みています...")
target_conn = psycopg2.connect(**target_db)
target_cur = target_conn.cursor()
print("ターゲットDBへの接続が成功しました")
print("データの取得を開始します...")
source_cur.execute("""
SELECT serial_number, zekken_number, event_code, cp_number, image_address,
goal_time, late_point, create_at, create_user,
update_at, update_user, buy_flag, colabo_company_memo
FROM gps_information
""")
rows = source_cur.fetchall()
print(f"取得したレコード数: {len(rows)}")
processed_count = 0
for row in rows:
(serial_number, zekken_number, event_code, cp_number, image_address,
goal_time, late_point, create_at, create_user,
update_at, update_user, buy_flag, colabo_company_memo) = row
latitude, longitude = None, None
if image_address and os.path.exists(image_address):
latitude, longitude = get_gps_from_image(image_address)
target_cur.execute("""
INSERT INTO gps_checkins (
path_order, zekken_number, event_code, cp_number,
lattitude, longitude, image_address,
image_receipt, image_QR, validate_location,
goal_time, late_point, create_at,
create_user, update_at, update_user,
buy_flag, colabo_company_memo, points
) VALUES (
%s, %s, %s, %s, %s, %s, %s, %s, %s, %s,
%s, %s, %s, %s, %s, %s, %s, %s, %s
)
""", (
serial_number,
zekken_number, event_code, cp_number,
latitude, longitude, image_address,
True, True, True,
goal_time, late_point, create_at,
create_user, update_at, update_user,
buy_flag if buy_flag is not None else False,
colabo_company_memo if colabo_company_memo else '',
0
))
processed_count += 1
if processed_count % 100 == 0:
print(f"処理済みレコード数: {processed_count}")
target_conn.commit()
print(f"移行完了: {processed_count}件のレコードを処理しました")
except Exception as e:
print(f"エラーが発生しました: {e}")
if target_conn:
target_conn.rollback()
finally:
if source_cur:
source_cur.close()
if target_cur:
target_cur.close()
if source_conn:
source_conn.close()
if target_conn:
target_conn.close()
print("すべての接続をクローズしました")
if __name__ == "__main__":
migrate_data()

View File

@ -1,18 +0,0 @@
# Generated by Django 3.2.9 on 2022-09-09 13:18
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('rog', '0032_alter_location_sub_loc_id'),
]
operations = [
migrations.AlterField(
model_name='templocation',
name='sub_loc_id',
field=models.CharField(blank=True, max_length=2048, null=True, verbose_name='Sub location id'),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 3.2.9 on 2022-10-06 10:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('rog', '0033_alter_templocation_sub_loc_id'),
]
operations = [
migrations.AlterField(
model_name='customuser',
name='email',
field=models.CharField(max_length=255, verbose_name='user name'),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 3.2.9 on 2022-10-06 10:52
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('rog', '0034_alter_customuser_email'),
]
operations = [
migrations.AlterField(
model_name='customuser',
name='email',
field=models.EmailField(max_length=254, unique=True, verbose_name='user name'),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 3.2.9 on 2022-10-06 11:01
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('rog', '0035_alter_customuser_email'),
]
operations = [
migrations.AlterField(
model_name='customuser',
name='email',
field=models.CharField(max_length=255, unique=True, verbose_name='Email'),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 3.2.9 on 2022-10-06 13:42
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('rog', '0036_alter_customuser_email'),
]
operations = [
migrations.AddField(
model_name='customuser',
name='is_rogaining',
field=models.BooleanField(default=False),
),
]

View File

@ -1,28 +0,0 @@
# Generated by Django 3.2.9 on 2022-10-16 10:50
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('rog', '0037_customuser_is_rogaining'),
]
operations = [
migrations.AddField(
model_name='customuser',
name='event_code',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Event Code'),
),
migrations.AddField(
model_name='customuser',
name='team_name',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Team Name'),
),
migrations.AddField(
model_name='customuser',
name='zekken_number',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Zekken Number'),
),
]

View File

@ -1,27 +0,0 @@
# Generated by Django 3.2.9 on 2022-10-17 11:39
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('rog', '0038_auto_20221016_1950'),
]
operations = [
migrations.CreateModel(
name='GoalImages',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('goalimage', models.FileField(blank=True, null=True, upload_to='%y%m%d')),
('goaltime', models.DateTimeField(verbose_name='Goal time')),
('team_name', models.CharField(max_length=255, verbose_name='Team name')),
('event_code', models.CharField(max_length=255, verbose_name='event code')),
('cp_number', models.IntegerField(verbose_name='CP numner')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@ -1,32 +0,0 @@
# Generated by Django 3.2.9 on 2022-11-05 10:39
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('rog', '0039_goalimages'),
]
operations = [
migrations.AlterField(
model_name='goalimages',
name='goalimage',
field=models.FileField(blank=True, null=True, upload_to='goals/%y%m%d'),
),
migrations.CreateModel(
name='CheckinImages',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('checkinimage', models.FileField(blank=True, null=True, upload_to='checkin/%y%m%d')),
('checkintime', models.DateTimeField(verbose_name='Goal time')),
('team_name', models.CharField(max_length=255, verbose_name='Team name')),
('event_code', models.CharField(max_length=255, verbose_name='event code')),
('cp_number', models.IntegerField(verbose_name='CP numner')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to=settings.AUTH_USER_MODEL)),
],
),
]

View File

@ -1,49 +0,0 @@
# Generated by Django 3.2.9 on 2023-05-26 08:24
import django.contrib.postgres.indexes
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('rog', '0040_auto_20221105_1939'),
]
operations = [
migrations.DeleteModel(
name='JpnAdminPerf',
),
migrations.DeleteModel(
name='JpnSubPerf',
),
migrations.AlterField(
model_name='location',
name='category',
field=models.CharField(blank=True, db_index=True, max_length=2048, null=True, verbose_name='Category'),
),
migrations.AlterField(
model_name='location',
name='event_active',
field=models.BooleanField(db_index=True, default=True, verbose_name='Is Event active'),
),
migrations.AlterField(
model_name='location',
name='event_name',
field=models.CharField(blank=True, db_index=True, max_length=2048, null=True, verbose_name='Event name'),
),
migrations.AlterField(
model_name='location',
name='group',
field=models.CharField(blank=True, db_index=True, max_length=2048, null=True, verbose_name='Group'),
),
migrations.AlterField(
model_name='location',
name='location_id',
field=models.IntegerField(blank=True, db_index=True, null=True, verbose_name='Location id'),
),
migrations.AddIndex(
model_name='location',
index=django.contrib.postgres.indexes.GistIndex(fields=['geom'], name='rog_locatio_geom_4793cc_gist'),
),
]

View File

@ -0,0 +1,274 @@
# Generated by Django 3.2.9 on 2024-07-24 05:31
import datetime
from django.conf import settings
import django.contrib.postgres.indexes
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import uuid
class Migration(migrations.Migration):
dependencies = [
('rog', '0032_alter_location_sub_loc_id'),
]
operations = [
migrations.CreateModel(
name='Category',
fields=[
('category_name', models.CharField(max_length=255, primary_key=True, serialize=False)),
('category_number', models.IntegerField(unique=True)),
('duration', models.DurationField(default=datetime.timedelta(seconds=18000))),
('num_of_member', models.IntegerField(default=1)),
('family', models.BooleanField(default=False)),
('female', models.BooleanField(default=False)),
],
),
migrations.CreateModel(
name='CheckinImages',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('checkinimage', models.FileField(blank=True, null=True, upload_to='checkin/%y%m%d')),
('checkintime', models.DateTimeField(verbose_name='Goal time')),
('team_name', models.CharField(max_length=255, verbose_name='Team name')),
('event_code', models.CharField(max_length=255, verbose_name='event code')),
('cp_number', models.IntegerField(verbose_name='CP numner')),
],
),
migrations.CreateModel(
name='Entry',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateTimeField()),
],
),
migrations.CreateModel(
name='EntryMember',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('is_temporary', models.BooleanField(default=False)),
],
),
migrations.CreateModel(
name='GoalImages',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('goalimage', models.FileField(blank=True, null=True, upload_to='goals/%y%m%d')),
('goaltime', models.DateTimeField(verbose_name='Goal time')),
('team_name', models.CharField(max_length=255, verbose_name='Team name')),
('event_code', models.CharField(max_length=255, verbose_name='event code')),
('cp_number', models.IntegerField(verbose_name='CP numner')),
],
),
migrations.CreateModel(
name='Member',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('is_temporary', models.BooleanField(default=False)),
],
),
migrations.CreateModel(
name='NewEvent',
fields=[
('event_name', models.CharField(max_length=255, primary_key=True, serialize=False)),
('start_datetime', models.DateTimeField(default=django.utils.timezone.now)),
('end_datetime', models.DateTimeField()),
],
),
migrations.CreateModel(
name='Team',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('zekken_number', models.CharField(max_length=255, unique=True)),
('team_name', models.CharField(max_length=255)),
],
),
migrations.CreateModel(
name='TempUser',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('email', models.EmailField(max_length=254, unique=True)),
('password', models.CharField(max_length=128)),
('is_rogaining', models.BooleanField(default=False)),
('zekken_number', models.CharField(blank=True, max_length=255, null=True)),
('event_code', models.CharField(blank=True, max_length=255, null=True)),
('team_name', models.CharField(blank=True, max_length=255, null=True)),
('group', models.CharField(max_length=255)),
('firstname', models.CharField(blank=True, max_length=255, null=True)),
('lastname', models.CharField(blank=True, max_length=255, null=True)),
('date_of_birth', models.DateField(blank=True, null=True)),
('female', models.BooleanField(default=False)),
('verification_code', models.UUIDField(default=uuid.uuid4, editable=False)),
('created_at', models.DateTimeField(auto_now_add=True)),
('expires_at', models.DateTimeField()),
],
),
migrations.DeleteModel(
name='JpnAdminPerf',
),
migrations.DeleteModel(
name='JpnSubPerf',
),
migrations.AddField(
model_name='customuser',
name='date_of_birth',
field=models.DateField(blank=True, null=True),
),
migrations.AddField(
model_name='customuser',
name='event_code',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Event Code'),
),
migrations.AddField(
model_name='customuser',
name='female',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='customuser',
name='firstname',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AddField(
model_name='customuser',
name='is_rogaining',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='customuser',
name='lastname',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AddField(
model_name='customuser',
name='team_name',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Team Name'),
),
migrations.AddField(
model_name='customuser',
name='zekken_number',
field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Zekken Number'),
),
migrations.AlterField(
model_name='customuser',
name='email',
field=models.CharField(max_length=255, unique=True, verbose_name='Email'),
),
migrations.AlterField(
model_name='location',
name='category',
field=models.CharField(blank=True, db_index=True, max_length=2048, null=True, verbose_name='Category'),
),
migrations.AlterField(
model_name='location',
name='event_active',
field=models.BooleanField(db_index=True, default=True, verbose_name='Is Event active'),
),
migrations.AlterField(
model_name='location',
name='event_name',
field=models.CharField(blank=True, db_index=True, max_length=2048, null=True, verbose_name='Event name'),
),
migrations.AlterField(
model_name='location',
name='group',
field=models.CharField(blank=True, db_index=True, max_length=2048, null=True, verbose_name='Group'),
),
migrations.AlterField(
model_name='location',
name='location_id',
field=models.IntegerField(blank=True, db_index=True, null=True, verbose_name='Location id'),
),
migrations.AlterField(
model_name='templocation',
name='sub_loc_id',
field=models.CharField(blank=True, max_length=2048, null=True, verbose_name='Sub location id'),
),
migrations.AddIndex(
model_name='location',
index=django.contrib.postgres.indexes.GistIndex(fields=['geom'], name='rog_locatio_geom_4793cc_gist'),
),
migrations.AddField(
model_name='team',
name='category',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='rog.category'),
),
migrations.AddField(
model_name='team',
name='owner',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='owned_teams', to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='member',
name='team',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rog.team'),
),
migrations.AddField(
model_name='member',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='goalimages',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='entrymember',
name='entry',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rog.entry'),
),
migrations.AddField(
model_name='entrymember',
name='member',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rog.member'),
),
migrations.AddField(
model_name='entry',
name='category',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rog.category'),
),
migrations.AddField(
model_name='entry',
name='event',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rog.newevent'),
),
migrations.AddField(
model_name='entry',
name='owner',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
),
migrations.AddField(
model_name='entry',
name='team',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rog.team'),
),
migrations.AddField(
model_name='checkinimages',
name='user',
field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to=settings.AUTH_USER_MODEL),
),
migrations.AlterUniqueTogether(
name='category',
unique_together={('category_name', 'category_number')},
),
migrations.AlterUniqueTogether(
name='team',
unique_together={('zekken_number', 'category')},
),
migrations.AlterUniqueTogether(
name='member',
unique_together={('team', 'user')},
),
migrations.AlterUniqueTogether(
name='entrymember',
unique_together={('entry', 'member')},
),
migrations.AlterUniqueTogether(
name='entry',
unique_together={('team', 'event', 'date', 'owner')},
),
]

View File

@ -0,0 +1,34 @@
# Generated by Django 3.2.9 on 2024-07-24 06:22
import datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('rog', '0033_auto_20240724_1431'),
]
operations = [
migrations.AlterField(
model_name='category',
name='category_number',
field=models.IntegerField(default=0),
),
migrations.CreateModel(
name='NewCategory',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('category_name', models.CharField(max_length=255, unique=True)),
('category_number', models.IntegerField(default=0)),
('duration', models.DurationField(default=datetime.timedelta(seconds=18000))),
('num_of_member', models.IntegerField(default=1)),
('family', models.BooleanField(default=False)),
('female', models.BooleanField(default=False)),
],
options={
'unique_together': {('category_name', 'category_number')},
},
),
]

View File

@ -0,0 +1,29 @@
# Generated by Django 3.2.9 on 2024-07-24 06:29
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('rog', '0034_auto_20240724_1522'),
]
operations = [
migrations.CreateModel(
name='NewEvent2',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('event_name', models.CharField(max_length=255, unique=True)),
('start_datetime', models.DateTimeField(default=django.utils.timezone.now)),
('end_datetime', models.DateTimeField()),
],
),
migrations.AlterField(
model_name='entry',
name='event',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rog.newevent2'),
),
]

View File

@ -0,0 +1,20 @@
# Generated by Django 3.2.9 on 2024-07-24 06:58
from django.db import migrations, models
import django.db.models.deletion
import rog.models
class Migration(migrations.Migration):
dependencies = [
('rog', '0035_auto_20240724_1529'),
]
operations = [
migrations.AlterField(
model_name='team',
name='category',
field=models.ForeignKey(default=rog.models.get_default_category, on_delete=django.db.models.deletion.SET_DEFAULT, to='rog.newcategory'),
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 3.2.9 on 2024-07-24 15:20
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('rog', '0036_alter_team_category'),
]
operations = [
migrations.AlterField(
model_name='member',
name='team',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='members', to='rog.team'),
),
]

View File

@ -0,0 +1,19 @@
# Generated by Django 3.2.9 on 2024-07-25 01:21
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('rog', '0037_alter_member_team'),
]
operations = [
migrations.AlterField(
model_name='entry',
name='category',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='rog.newcategory'),
),
]

View File

@ -0,0 +1,29 @@
# Generated by Django 3.2.9 on 2024-07-26 06:08
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('rog', '0038_alter_entry_category'),
]
operations = [
migrations.AddField(
model_name='customuser',
name='date_joined',
field=models.DateTimeField(default=django.utils.timezone.now),
),
migrations.AlterField(
model_name='customuser',
name='email',
field=models.EmailField(max_length=254, unique=True),
),
migrations.AlterField(
model_name='customuser',
name='group',
field=models.CharField(blank=True, max_length=255),
),
]

View File

@ -0,0 +1,33 @@
# Generated by Django 3.2.9 on 2024-08-01 08:29
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('rog', '0039_auto_20240726_1508'),
]
operations = [
migrations.AddField(
model_name='member',
name='date_of_birth',
field=models.DateField(blank=True, null=True),
),
migrations.AddField(
model_name='member',
name='female',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='member',
name='firstname',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AddField(
model_name='member',
name='lastname',
field=models.CharField(blank=True, max_length=255, null=True),
),
]

View File

@ -0,0 +1,37 @@
# Generated by Django 3.2.9 on 2024-08-02 15:11
import django.contrib.gis.db.models.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('rog', '0040_auto_20240801_1729'),
]
operations = [
migrations.CreateModel(
name='JpnSubPerf',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('geom', django.contrib.gis.db.models.fields.MultiPolygonField(blank=True, null=True, srid=4326)),
('adm0_en', models.CharField(blank=True, max_length=254, null=True)),
('adm0_ja', models.CharField(blank=True, max_length=254, null=True)),
('adm0_pcode', models.CharField(blank=True, max_length=254, null=True)),
('adm1_en', models.CharField(blank=True, max_length=254, null=True)),
('adm1_ja', models.CharField(blank=True, max_length=254, null=True)),
('adm1_pcode', models.CharField(blank=True, max_length=254, null=True)),
('adm2_ja', models.CharField(blank=True, max_length=254, null=True)),
('adm2_en', models.CharField(blank=True, max_length=254, null=True)),
('adm2_pcode', models.CharField(blank=True, max_length=254, null=True)),
('name_modified', models.CharField(blank=True, max_length=254, null=True)),
('area_name', models.CharField(blank=True, max_length=254, null=True)),
('list_order', models.IntegerField(default=0)),
],
options={
'db_table': 'jpn_sub_perf',
'managed': False,
},
),
]

View File

View File

@ -1,3 +1,4 @@
from django.contrib.auth.hashers import make_password
from dataclasses import field
import email
from enum import unique
@ -23,17 +24,27 @@ 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.utils import timezone
from datetime import timedelta,date
from django.contrib.gis.geos import Point,MultiPoint
#from django.db import models
from django.core.exceptions import ValidationError
import csv
import codecs
import sys
import time
import uuid
import logging
logger = logging.getLogger(__name__)
env = environ.Env(DEBUG=(bool, False))
environ.Env.read_env(env_file=".env")
db = Db(dbname=env("POSTGRES_DBNAME"), user=env("POSTGRES_USER"), password=env("POSTGRES_PASS"), host="postgres-db", port=env("PG_PORT"))
db = Db(dbname=env("POSTGRES_DBNAME"), user=env("POSTGRES_USER"), password=env("POSTGRES_PASS"), host=env("PG_HOST"), port=env("PG_PORT"))
def get_file_path(instance, filename):
@ -61,16 +72,41 @@ def remove_bom_inplace(path):
fp.seek(-bom_length, os.SEEK_CUR)
fp.truncate()
class GifurogeRegister(models.Model):
event_code = models.CharField(max_length=100)
time = models.IntegerField(choices=[(3, '3時間'), (5, '5時間')])
owner_name_kana = models.CharField(max_length=100)
owner_name = models.CharField(max_length=100)
email = models.EmailField()
password = models.CharField(max_length=100)
owner_birthday = models.DateField(blank=True,null=True)
owner_sex = models.CharField(max_length=10,blank=True,null=True)
team_name = models.CharField(max_length=100)
department = models.CharField(max_length=100)
members_count = models.IntegerField()
member2 = models.CharField(max_length=100, blank=True, null=True)
birthday2 = models.DateField(blank=True,null=True)
sex2 = models.CharField(max_length=10,blank=True,null=True)
member3 = models.CharField(max_length=100, blank=True, null=True)
birthday3 = models.DateField(blank=True,null=True)
sex3 = models.CharField(max_length=10,blank=True,null=True)
member4 = models.CharField(max_length=100, blank=True, null=True)
birthday4 = models.DateField(blank=True,null=True)
sex4 = models.CharField(max_length=10,blank=True,null=True)
member5 = models.CharField(max_length=100, blank=True, null=True)
birthday5 = models.DateField(blank=True,null=True)
sex5 = models.CharField(max_length=10,blank=True,null=True)
class CustomUserManager(BaseUserManager):
def create_user(self, email, password, group, event_code, team_name, **other_fields):
def create_user(self, email, password=None, **other_fields):
if not email:
raise ValueError(_("You must provide an email address"))
user = self.model(email=email, group=group, event_code=event_code, team_name=team_name, zekken_number=email, is_rogaining=True, **other_fields)
email = self.normalize_email(email)
user = self.model(email=email, **other_fields)
user.set_password(password)
user.save()
user.save(using=self._db)
return user
@ -90,7 +126,7 @@ class CustomUserManager(BaseUserManager):
if other_fields.get('is_superuser') is not True:
raise ValueError(_('Superuser must have is_superuser set to True'))
return self.create_user(email, password, group, event_code, team_name, **other_fields)
return self.create_user(email, password, **other_fields)
@ -136,28 +172,28 @@ class JpnAdminMainPerf(models.Model):
# ###
# ### Cities
# ###
# class JpnSubPerf(models.Model):
# geom = models.MultiPolygonField(blank=True, null=True)
# adm0_en = models.CharField(max_length=254, blank=True, null=True)
# adm0_ja = models.CharField(max_length=254, blank=True, null=True)
# adm0_pcode = models.CharField(max_length=254, blank=True, null=True)
# adm1_en = models.CharField(max_length=254, blank=True, null=True)
# adm1_ja = models.CharField(max_length=254, blank=True, null=True)
# adm1_pcode = models.CharField(max_length=254, blank=True, null=True)
# adm2_ja = models.CharField(max_length=254, blank=True, null=True)
# adm2_en = models.CharField(max_length=254, blank=True, null=True)
# adm2_pcode = models.CharField(max_length=254, blank=True, null=True)
# name_modified = models.CharField(max_length=254, blank=True, null=True)
# area_name = models.CharField(max_length=254, blank=True, null=True)
# list_order =models.IntegerField(default=0)
class JpnSubPerf(models.Model):
geom = models.MultiPolygonField(blank=True, null=True)
adm0_en = models.CharField(max_length=254, blank=True, null=True)
adm0_ja = models.CharField(max_length=254, blank=True, null=True)
adm0_pcode = models.CharField(max_length=254, blank=True, null=True)
adm1_en = models.CharField(max_length=254, blank=True, null=True)
adm1_ja = models.CharField(max_length=254, blank=True, null=True)
adm1_pcode = models.CharField(max_length=254, blank=True, null=True)
adm2_ja = models.CharField(max_length=254, blank=True, null=True)
adm2_en = models.CharField(max_length=254, blank=True, null=True)
adm2_pcode = models.CharField(max_length=254, blank=True, null=True)
name_modified = models.CharField(max_length=254, blank=True, null=True)
area_name = models.CharField(max_length=254, blank=True, null=True)
list_order =models.IntegerField(default=0)
# class Meta:
# managed = False
# db_table = 'jpn_sub_perf'
# indexes = [
# models.Index(fields=['geom'], name='jpn_sub_perf_geom_idx'),
# # Add other fields for indexing as per the requirements
# ]
class Meta:
managed = False
db_table = 'jpn_sub_perf'
indexes = [
models.Index(fields=['geom'], name='jpn_sub_perf_geom_idx'),
# Add other fields for indexing as per the requirements
]
###
### Gifu Areas
@ -203,24 +239,282 @@ class CustomUser(AbstractBaseUser, PermissionsMixin):
GB2 = '大垣-3時間', '大垣-3時間'
GB3 = '大垣-5時間', '大垣-5時間'
email = models.CharField(_("Email"), max_length=255, unique=True)
is_staff = models.BooleanField(default=False)
email = models.EmailField(unique=True)
firstname = models.CharField(max_length=255,blank=True, null=True)
lastname = models.CharField(max_length=255, blank=True, null=True)
date_of_birth = models.DateField(blank=True, null=True)
female = models.BooleanField(default=False)
group = models.CharField(max_length=255,blank=True)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
date_joined = models.DateTimeField(default=timezone.now)
is_rogaining = models.BooleanField(default=False)
zekken_number = models.CharField(_("Zekken Number"), max_length=255, blank=True, null=True)
event_code = models.CharField(_("Event Code"), max_length=255, blank=True, null=True)
team_name = models.CharField(_("Team Name"), max_length=255, blank=True, null=True)
group = models.CharField(max_length=255,
choices=Groups.choices,
default=Groups.GB1)
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['group',]
REQUIRED_FIELDS = []
objects = CustomUserManager()
def set_password(self, raw_password):
self.password = make_password(raw_password)
self._password = raw_password
def __str__(self):
return self.email
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)
firstname = models.CharField(max_length=255,blank=True, null=True)
lastname = models.CharField(max_length=255, blank=True, null=True)
date_of_birth = models.DateField(blank=True, null=True)
female = models.BooleanField(default=False)
verification_code = models.UUIDField(default=uuid.uuid4, editable=False)
created_at = models.DateTimeField(auto_now_add=True)
expires_at = models.DateTimeField()
def set_password(self, raw_password):
self.password = make_password(raw_password)
def check_password(self, raw_password):
return check_password(raw_password, self.password)
# TempUserの作成時にこのメソッドを使用
@classmethod
def create_temp_user(cls, email, password, **kwargs):
temp_user = cls(email=email, **kwargs)
temp_user.set_password(password)
temp_user.save()
return temp_user
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 NewEvent2(models.Model):
event_name = models.CharField(max_length=255, unique=True)
start_datetime = models.DateTimeField(default=timezone.now)
end_datetime = models.DateTimeField()
deadlineDateTime = models.DateTimeField(null=True, blank=True)
#// Added @2024-10-21
public = models.BooleanField(default=False)
hour_3 = models.BooleanField(default=False)
hour_5 = models.BooleanField(default=True)
class_general = models.BooleanField(default=True)
class_family = models.BooleanField(default=True)
class_solo_male = models.BooleanField(default=True)
class_solo_female = models.BooleanField(default=True)
self_rogaining = models.BooleanField(default=False)
def __str__(self):
return f"{self.event_name} - From:{self.start_datetime} To:{self.end_datetime}"
def save(self, *args, **kwargs):
if not self.deadlineDateTime:
self.deadlineDateTime = self.end_datetime #- timedelta(days=7)
super().save(*args, **kwargs)
class NewEvent(models.Model):
event_name = models.CharField(max_length=255, primary_key=True)
start_datetime = models.DateTimeField(default=timezone.now)
end_datetime = models.DateTimeField()
def __str__(self):
return f"{self.event_name} - From:{self.start_datetime} To:{self.end_datetime}"
def get_default_category():
return NewCategory.objects.get_or_create(category_name="Default Category", category_number=1)[0].id
class Team(models.Model):
# zekken_number = models.CharField(max_length=255, unique=True)
team_name = models.CharField(max_length=255)
owner = models.ForeignKey(CustomUser, on_delete=models.CASCADE, related_name='owned_teams', blank=True, null=True)
category = models.ForeignKey('NewCategory', on_delete=models.SET_DEFAULT, default=get_default_category)
# class Meta:
# unique_together = ('zekken_number', 'category')
def __str__(self):
return f"{self.team_name}, owner:{self.owner.lastname} {self.owner.firstname}"
class Member(models.Model):
team = models.ForeignKey(Team, on_delete=models.CASCADE, related_name='members')
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
firstname = models.CharField(max_length=255, blank=True, null=True)
lastname = models.CharField(max_length=255, blank=True, null=True)
date_of_birth = models.DateField(null=True, blank=True)
female = models.BooleanField(default=False)
is_temporary = models.BooleanField(default=False) # Akira 2024-7-24
class Meta:
unique_together = ('team', 'user')
def __str__(self):
return f"{self.team.team_name} - {self.user.lastname} {self.user.firstname}"
#
class Category(models.Model):
category_name = models.CharField(max_length=255, primary_key=True)
category_number = models.IntegerField(default=0)
duration = models.DurationField(default=timedelta(hours=5))
num_of_member = models.IntegerField(default=1)
family = models.BooleanField(default=False)
female = models.BooleanField(default=False)
class Meta:
unique_together = ('category_name','category_number')
def __str__(self):
hours = self.duration.total_seconds() // 3600
return f"{self.category_name} - {self.category_number} ({int(hours)}時間)"
@property
def hours(self):
return self.duration.total_seconds() // 3600
class NewCategory(models.Model):
category_name = models.CharField(max_length=255, unique=True)
category_number = models.IntegerField(default=0)
duration = models.DurationField(default=timedelta(hours=5))
num_of_member = models.IntegerField(default=1)
family = models.BooleanField(default=False)
female = models.BooleanField(default=False)
class Meta:
unique_together = ('category_name','category_number')
def __str__(self):
hours = self.duration.total_seconds() // 3600
return f"{self.category_name} - {self.category_number} ({int(hours)}時間)"
@property
def hours(self):
return self.duration.total_seconds() // 3600
class Entry(models.Model):
team = models.ForeignKey(Team, on_delete=models.CASCADE)
event = models.ForeignKey(NewEvent2, on_delete=models.CASCADE)
category = models.ForeignKey(NewCategory, on_delete=models.CASCADE)
date = models.DateTimeField()
owner = models.ForeignKey(CustomUser, on_delete=models.CASCADE,blank=True, null=True) # Akira 2024-7-24
zekken_number = models.IntegerField(default=0)
is_active = models.BooleanField(default=True) # 新しく追加
hasParticipated = models.BooleanField(default=False) # 新しく追加
hasGoaled = models.BooleanField(default=False) # 新しく追加
class Meta:
unique_together = ('zekken_number', 'event', 'date')
def __str__(self):
return f"{self.zekken_number} - {self.team.team_name} - {self.event.event_name} - {self.date}"
def clean(self):
super().clean()
if self.event and self.category and self.date:
start = self.event.start_datetime
end = self.event.end_datetime #- self.category.duration
if not (start.date() <= self.date.date() <= end.date()):
raise ValidationError({
'date': f'日時{self.date}{start.date()}から{end.date()}の間である必要があります。'
})
# メンバーの年齢と性別をチェック
if self.team: # and not self.team.members.exists():
members = self.team.members.all() # membersを適切に取得
if not members.exists():
raise ValidationError({'team': 'チームにメンバーが登録されていません。'})
#members = Member.objects.filter(team=self.team)
#if not members.exists():
# # ここで、owner をMemberに登録する。 Akira 2024-7-24
# raise ValidationError("チームにメンバーが登録されていません。")
adults = [m for m in members if self.is_adult(m.user.date_of_birth)]
children = [m for m in members if self.is_child(m.user.date_of_birth)]
teenagers = [m for m in members if self.is_teenager(m.user.date_of_birth)]
if self.category.family:
if not (adults and children):
raise ValidationError("ファミリーカテゴリーには、18歳以上のメンバーと小学生以下のメンバーが各1名以上必要です。")
else:
if not adults:
raise ValidationError("18歳以上のメンバーが1名以上必要です。")
if children:
raise ValidationError("ファミリーカテゴリー以外では、小学生以下のメンバーは参加できません。")
if self.category.num_of_member == 1:
if len(members) != 1:
raise ValidationError("このカテゴリーはソロ参加のみ可能です。")
if not adults:
raise ValidationError("ソロ参加は18歳以上のみ可能です。")
if self.category.female and not members[0].user.female:
raise ValidationError("このカテゴリーは女性のみ参加可能です。")
if not self.category.female and members[0].user.female:
raise ValidationError("このカテゴリーは男性のみ参加可能です。")
if len(members) > self.category.num_of_member:
raise ValidationError(f"このカテゴリーは{self.category.num_of_member}名までの参加が必要です。")
def save(self, *args, **kwargs):
self.full_clean()
super().save(*args, **kwargs)
@staticmethod
def is_adult(birth_date):
today = date.today()
age = today.year - birth_date.year - ((today.month, today.day) < (birth_date.month, birth_date.day))
return age >= 18
@staticmethod
def is_child(birth_date):
today = date.today()
age = today.year - birth_date.year - ((today.month, today.day) < (birth_date.month, birth_date.day))
return age <= 12
@staticmethod
def is_teenager(birth_date):
today = date.today()
age = today.year - birth_date.year - ((today.month, today.day) < (birth_date.month, birth_date.day))
return 13 <= age <= 17
class EntryMember(models.Model):
entry = models.ForeignKey(Entry, on_delete=models.CASCADE)
member = models.ForeignKey(Member, on_delete=models.CASCADE)
is_temporary = models.BooleanField(default=False) # Akira 2024-7-24
class Meta:
unique_together = ('entry', 'member')
def __str__(self):
return f"{self.entry.team.zekken_number} - {self.member.user.lastname} {self.member.user.firstname}"
class GoalImages(models.Model):
user=models.ForeignKey(CustomUser, on_delete=models.DO_NOTHING)
goalimage = models.FileField(upload_to='goals/%y%m%d', blank=True, null=True)
@ -237,6 +531,121 @@ class CheckinImages(models.Model):
event_code = models.CharField(_("event code"), max_length=255)
cp_number = models.IntegerField(_("CP numner"))
class GpsCheckin(models.Model):
path_order = models.IntegerField(
null=False,
help_text="チェックポイントの順序番号"
)
zekken_number = models.TextField(
null=False,
help_text="ゼッケン番号"
)
event_code = models.TextField(
null=False,
help_text="イベントコード"
)
cp_number = models.IntegerField(
null=True,
blank=True,
help_text="チェックポイント番号"
)
lattitude = models.FloatField(
null=True,
blank=True,
help_text="緯度:写真から取得"
)
longitude = models.FloatField(
null=True,
blank=True,
help_text="経度:写真から取得"
)
image_address = models.TextField(
null=True,
blank=True,
help_text="チェックイン画像のパス"
)
image_receipt = models.TextField(
null=True,
blank=True,
default=False,
help_text="レシート画像のパス"
)
image_qr = models.BooleanField(
default=False,
help_text="QRコードスキャンフラグ"
)
validate_location = models.BooleanField(
default=False,
help_text="位置情報検証フラグ:画像認識で検証した結果"
)
goal_time = models.TextField(
null=True,
blank=True,
help_text="ゴール時刻=ゴール時のみ使用される。画像から時刻を読み取り設定する。"
)
late_point = models.IntegerField(
null=True,
blank=True,
help_text="遅刻ポイント:ゴールの時刻が制限時間を超えた場合、1分につき-50点が加算。"
)
create_at = models.DateTimeField(
null=True,
blank=True,
help_text="作成日時:データの作成日時"
)
create_user = models.TextField(
null=True,
blank=True,
help_text="作成ユーザー"
)
update_at = models.DateTimeField(
null=True,
blank=True,
help_text="更新日時"
)
update_user = models.TextField(
null=True,
blank=True,
help_text="更新ユーザー"
)
buy_flag = models.BooleanField(
default=False,
help_text="購入フラグ協賛店で購入した場合、無条件でTRUEにする。"
)
colabo_company_memo = models.TextField(
null=False,
default='',
help_text="グループコード:複数のイベントで合算する場合に使用する"
)
points = models.IntegerField(
null=True,
blank=True,
help_text="ポイント:このチェックインによる獲得ポイント。通常ポイントと買い物ポイントは分離される。ゴールの場合には減点なども含む。"
)
class Meta:
db_table = 'gps_checkins'
constraints = [
models.UniqueConstraint(
fields=['zekken_number', 'event_code', 'path_order'],
name='unique_gps_checkin'
)
]
indexes = [
models.Index(fields=['zekken_number', 'event_code','path_order'], name='idx_zekken_event'),
models.Index(fields=['create_at'], name='idx_create_at'),
]
def __str__(self):
return f"{self.event_code}-{self.zekken_number}-{self.path_order}"
def save(self, *args, **kwargs):
# 作成時・更新時のタイムスタンプを自動設定
from django.utils import timezone
if not self.create_at:
self.create_at = timezone.now()
self.update_at = timezone.now()
super().save(*args, **kwargs)
class RogUser(models.Model):
user=models.OneToOneField(CustomUser, on_delete=models.CASCADE)
@ -611,7 +1020,7 @@ class ShapeFileLocations(models.Model):
@receiver(pre_save, sender=Location)
def location_presave(sender, instance, *args, **kwargs):
#print("------############------------", instance.location_id)
print("------############------------", instance.location_id)
templocation.objects.filter(location_id = instance.location_id).delete()
@ -649,6 +1058,8 @@ def deleteShapelocation(sender,instance,*args,**kwargs):
@receiver(post_save, sender=ShapeLayers)
def publish_data(sender, instance, created, **kwargs):
logger.info(f"Processing ShapeLayer: {instance.name}")
file = instance.file.path
file_format = os.path.basename(file).split('.')[-1]
file_name = os.path.basename(file).split('.')[0]
@ -662,42 +1073,58 @@ def publish_data(sender, instance, created, **kwargs):
os.remove(file)
try:
#logger.debug("Attempting to read shape file")
# print("before reading the file")
shp = glob.glob(r'{}/**/*.shp'.format(file_path), recursive=True)[0]
#logger.info(f"Shape file read: {shp}")
# print("this is the read file",shp)
gdf = gpd.read_file(shp)
crs_name = str(gdf.crs.srs)
#logger.debug(f"CRS name: {crs_name}")
# print(crs_name, 'crs - name')
epsg = int(crs_name.replace('epsg:',''))
if epsg is None:
epsg=4326
lm2 = getTempMappingforModel(instance.layerof, shp)
#logger.info("Saving to temporary table")
# print("### shape file is ###")
lm2.save(strict=True, verbose=True)
#logger.info("Save to temporary table completed")
os.remove(shp)
except Exception as e:
print('######## shape file##########',e)
try:
logger.debug("Attempting to read CSV file")
csv_f = glob.glob(r'{}/**/*.csv'.format(file_path), recursive=True)[0]
remove_bom_inplace(csv_f)
mdl = apps.get_model(app_label="rog", model_name=LAYER_CHOICES[instance.layerof -1][1])
# print(mdl)
# print(f"#### instance.layerof - {instance.layerof}")
print(mdl)
print(f"#### instance.layerof - {instance.layerof}")
#logger.debug(f"Model for layer: {mdl}")
with open(csv_f, mode="r", encoding="utf-8") as txt_file:
#heading = next(txt_file)
reader = csv.reader(txt_file, delimiter=",")
for fields in reader:
logger.debug(f"Processing row: {fields[0]}")
print("@@@@@@@@@@@@")
print(fields[0])
print("@@@@@@@@@@@@")
if instance.layerof == 1:
#insertShapeLayerLocation(instance.name, fields)
updateLocation(mdl, fields)
#updateLocation(mdl, fields)
update_or_create_location(mdl, fields)
if instance.layerof == 2:
updateLineTable(mdl, fields)
if instance.layerof == 3:
@ -706,76 +1133,157 @@ def publish_data(sender, instance, created, **kwargs):
with open(csv_f, mode="r", encoding="utf-8") as txt_file:
reader_2 = csv.reader(txt_file, delimiter=",")
for fields in reader_2:
logger.debug(f"Inserting ShapeLayerLocation: {fields[0]}")
print("@@@@@@@@@@@@")
print(fields[0])
print("@@@@@@@@@@@@")
if instance.layerof == 1:
insertShapeLayerLocation(instance.name, fields)
logger.info("CSV processing completed")
except Exception as e:
print('######## csv file ##########',e)
def insertShapeLayerLocation(name, fields):
sll = UserUploadUser(userfile=name, email=fields[0])
sll.save();
logger.info(f"Attempting to insert ShapeFileLocations for file: {name}, location_id: {fields[0]}")
try:
sll = UserUploadUser(userfile=name, email=fields[0])
sll.save();
except Exception as e:
logger.error(f"Error inserting ShapeFileLocations: {e}", exc_info=True)
def insertUserUploadUser(name, fields):
with transaction.atomic():
sll = UserUploadUser(userfile=name, email=fields[0])
sll.save()
try:
with transaction.atomic():
sll = UserUploadUser(userfile=name, email=fields[0])
sll.save()
except Exception as e:
logger.error(f"Error updating TempLocation: {e}", exc_info=True)
def update_or_create_location(mdl, fields):
try:
with transaction.atomic():
latitude = float(fields[11]) if fields[11] and len(fields[11]) > 0 else None
longitude = float(fields[12]) if fields[12] and len(fields[12]) > 0 else None
geom = MultiPoint(Point(longitude, latitude)) if latitude is not None and longitude is not None else None
defaults={
'sub_loc_id': fields[1] if len(fields[1]) > 0 else '',
'cp': fields[2] if len(fields[2]) > 0 else 0,
# その他のフィールド...
'location_name': fields[3] if len(fields[3]) > 0 else '',
'category': fields[4] if len(fields[4]) > 0 else '',
'subcategory': fields[5] if len(fields[5]) > 0 else '',
'zip': fields[6] if len(fields[6]) > 0 else '',
'address': fields[7] if len(fields[7]) > 0 else '',
'prefecture': fields[8] if len(fields[8]) > 0 else '',
'area': fields[9] if len(fields[9]) > 0 else '',
'city': fields[10] if len(fields[10]) > 0 else '',
'latitude': latitude,
'longitude': longitude,
'photos': fields[13] if len(fields[13]) > 0 else '',
'videos': fields[14] if len(fields[14]) > 0 else '',
'webcontents': fields[15] if len(fields[15]) > 0 else '',
'status': fields[16] if len(fields[16]) > 0 else '',
'portal': fields[17] if len(fields[17]) > 0 else '',
'group': fields[18] if len(fields[18]) > 0 else '',
'phone': fields[19] if len(fields[19]) > 0 else '',
'fax': fields[20] if len(fields[20]) > 0 else '',
'email': fields[21] if len(fields[21]) > 0 else '',
'facility': fields[22] if len(fields[22]) > 0 else '',
'remark': fields[23] if len(fields[23]) > 0 else '',
'tags': fields[24] if len(fields[24]) > 0 else '',
'hidden_location': fields[25] if len(fields[25]) > 0 else False,
'auto_checkin': fields[26] if len(fields[26]) > 0 else False,
'checkin_radius': fields[27] if len(fields[27]) > 0 else 15,
'checkin_point': fields[28] if len(fields[28]) > 0 else 10,
'buy_point': fields[29] if len(fields[29]) > 0 else 0,
'evaluation_value': fields[30] if len(fields[30]) > 0 else '',
'shop_closed': fields[31] if len(fields[31]) > 0 else False,
'shop_shutdown': fields[32] if len(fields[32]) > 0 else False,
'opening_hours_mon': fields[33] if len(fields[33]) > 0 else '',
'opening_hours_tue': fields[34] if len(fields[34]) > 0 else '',
'opening_hours_wed': fields[35] if len(fields[35]) > 0 else '',
'opening_hours_thu': fields[36] if len(fields[36]) > 0 else '',
'opening_hours_fri': fields[37] if len(fields[37]) > 0 else '',
'opening_hours_sat': fields[38] if len(fields[38]) > 0 else '',
'opening_hours_sun': fields[39] if len(fields[39]) > 0 else ''
}
if geom:
defaults['geom'] = geom
obj, created = mdl.objects.update_or_create(
location_id=int(fields[0]),
defaults=defaults
)
if created:
logger.info(f"New location created with id: {obj.location_id}")
else:
logger.info(f"Location updated with id: {obj.location_id}")
except Exception as e:
logger.error(f"Error updating or creating location: {e}", exc_info=True)
def updateLocation(mdl, fields):
print(f"Updating {fields[0]} - {fields[1]}")
print(mdl.objects.filter(location_id = int(fields[0])))
print(f"--- ${fields} ----")
with transaction.atomic():
mdl.objects.filter(location_id = int(fields[0])).update(
sub_loc_id = fields[1] if len(fields[1]) > 0 else '',
cp = fields[2] if len(fields[2]) > 0 else 0,
location_name = fields[3] if len(fields[3]) > 0 else '',
category = fields[4] if len(fields[4]) > 0 else '',
subcategory = fields[5] if len(fields[5]) > 0 else '',
zip = fields[6] if len(fields[6]) > 0 else '',
address = fields[7] if len(fields[7]) > 0 else '',
prefecture = fields[8] if len(fields[8]) > 0 else '',
area = fields[9] if len(fields[9]) > 0 else '',
city = fields[10] if len(fields[10]) > 0 else '',
latitude = fields[11] if len(fields[11]) > 0 else '',
longitude = fields[12] if len(fields[12]) > 0 else '',
photos = fields[13] if len(fields[13]) > 0 else '',
videos = fields[14] if len(fields[14]) > 0 else '',
webcontents = fields[15] if len(fields[15]) > 0 else '',
status = fields[16] if len(fields[16]) > 0 else '',
portal = fields[17] if len(fields[17]) > 0 else '',
group = fields[18] if len(fields[18]) > 0 else '',
phone = fields[19] if len(fields[19]) > 0 else '',
fax = fields[20] if len(fields[20]) > 0 else '',
email = fields[21] if len(fields[21]) > 0 else '',
facility = fields[22] if len(fields[22]) > 0 else '',
remark = fields[23] if len(fields[23]) > 0 else '',
tags = fields[24] if len(fields[24]) > 0 else '',
hidden_location = fields[25] if len(fields[25]) > 0 else False,
auto_checkin = fields[26] if len(fields[26]) > 0 else False,
checkin_radius = fields[27] if len(fields[27]) > 0 else 15,
checkin_point = fields[28] if len(fields[28]) > 0 else 10,
buy_point = fields[29] if len(fields[29]) > 0 else 0,
evaluation_value = fields[30] if len(fields[30]) > 0 else '',
shop_closed = fields[31] if len(fields[31]) > 0 else False,
shop_shutdown = fields[32] if len(fields[32]) > 0 else False,
opening_hours_mon = fields[33] if len(fields[33]) > 0 else '',
opening_hours_tue = fields[34] if len(fields[34]) > 0 else '',
opening_hours_wed = fields[35] if len(fields[35]) > 0 else '',
opening_hours_thu = fields[36] if len(fields[36]) > 0 else '',
opening_hours_fri = fields[37] if len(fields[37]) > 0 else '',
opening_hours_sat = fields[38] if len(fields[38]) > 0 else '',
opening_hours_sun = fields[39] if len(fields[39]) > 0 else ''
)
try:
with transaction.atomic():
updated = mdl.objects.filter(location_id = int(fields[0])).update(
sub_loc_id = fields[1] if len(fields[1]) > 0 else '',
cp = fields[2] if len(fields[2]) > 0 else 0,
location_name = fields[3] if len(fields[3]) > 0 else '',
category = fields[4] if len(fields[4]) > 0 else '',
subcategory = fields[5] if len(fields[5]) > 0 else '',
zip = fields[6] if len(fields[6]) > 0 else '',
address = fields[7] if len(fields[7]) > 0 else '',
prefecture = fields[8] if len(fields[8]) > 0 else '',
area = fields[9] if len(fields[9]) > 0 else '',
city = fields[10] if len(fields[10]) > 0 else '',
latitude = fields[11] if len(fields[11]) > 0 else '',
longitude = fields[12] if len(fields[12]) > 0 else '',
photos = fields[13] if len(fields[13]) > 0 else '',
videos = fields[14] if len(fields[14]) > 0 else '',
webcontents = fields[15] if len(fields[15]) > 0 else '',
status = fields[16] if len(fields[16]) > 0 else '',
portal = fields[17] if len(fields[17]) > 0 else '',
group = fields[18] if len(fields[18]) > 0 else '',
phone = fields[19] if len(fields[19]) > 0 else '',
fax = fields[20] if len(fields[20]) > 0 else '',
email = fields[21] if len(fields[21]) > 0 else '',
facility = fields[22] if len(fields[22]) > 0 else '',
remark = fields[23] if len(fields[23]) > 0 else '',
tags = fields[24] if len(fields[24]) > 0 else '',
hidden_location = fields[25] if len(fields[25]) > 0 else False,
auto_checkin = fields[26] if len(fields[26]) > 0 else False,
checkin_radius = fields[27] if len(fields[27]) > 0 else 15,
checkin_point = fields[28] if len(fields[28]) > 0 else 10,
buy_point = fields[29] if len(fields[29]) > 0 else 0,
evaluation_value = fields[30] if len(fields[30]) > 0 else '',
shop_closed = fields[31] if len(fields[31]) > 0 else False,
shop_shutdown = fields[32] if len(fields[32]) > 0 else False,
opening_hours_mon = fields[33] if len(fields[33]) > 0 else '',
opening_hours_tue = fields[34] if len(fields[34]) > 0 else '',
opening_hours_wed = fields[35] if len(fields[35]) > 0 else '',
opening_hours_thu = fields[36] if len(fields[36]) > 0 else '',
opening_hours_fri = fields[37] if len(fields[37]) > 0 else '',
opening_hours_sat = fields[38] if len(fields[38]) > 0 else '',
opening_hours_sun = fields[39] if len(fields[39]) > 0 else ''
)
logger.info(f"TempLocation updated successfully. Rows affected: {updated}")
except Exception as e:
logger.error(f"Error updating TempLocation: {e}", exc_info=True)
def updateLineTable(mdl, fields):
print(f"Updating {fields[0]} - {fields[1]}")
@ -835,7 +1343,10 @@ def updatePolygonTable(mdl, fields):
def createUser(fields):
with transaction.atomic():
user = CustomUser.objects.create_user(email=fields[0], event_code=fields[1], team_name=fields[2], group=fields[3], password=fields[4])
other_fields.setdefault('event_code',fields[1])
other_fields.setdefault('team_name',fields[1])
other_fields.setdefault('group',fields[1])
user = CustomUser.objects.create_user(email=fields[0], password=fields[4], **other_fields)
user.is_superuser = False
user.is_staff = False
user.save()
@ -855,6 +1366,8 @@ def deleteUserUploadUser(sender,instance,*args,**kwargs):
@receiver(post_save, sender=UserUpload)
def publish_data(sender, instance, created, **kwargs):
logger.info(f"Processing ShapeLayer: {instance.name}")
file = instance.file.path
#os.remove(file)

25
rog/permissions.py Normal file
View File

@ -0,0 +1,25 @@
from rest_framework import permissions
from .models import Team,Member
class IsMemberOrTeamOwner(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
# Check if user is a member of the team or the team owner
return request.user in obj.team.members.all() or request.user == obj.team.owner
class IsTeamOwner(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if isinstance(obj, Team):
return obj.owner == request.user
elif isinstance(obj, Member):
return obj.team.owner == request.user
return False
class IsTeamOwnerOrMember(permissions.BasePermission):
def has_permission(self, request, view):
team_id = view.kwargs.get('team_id')
if not team_id:
return False
team = Team.objects.get(id=team_id)
return team.owner == request.user or team.members.filter(user=request.user).exists()

View File

@ -1,7 +1,20 @@
from django.contrib.auth.hashers import make_password, check_password
from django.contrib.auth import get_user_model
User = get_user_model()
import uuid
from django.db import IntegrityError
from django.conf import settings
from django.urls import reverse
from django.contrib.auth.password_validation import validate_password
from django.core.exceptions import ValidationError
from django.db import transaction
from rest_framework import serializers
from rest_framework_gis.serializers import GeoFeatureModelSerializer
from sqlalchemy.sql.functions import mode
from .models import Location, Location_line, Location_polygon, JpnAdminMainPerf, Useractions, GifuAreas, RogUser, UserTracks, GoalImages, CheckinImages,CustomUser
from .models import Location, Location_line, Location_polygon, JpnAdminMainPerf, Useractions, GifuAreas, RogUser, UserTracks, GoalImages, CheckinImages,CustomUser,NewEvent,NewEvent2, Team, NewCategory, Category, Entry, Member, TempUser,EntryMember
from drf_extra_fields.fields import Base64ImageField
#from django.contrib.auth.models import User
@ -9,7 +22,12 @@ from .models import CustomUser
from django.contrib.auth import authenticate
from .models import TestModel
import logging
from django.shortcuts import get_object_or_404
from django.utils import timezone
from datetime import datetime, date
logger = logging.getLogger(__name__)
class LocationCatSerializer(serializers.ModelSerializer):
class Meta:
@ -59,17 +77,100 @@ class GifuAreaSerializer(serializers.ModelSerializer):
model= GifuAreas
fields=['id', 'adm0_ja', 'adm0_pcode', 'adm1_en', 'adm1_ja', 'adm1_pcode', 'adm2_ja', 'adm2_en', 'adm2_pcode', 'area_nm']
class CreateUserSerializer(serializers.ModelSerializer):
class UserRegistrationSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True, required=True, validators=[validate_password])
password2 = serializers.CharField(write_only=True, required=True, validators=[validate_password])
class Meta:
model = CustomUser
fields = ('email', 'password')
extra_kwargs = {'password': {'write_only': True}}
fields = ('email', 'password', 'password2', 'firstname', 'lastname', 'date_of_birth', 'female')
extra_kwargs = {
'email': {'required': True},
'firstname': {'required': True},
'lastname': {'required': True},
'date_of_birth': {'required': True},
}
def validate(self, attrs):
if attrs['password'] != attrs['password2']:
raise serializers.ValidationError({"password": "Password fields didn't match."})
try:
validate_password(attrs['password'])
except ValidationError as e:
raise serializers.ValidationError({"password": list(e.messages)})
return attrs
def validate_email(self, value):
if CustomUser.objects.filter(email=value).exists() or TempUser.objects.filter(email=value).exists():
raise serializers.ValidationError("この電子メールアドレスは既に使用されています。")
return value
def create(self, validated_data):
user = CustomUser.objects.create_user(validated_data['email'],validated_data['password'], '大垣-初心者','','')
return user
raw_password = validated_data.get('password')
# デバッグコード
hashed_password = make_password(raw_password)
print(f"Hashed password during registration: {hashed_password}")
is_valid = check_password(raw_password, hashed_password)
print(f"Password is valid during registration: {is_valid}")
validated_data['password'] = hashed_password
return super(UserRegistrationSerializer, self).create(validated_data)
#validated_data['password'] = make_password(validated_data.get('password'))
#return super(UserRegistrationSerializer, self).create(validated_data)
# try:
# with transaction.atomic():
# password = validated_data['password'].encode('utf-8').decode('utf-8')
#
# user = CustomUser.objects.create_user(
# email=validated_data['email'],
# password=password, # validated_data['password'],
# firstname=validated_data['firstname'],
# lastname=validated_data['lastname'],
# date_of_birth=validated_data['date_of_birth'],
# female=validated_data.get('female', False),
# group='' # この値は必要に応じて変更してください
# )
# logger.debug(f"Creating user with data: {validated_data}")
# user.set_password(validated_data['password'])
# user.save()
#
# return user
# except ValidationError as e:
# raise serializers.ValidationError({"password": list(e.messages)})
#class CreateUserSerializer(serializers.ModelSerializer):
# class Meta:
# model = CustomUser
# fields = ('email', 'password')
# extra_kwargs = {'password': {'write_only': True}}
#
# def create(self, validated_data):
# user = CustomUser.objects.create_user(validated_data['email'],validated_data['password'], '大垣-初心者','','')
# return user
class TempUserRegistrationSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
class Meta:
model = TempUser
fields = ('email', 'password', 'firstname', 'lastname', 'date_of_birth', 'female')
def create(self, validated_data):
# パスワードのハッシュ化はviewで行うので、ここではそのまま保存
return TempUser.objects.create(**validated_data)
#validated_data['verification_code'] = str(uuid.uuid4())
#raw_password = validated_data.get('password')
#hashed_password = make_password(raw_password)
#validated_data['password'] = hashed_password
#return TempUser.objects.create(**validated_data)
class UserSerializer(serializers.ModelSerializer):
class Meta:
@ -78,15 +179,37 @@ class UserSerializer(serializers.ModelSerializer):
class GolaImageSerializer(serializers.ModelSerializer):
goalimage = Base64ImageField(max_length=None, use_url=True)
class Meta:
model = GoalImages
fields="__all__"
def get_goalimage_url(self, car):
def get_goalimage_url_old(self, car):
request = self.context.get('request')
photo_url = GoalImages.goalimage.url
return request.build_absolute_uri(photo_url)
def get_goalimage_url(self, obj):
request = self.context.get('request')
if request is None:
logger.warning("Request not found in serializer context")
return None
try:
photo_url = obj.goalimage.url
absolute_url = request.build_absolute_uri(photo_url)
logger.info(f"Generated URL for goalimage: {absolute_url}")
return absolute_url
except AttributeError as e:
logger.error(f"Error generating URL for goalimage: {str(e)}")
return None
def to_representation(self, instance):
representation = super().to_representation(instance)
representation['goalimage_url'] = self.get_goalimage_url(instance)
logger.debug(f"Serialized data: {representation}")
return representation
class CheckinImageSerializer(serializers.ModelSerializer):
checkinimage = Base64ImageField(max_length=None, use_url=True)
class Meta:
@ -106,14 +229,32 @@ class RogUserSerializer(serializers.ModelSerializer):
class LoginUserSerializer(serializers.Serializer):
email = serializers.CharField()
#email = serializers.CharField()
email = serializers.EmailField()
password = serializers.CharField()
def validate(self, data):
user = authenticate(**data)
if user and user.is_active:
return user
raise serializers.ValidationError("Invalid Details.")
email = data.get('email')
password = data.get('password')
if email and password:
user = authenticate(username=email, password=password)
if user:
if user.is_active:
return user
raise serializers.ValidationError("User account is disabled.")
else:
# Check if the user exists
try:
user_obj = User.objects.get(email=email)
raise serializers.ValidationError("Incorrect password.")
except User.DoesNotExist:
raise serializers.ValidationError("User with this email does not exist.")
else:
raise serializers.ValidationError("Must include 'email' and 'password'.")
class UseractionsSerializer(serializers.ModelSerializer):
@ -192,4 +333,546 @@ class RegistrationSerializer(serializers.ModelSerializer):
raise serializers.ValidationError({'password': 'Passwords must match.'})
user.set_password(password)
user.save()
return user
return user
class NewCategorySerializer(serializers.ModelSerializer):
class Meta:
model = NewCategory
fields = ['id','category_name', 'category_number', 'duration', 'num_of_member', 'family', 'female']
#fields = ['id','category_name', 'category_number']
class NewEvent2Serializer(serializers.ModelSerializer):
class Meta:
model = NewEvent2
fields = ['id','event_name', 'start_datetime', 'end_datetime', 'deadlineDateTime', 'public', 'hour_3', 'hour_5', 'class_general','class_family','class_solo_male','class_solo_female']
class NewEventSerializer(serializers.ModelSerializer):
class Meta:
model = NewEvent
fields = ['event_name', 'start_datetime', 'end_datetime']
class TeamSerializer(serializers.ModelSerializer):
category = serializers.PrimaryKeyRelatedField(queryset=NewCategory.objects.all())
#category = serializers.IntegerField()
#category = NewCategorySerializer(read_only=True)
#category_id = serializers.PrimaryKeyRelatedField(
# queryset=NewCategory.objects.all(),
# source='category',
# write_only=True
#)
owner = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = Team
fields = ['id','team_name', 'category', 'owner']
read_only_fields = ['id', 'owner']
def to_representation(self, instance):
ret = super().to_representation(instance)
if instance.category:
ret['category'] = {
'id': instance.category.id,
'category_name': instance.category.category_name,
'category_number': instance.category.category_number,
'duration': instance.category.duration,
'num_of_member':instance.category.num_of_member,
'family':instance.category.family,
'female':instance.category.female
}
else:
ret['category'] = None
ret['owner'] = CustomUserSerializer(instance.owner).data
return ret
def validate_category(self, value):
if not isinstance(value, NewCategory):
raise serializers.ValidationError("Invalid category")
return value
#if not NewCategory.objects.filter(id=value).exists():
# raise serializers.ValidationError("Invalid category ID")
#return value
def create(self, validated_data):
return Team.objects.create(**validated_data)
#category_id = validated_data.pop('category')
#category = get_object_or_404(NewCategory, id=category_id)
#team = Team.objects.create(category=category, **validated_data)
#team.category = category
#return team
#category = validated_data.pop('category')
#team = Team.objects.create(category=category, **validated_data)
#return team
#logger.debug(f"Creating team with data: {validated_data}")
#validated_data['owner'] = self.context['request'].user
#return super().create(validated_data)
def update(self, instance, validated_data):
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
return instance
#if 'category' in validated_data:
# category_id = validated_data.pop('category')
# category = get_object_or_404(NewCategory, id=category_id)
# instance.category = category
#return super().update(instance, validated_data)
#if 'category' in validated_data:
# instance.category = validated_data.pop('category')
#return super().update(instance, validated_data)
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ['category_name', 'category_number', 'duration', 'num_of_member', 'family', 'female']
class EntrySerializer(serializers.ModelSerializer):
team = serializers.PrimaryKeyRelatedField(queryset=Team.objects.all())
event = serializers.PrimaryKeyRelatedField(queryset=NewEvent2.objects.all())
category = serializers.PrimaryKeyRelatedField(queryset=NewCategory.objects.all())
owner = serializers.PrimaryKeyRelatedField(read_only=True)
date = serializers.DateTimeField(input_formats=['%Y-%m-%d'])
zekken_number = serializers.IntegerField()
#date = serializers.DateTimeField(default_timezone=timezone.get_current_timezone())
class Meta:
model = Entry
fields = ['id','team', 'event', 'category', 'date','zekken_number','owner','is_active', 'hasParticipated', 'hasGoaled']
read_only_fields = ['id','owner']
def validate_date(self, value):
if isinstance(value, str):
try:
value = datetime.strptime(value, "%Y-%m-%d")
except ValueError:
raise serializers.ValidationError("Invalid date format. Use YYYY-MM-DD.")
if isinstance(value, date):
value = datetime.combine(value, datetime.min.time())
if timezone.is_naive(value):
return timezone.make_aware(value, timezone.get_current_timezone())
return value
#if isinstance(value, date):
# # dateオブジェクトをdatetimeオブジェクトに変換
# value = datetime.combine(value, datetime.min.time())
#if timezone.is_naive(value):
# return timezone.make_aware(value, timezone.get_current_timezone())
#return value
def validate_team(self, value):
if not value.members.exists():
raise serializers.ValidationError("チームにメンバーが登録されていません。")
return value
def validate_date(self, value):
if isinstance(value, datetime):
return value.date()
return value
def validate(self, data):
team = data.get('team')
event = data.get('event')
category = data.get('category')
entry_date = data.get('date')
if isinstance(entry_date, datetime):
entry_date = entry_date.date()
elif isinstance(entry_date, str):
entry_date = datetime.strptime(entry_date, "%Y-%m-%d").date()
logger.debug("test-0")
logger.debug(f"==== start:{event.start_datetime.date()} <= entry_date : {entry_date} <= end:{event.end_datetime.date()} ?? ====")
if entry_date < event.start_datetime.date() or entry_date > event.end_datetime.date():
raise serializers.ValidationError(f"日付は{event.start_datetime.date()}から{event.end_datetime.date()}の間である必要があります。")
logger.debug("test-1")
try:
logger.debug(f"Parsed data: team={team}, event={event}, category={category}, ")
owner = self.context['request'].user
zekken_number = data.get('zekken_number')
logger.debug(f"entry_date={entry_date}, owner={owner}, zekken_number={zekken_number}")
except Exception:
raise serializers.ValidationError(f"何らかのエラーが発生しました")
# Check if team, event, and category exist
if not Team.objects.filter(id=team.id).exists():
raise serializers.ValidationError("指定されたチームは存在しません。")
if not NewEvent2.objects.filter(id=event.id).exists():
raise serializers.ValidationError("指定されたイベントは存在しません。")
if not NewCategory.objects.filter(id=category.id).exists():
raise serializers.ValidationError("指定されたカテゴリーは存在しません。")
# Check for unique constraint
if Entry.objects.filter(team=team, event=event, date__date=entry_date, owner=owner).exists():
raise serializers.ValidationError("既に登録済みです。")
# Validate zekken_number
if zekken_number is not None:
if zekken_number <= 0:
raise serializers.ValidationError("ゼッケン番号は正の整数である必要があります。")
# if Entry.objects.filter(event=event, zekken_number=zekken_number).exists():
# raise serializers.ValidationError("このゼッケン番号は既に使用されています。")
return data
def to_internal_value(self, data):
# dateフィールドが文字列で来た場合の処理
if 'date' in data and isinstance(data['date'], str):
try:
# 文字列をdatetimeオブジェクトに変換
data['date'] = datetime.strptime(data['date'], "%Y-%m-%d")
except ValueError:
raise serializers.ValidationError({"date": "無効な日付形式です。YYYY-MM-DD形式を使用してください。"})
return super().to_internal_value(data)
def to_representation(self, instance):
ret = super().to_representation(instance)
ret['team'] = TeamSerializer(instance.team).data
ret['event'] = NewEvent2Serializer(instance.event).data
ret['category'] = NewCategorySerializer(instance.category).data
ret['owner'] = CustomUserSerializer(instance.owner).data
if isinstance(ret['date'], datetime):
ret['date'] = ret['date'].date().isoformat()
elif isinstance(ret['date'], date):
ret['date'] = ret['date'].isoformat()
return ret
#if isinstance(ret['date'], datetime):
# ret['date'] = ret['date'].date().isoformat()
#return ret
#def to_representation(self, instance):
# ret = super().to_representation(instance)
# ret['team'] = instance.team.team_name
# ret['event'] = instance.event.event_name
# ret['category'] = instance.category.category_name
# ret['owner'] = instance.owner.email
# return ret
class CustomUserSerializer(serializers.ModelSerializer):
class Meta:
model = CustomUser
fields = ['id','email', 'firstname', 'lastname', 'date_of_birth', 'female']
read_only_fields = ['id','email']
class TeamDetailSerializer(serializers.ModelSerializer):
category = NewCategorySerializer(read_only=True)
class Meta:
model = Team
fields = ['id', 'zekken_number', 'team_name', 'category']
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = CustomUser
fields = ['id','email', 'firstname', 'lastname', 'date_of_birth', 'female', 'is_rogaining', 'zekken_number', 'event_code', 'team_name', 'group']
read_only_fields = ('id', 'email')
class UserUpdateSerializer(serializers.ModelSerializer):
class Meta:
model = CustomUser
fields = ['firstname', 'lastname', 'date_of_birth', 'female']
extra_kwargs = {'email': {'read_only': True}}
def update(self, instance, validated_data):
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
return instance
class MemberCreationSerializer(serializers.Serializer):
#email = serializers.EmailField()
email = serializers.EmailField(allow_blank=True, required=False)
firstname = serializers.CharField(required=False, allow_blank=True)
lastname = serializers.CharField(required=False, allow_blank=True)
date_of_birth = serializers.DateField(required=False)
female = serializers.BooleanField(required=False)
class MemberWithUserSerializer(serializers.ModelSerializer):
user = UserSerializer(read_only=True)
class Meta:
model = Member
fields = ['user', 'team']
class MemberSerializer(serializers.ModelSerializer):
email = serializers.EmailField(write_only=True)
firstname = serializers.CharField(required=False, allow_blank=True, allow_null=True)
lastname = serializers.CharField(required=False, allow_blank=True, allow_null=True)
date_of_birth = serializers.DateField(required=False, allow_null=True)
female = serializers.BooleanField(required=False)
class Meta:
model = Member
fields = ['id', 'email', 'firstname', 'lastname', 'date_of_birth', 'female']
def validate_firstname(self, value):
return value or None
def validate_lastname(self, value):
return value or None
def create(self, validated_data):
email = validated_data.pop('email')
team = self.context['team']
# 既存のユーザーを探すか、新しいユーザーを作成
user, created = CustomUser.objects.get_or_create(email=email)
# ユーザーが新しく作成された場合のみ、追加情報を更新
if created:
user.firstname = validated_data.get('firstname', '')
user.lastname = validated_data.get('lastname', '')
user.date_of_birth = validated_data.get('date_of_birth')
user.female = validated_data.get('female', False)
user.save()
# メンバーを作成
member = Member.objects.create(
user=user,
team=team,
firstname=validated_data.get('firstname'),
lastname=validated_data.get('lastname'),
date_of_birth=validated_data.get('date_of_birth'),
female=validated_data.get('female', False)
)
return member
# メンバーを作成して返す
#class MemberCreationSerializerreturn Member.objects.create(user=user, team=team)
def update(self, instance, validated_data):
user_data = validated_data.pop('user', {})
user = instance.user
for attr, value in user_data.items():
setattr(user, attr, value)
user.save()
return super().update(instance, validated_data)
def to_representation(self, instance):
representation = super().to_representation(instance)
representation['email'] = instance.user.email
representation['firstname'] = instance.user.firstname
representation['lastname'] = instance.user.lastname
representation['date_of_birth'] = instance.user.date_of_birth
representation['female'] = instance.user.female
return representation
class MemberSerializerOld(serializers.ModelSerializer):
user = CustomUserSerializer(read_only=True)
firstname = serializers.CharField(required=False, allow_blank=True, allow_null=True)
lastname = serializers.CharField(required=False, allow_blank=True, allow_null=True)
date_of_birth = serializers.DateField(required=False, allow_null=True)
female = serializers.BooleanField(required=False)
#team = TeamDetailSerializer(read_only=True)
#email = serializers.EmailField(write_only=True, required=False)
#firstname = serializers.CharField(write_only=True, required=False)
#lastname = serializers.CharField(write_only=True, required=False)
#date_of_birth = serializers.DateField(write_only=True, required=False)
#female = serializers.BooleanField(write_only=True, required=False)
class Meta:
model = Member
fields = ['id','email','firstname','lastname','date_of_birth','female']
#read_only_fields = ['id', 'team']
'''
def create(self, validated_data):
team = validated_data['team']
email = validated_data.get('email')
if email.startswith('dummy_'):
user, _ = CustomUser.objects.get_or_create(
email=email,
defaults={**user_data, 'is_active': True}
)
else:
user, _ = CustomUser.objects.get_or_create(
email=email,
defaults={**user_data, 'is_active': False}
)
member = Member.objects.create(user=user, **validated_data)
return member
'''
def create(self, validated_data):
email = validated_data.pop('email')
team = self.context['team']
# 既存のユーザーを探すか、新しいユーザーを作成
user, created = CustomUser.objects.get_or_create(email=email)
# メンバーを作成
member = Member.objects.create(
user=user,
team=team,
firstname=validated_data.get('firstname', ''),
lastname=validated_data.get('lastname', ''),
date_of_birth=validated_data.get('date_of_birth'),
female=validated_data.get('female', False)
)
return member
def update(self, instance, validated_data):
user_data = validated_data.pop('user', {})
user = instance.user
for attr, value in user_data.items():
setattr(user, attr, value)
user.save()
return super().update(instance, validated_data)
#if user.email.startswith('dummy_'): # dummy_ で始まるメールアドレスの場合のみ更新
# for attr, value in user_data.items():
# setattr(user, attr, value)
# user.save()
#else:
# raise serializers.ValidationError("このユーザーの情報は更新できません。")
#return super().update(instance, validated_data)
def to_representation(self, instance):
representation = super().to_representation(instance)
representation['email'] = instance.user.email
return representation
'''
def to_representation(self, instance):
representation = super().to_representation(instance)
user_data = representation['user']
return {
'id': representation['id'],
'email': user_data['email'],
'firstname': user_data['firstname'],
'lastname': user_data['lastname'],
'date_of_birth': user_data['date_of_birth'],
'female': user_data['female'],
'team': representation['team']
}
'''
class EntryMemberSerializer(serializers.ModelSerializer):
class Meta:
model = EntryMember
fields = ['id', 'entry', 'member', 'is_temporary']
class TempUserSerializer(serializers.ModelSerializer):
class Meta:
model = TempUser
#fields = ['id','email', 'password', 'is_rogaining', 'zekken_number', 'event_code', 'team_name', 'group', 'firstname', 'lastname', 'date_of_birth', 'female', 'verification_code', 'created_at', 'expires_at']
fields = ['email', 'password', 'firstname', 'lastname', 'date_of_birth', 'female', 'verification_code']
class EntryCreationSerializer(serializers.Serializer):
owner_email = serializers.EmailField()
event_name = serializers.CharField()
category_name = serializers.CharField()
team_name = serializers.CharField()
zekken_number = serializers.CharField()
date = serializers.DateField()
members = serializers.ListField(child=serializers.DictField())
def create(self, validated_data):
owner = CustomUser.objects.get(email=validated_data['owner_email'])
event = NewEvent2.objects.get(event_name=validated_data['event_name'])
category = NewCategory.objects.get(category_name=validated_data['category_name'])
# Create or get team
team, _ = Team.objects.get_or_create(
zekken_number=validated_data['zekken_number'],
category=category,
defaults={'team_name': validated_data['team_name'], 'owner': owner}
)
# Create or update entry
entry, _ = Entry.objects.update_or_create(
owner=owner,
team=team,
event=event,
date=validated_data['date'],
defaults={'category': category}
)
# Process members
for member_data in validated_data['members']:
user, created = CustomUser.objects.get_or_create(
email=member_data.get('email'),
defaults={
'firstname': member_data['firstname'],
'lastname': member_data['lastname'],
'date_of_birth': member_data['date_of_birth']
}
)
if created:
TempUser.objects.create(
email=user.email,
firstname=user.firstname,
lastname=user.lastname,
date_of_birth=user.date_of_birth
)
# Send invitation email here
member, _ = NewMember.objects.get_or_create(
user=user,
team=team,
defaults={'is_temporary': created}
)
EntryMember.objects.get_or_create(entry=entry, member=member)
return entry
class PasswordResetRequestSerializer(serializers.Serializer):
email = serializers.EmailField()
class PasswordResetConfirmSerializer(serializers.Serializer):
new_password = serializers.CharField(write_only=True)
confirm_password = serializers.CharField(write_only=True)
def validate(self, data):
if data['new_password'] != data['confirm_password']:
raise serializers.ValidationError("Passwords do not match")
validate_password(data['new_password'])
return data
class UserLastGoalTimeSerializer(serializers.Serializer):
user_email = serializers.EmailField()
last_goal_time = serializers.DateTimeField()

View File

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>アクティベーション成功</title>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f0f0f0;
}
.message {
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
text-align: center;
}
</style>
</head>
<body>
<div class="message">
<h1>アクティベーション成功</h1>
<p>{{ message }}</p>
</div>
</body>
</html>

View File

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>アクティベーション成功</title>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f0f0f0;
}
.message {
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
text-align: center;
}
</style>
</head>
<body>
<div class="message">
<h1>アクティベーション成功</h1>
<p>{{ message }}</p>
</div>
</body>
</html>

View File

@ -0,0 +1,24 @@
{% extends "admin/base_site.html" %}
{% load i18n admin_urls %}
{% block content %}
<div id="content-main">
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<fieldset class="module aligned">
{% for field in form %}
<div class="form-row">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
{% if field.help_text %}
<p class="help">{{ field.help_text|safe }}</p>
{% endif %}
</div>
{% endfor %}
</fieldset>
<div class="submit-row">
<input type="submit" value="CSVアップロード" class="default" name="_save">
</div>
</form>
</div>
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends "admin/change_list.html" %}
{% load i18n admin_urls %}
{% block object-tools-items %}
{{ block.super }}
<li>
<a href="{% url 'admin:gifuroge_register_upload_csv' %}" class="addlink">
{% blocktranslate with name=opts.verbose_name %}Upload CSV{% endblocktranslate %}
</a>
</li>
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends "admin/change_list.html" %}
{% load i18n admin_urls %}
{% block object-tools-items %}
{{ block.super }}
<li>
<a href="{% url 'admin:gifuroge_register_upload_csv' %}" class="addlink">
{% translate "Upload CSV" %}
</a>
</li>
{% endblock %}

View File

@ -0,0 +1,22 @@
{% extends "admin/base_site.html" %}
{% load i18n admin_urls %}
{% block content %}
<h1>Upload CSV File</h1>
CSV のフォーマット:
イベントコード,時間(3 or 5),代表者かな,代表者名,メール,パスワード,代表者生年月日、代表者性別,チーム名,部門,メンバー数,メンバー2,誕生日2,性別2,メンバー3,誕生日3,性別3,メンバー4,誕生日4,性別4,メンバー5,誕生日5,性別5<br>
(例)<br>
event_code,time,owner_name_kana,owner_name,email,password,owner_birthday,owner_sex,team_name,department,members_count,member2,birthday2,sex2,member3,birthday3,sex3,member4,birthday4,sex4,member5,birthday5,sex5<br>
FC岐阜,3,みやたあきら,宮田 明,hannivalscipio@gmail.com,Sachiko123,タヌキの宮家,一般,3,宮田幸子,1965-4-4,female,川本勇,1965-1-1,male,,,,,,<br>
<br>
この形式のCSVをアップロードすると、以下を実施する。<br>
1)未登録のユーザーの登録をパスワードとともに生成<br>
2)チームを生成し、メンバーを登録<br>
3)イベントコードで示すイベントにエントリー<br>
<br>
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
<input type="file" name="csv_file" required>
<button type="submit">Upload</button>
</form>
{% endblock %}

View File

@ -0,0 +1,23 @@
件名: 岐阜ロゲ「岐阜ナビ」アカウントのユーザー登録完了と{{invitor}}さんからのチーム参加のお願い
{{name}} 様
こちらは岐阜aiネットワークのAI担当です。
この度は岐阜ロゲアプリ「岐阜ナビ」のダウンロードおよびユーザー登録、誠にありがとうございました。
あなた様の登録で間違いがなければ、こちらをタップし、ユーザー登録を完了させてください。
同時に、{{invitor}}さんからのチーム「{{team_name}}」への参加承認にもなります。
{{activation_link}}
リンクをタップすると、アプリからログインすることができるようになります。
ログインした後は、チーム編成やエントリーまたはチームへの参加を行ってください。
なお、リアルロゲイニングは別途申し込みが必要になりますので、ご連絡をお待ちください。
それでは、岐阜ロゲアプリ「岐阜ナビ」をお楽しみください。
本メールは送信専用のメールアドレスで送信しております。 本メールに返信いただいてもご回答いたしかねますので、あらかじめご了承くださいご質問等はinfo@gifuai.netまでお願いいたします。もしこのメールに心当たりがない場合は破棄願います。
NPO 岐阜aiネットワーク 岐阜ロゲ担当AI

View File

@ -0,0 +1,17 @@
件名: {{invitor}}さんからの岐阜ロゲチーム「{{team_name}}」への招待です。
{{name}} 様
こちらは岐阜aiネットワーク担当AIです。
このメールは{{invitor}}さんから、あなたへの、岐阜ロゲチームへの招待メールです。
以下のリンクをタップしていただければ、{{invitor}}さんのチーム「{{team_name}}」の正式メンバーとして登録されます。
{{activation_link}}
それでは、今後とも岐阜ロゲをよろしくお願いいたします。
本メールは送信専用のメールアドレスで送信しております。 本メールに返信いただいてもご回答いたしかねますので、あらかじめご了承くださいご質問等はinfo@gifuai.netまでお願いいたします。もしこのメールに心当たりがない場合は破棄願います。
NPO岐阜aiネットワーク 担当AI

View File

@ -0,0 +1,25 @@
件名: {{invitor}}様からの岐阜ロゲ(チーム名:{{team_name}})への招待メール
{{name}} 様
こちらは岐阜ネットワークのAI担当です。
このメールは {{invitor}} さんから、あなたへの、岐阜ロゲチーム「{{team_name}}」への招待メールです。
まずは、アプリをダウンロードして、ユーザー登録をしてください。
iPhoneの方{{app_download_link}}
Androidの方{{android_download_link}}
ダウンロードした後にユーザー登録を行ってください。
アクティベーション後に個人情報を入力していただくことでチームに正式登録されます。
それでは、今後とも岐阜ロゲをよろしくお願いいたします。
※ 本メールは送信専用のメールアドレスで送信しております。 本メールに返信いただいてもご回答いたしかねますので、あらかじめご了承くださいご質問等はinfo@gifuai.netまでお願いいたします。もしこのメールに心当たりがない場合は破棄願います。
NPO岐阜aiネットワーク 担当AI

View File

@ -0,0 +1,23 @@
件名: 岐阜ロゲのパスワードリセットのお知らせ
{{name}} 様
こちらは岐阜ネットワークのAI担当です。
このメールはパスワードのリセットのご依頼によるリセット確認メールです。
身に覚えのない方は、お手数ですが削除をお願いします。
以下のリンクからパスワードのリセットが行えます。
{{activation_link}}
それでは、今後とも岐阜ロゲをよろしくお願いいたします。
※ 本メールは送信専用のメールアドレスで送信しております。 本メールに返信いただいてもご回答いたしかねますので、あらかじめご了承くださいご質問等はinfo@gifuai.netまでお願いいたします。もしこのメールに心当たりがない場合は破棄願います。
NPO岐阜aiネットワーク 担当AI

View File

@ -0,0 +1,23 @@
件名: 岐阜ロゲ「岐阜ナビ」アカウントのユーザー登録完了依頼
{{name}} 様
こちらは岐阜aiネットワークのAI担当です。
この度は岐阜ロゲアプリ「岐阜ナビ」のダウンロードおよびユーザー登録、誠にありがとうございました。
あなた様の登録で間違いがなければ、こちらをタップし、ユーザー登録を完了させてください。
{{activation_link}}
リンクをタップすると、アプリからログインすることができるようになります。
ログインした後は、チーム編成やエントリーまたはチームへの参加を行ってください。
すでにメンバーとして登録されている場合には、自動的にメンバー登録されます。
なお、リアルロゲイニングは別途申し込みが必要になりますので、ご連絡をお待ちください。
それでは、岐阜ロゲアプリ「岐阜ナビ」をお楽しみください。
本メールは送信専用のメールアドレスで送信しております。 本メールに返信いただいてもご回答いたしかねますので、あらかじめご了承くださいご質問等はinfo@gifuai.netまでお願いいたします。もしこのメールに心当たりがない場合は破棄願います。
NPO 岐阜aiネットワーク 岐阜ロゲ担当AI

View File

@ -0,0 +1,116 @@
import React, { useState } from 'react';
import { useParams } from 'react-router-dom';
import { Eye, EyeOff } from 'lucide-react';
const PasswordReset = () => {
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [showPassword, setShowPassword] = useState(false);
const [message, setMessage] = useState('');
const { uid, token } = useParams();
const handleSubmit = async (e) => {
e.preventDefault();
if (password !== confirmPassword) {
setMessage('パスワードが一致しません。');
return;
}
try {
const response = await fetch(`/api/reset-password/${uid}/${token}/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ new_password: password }),
});
const data = await response.json();
if (response.ok) {
setMessage('パスワードが正常にリセットされました。');
} else {
setMessage(data.message || 'パスワードのリセットに失敗しました。');
}
} catch (error) {
setMessage('エラーが発生しました。もう一度お試しください。');
}
};
return (
<div className="min-h-screen bg-gray-100 flex flex-col justify-center py-12 sm:px-6 lg:px-8">
<div className="sm:mx-auto sm:w-full sm:max-w-md">
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
</h2>
</div>
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
<form className="space-y-6" onSubmit={handleSubmit}>
<div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700">
</label>
<div className="mt-1 relative">
<input
id="password"
name="password"
type={showPassword ? "text" : "password"}
autoComplete="new-password"
required
value={password}
onChange={(e) => setPassword(e.target.value)}
className="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
/>
<button
type="button"
className="absolute inset-y-0 right-0 pr-3 flex items-center"
onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? (
<EyeOff className="h-5 w-5 text-gray-400" />
) : (
<Eye className="h-5 w-5 text-gray-400" />
)}
</button>
</div>
</div>
<div>
<label htmlFor="confirm-password" className="block text-sm font-medium text-gray-700">
</label>
<div className="mt-1">
<input
id="confirm-password"
name="confirm-password"
type="password"
autoComplete="new-password"
required
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
/>
</div>
</div>
<div>
<button
type="submit"
className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
</button>
</div>
</form>
{message && (
<div className="mt-6 text-center text-sm text-gray-500">
{message}
</div>
)}
</div>
</div>
</div>
);
};
export default PasswordReset;

View File

@ -0,0 +1,156 @@
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>パスワードのリセット</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
background-color: #f0f2f5;
margin: 0;
padding: 20px;
line-height: 1.6;
}
.container {
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding: 20px;
max-width: 400px;
margin: 0 auto;
}
h1 {
color: #1a1a1a;
text-align: center;
margin-bottom: 20px;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
color: #4a4a4a;
}
input[type="password"] {
width: 100%;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
}
button {
width: 100%;
padding: 10px;
background-color: #0056b3;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
button:hover {
background-color: #003d82;
}
.message {
margin-top: 15px;
padding: 10px;
border-radius: 4px;
text-align: center;
}
.message.error {
background-color: #ffe6e6;
color: #d8000c;
}
.message.success {
background-color: #e6ffe6;
color: #006400;
}
</style>
</head>
<body>
<div class="container">
<h1>岐阜ナビ:パスワードのリセット</h1>
<form id="reset-form">
<div class="form-group">
<label for="password">新しいパスワード</label>
<input type="password" id="password" name="password" required>
</div>
<div class="form-group">
<label for="confirm-password">パスワードの確認</label>
<input type="password" id="confirm-password" name="confirm-password" required>
</div>
<button type="submit">パスワードをリセット</button>
</form>
<div id="message" class="message"></div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const currentUrl = window.location.href;
function extractParams(url) {
const regex = /\/reset-password\/([^\/]+)\/([^\/]+)\/?/;
const match = url.match(regex);
if (match) {
return { uidb64: match[1], token: match[2] };
}
const urlParams = new URLSearchParams(new URL(url).search);
return {
uidb64: urlParams.get('uidb64') || '',
token: urlParams.get('token') || ''
};
}
const { uidb64, token } = extractParams(currentUrl);
document.getElementById('reset-form').addEventListener('submit', async function(e) {
e.preventDefault();
const password = document.getElementById('password').value;
const confirmPassword = document.getElementById('confirm-password').value;
const messageElement = document.getElementById('message');
if (password !== confirmPassword) {
messageElement.textContent = 'パスワードが一致しません。';
messageElement.className = 'message error';
return;
}
if (!uidb64 || !token) {
messageElement.textContent = '無効なリセットリンクです。';
messageElement.className = 'message error';
return;
}
try {
const response = await fetch(`/api/reset-password/${uidb64}/${token}/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
new_password: password,
confirm_password: confirmPassword
}),
});
const data = await response.json();
if (response.ok) {
messageElement.textContent = 'パスワードが正常にリセットされました。';
messageElement.className = 'message success';
} else {
messageElement.textContent = `パスワードのリセットに失敗しました。エラー: ${data.error || response.statusText}`;
messageElement.className = 'message error';
}
} catch (error) {
console.error('Error:', error);
messageElement.textContent = 'エラーが発生しました。もう一度お試しください。';
messageElement.className = 'message error';
}
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,41 @@
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>無効なパスワードリセットリンク</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f3f4f6;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
}
.container {
background-color: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 400px;
text-align: center;
}
h1 {
color: #1f2937;
margin-bottom: 1rem;
}
p {
color: #4b5563;
}
</style>
</head>
<body>
<div class="container">
<h1>無効なリンク</h1>
<p>このパスワードリセットリンクは無効です。新しいリセットリンクを要求してください。</p>
</div>
</body>
</html>

View File

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>メール確認成功</title>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f0f0f0;
}
.message {
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
text-align: center;
}
</style>
</head>
<body>
<div class="message">
<h1>{{title}}</h1>
<p>{{ message }}</p>
</div>
</body>
</html>

View File

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>メール確認成功</title>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f0f0f0;
}
.message {
background-color: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
text-align: center;
}
</style>
</head>
<body>
<div class="message">
<h1>メール確認成功</h1>
<p>{{ message }}</p>
</div>
</body>
</html>

134
rog/transfer.py Normal file
View File

@ -0,0 +1,134 @@
import psycopg2
from datetime import datetime
import sys
from typing import Dict, List, Tuple
def get_db_connection(dbname: str) -> psycopg2.extensions.connection:
"""データベース接続を確立する"""
try:
return psycopg2.connect(
dbname=dbname,
user='your_username', # 実際のユーザー名に変更してください
password='your_password', # 実際のパスワードに変更してください
host='localhost' # 実際のホスト名に変更してください
)
except psycopg2.Error as e:
print(f"データベース {dbname} への接続エラー: {e}")
sys.exit(1)
def get_source_data() -> List[Dict]:
"""rogdbからデータを取得する"""
conn = get_db_connection('rogdb')
try:
with conn.cursor() as cur:
cur.execute("""
SELECT DISTINCT ON (rci.user_id, rci.cp_number)
rci.team_name,
rci.event_code,
rci.cp_number,
rci.checkinimage,
rci.checkintime,
rci.user_id,
COALESCE(p.point, 0) as late_point
FROM rog_checkinimages rci
LEFT JOIN point p ON p.user_id = rci.user_id
AND p.event_code = rci.event_code
AND p.cp_number = rci.cp_number
WHERE rci.event_code = 'FC岐阜'
ORDER BY rci.user_id, rci.cp_number, rci.checkintime DESC
""")
columns = [desc[0] for desc in cur.description]
return [dict(zip(columns, row)) for row in cur.fetchall()]
finally:
conn.close()
def get_next_serial_number(cur) -> int:
"""次のserial_numberを取得する"""
cur.execute("SELECT nextval('gps_information_serial_number_seq')")
return cur.fetchone()[0]
def insert_into_target(data: List[Dict]) -> Tuple[int, List[str]]:
"""gifurogeデータベースにデータを挿入する"""
conn = get_db_connection('gifuroge')
inserted_count = 0
errors = []
try:
with conn.cursor() as cur:
for record in data:
try:
serial_number = get_next_serial_number(cur)
cur.execute("""
INSERT INTO gps_information (
serial_number,
zekken_number,
event_code,
cp_number,
image_address,
goal_time,
late_point,
create_at,
create_user,
update_at,
update_user,
buy_flag,
minus_photo_flag,
colabo_company_memo
) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
""", (
serial_number,
record['team_name'],
record['event_code'],
record['cp_number'],
record['checkinimage'],
record['checkintime'].strftime('%Y-%m-%d %H:%M:%S'),
record['late_point'],
record['checkintime'],
'system',
record['checkintime'],
'system',
False,
False,
''
))
inserted_count += 1
except psycopg2.Error as e:
errors.append(f"Error inserting record for team {record['team_name']}: {str(e)}")
if inserted_count % 100 == 0:
print(f"Processed {inserted_count} records...")
conn.commit()
except psycopg2.Error as e:
conn.rollback()
errors.append(f"Transaction error: {str(e)}")
finally:
conn.close()
return inserted_count, errors
def main():
print("データ移行を開始します...")
# ソースデータの取得
print("ソースデータを取得中...")
source_data = get_source_data()
print(f"取得したレコード数: {len(source_data)}")
# データの挿入
print("データを移行中...")
inserted_count, errors = insert_into_target(source_data)
# 結果の表示
print("\n=== 移行結果 ===")
print(f"処理したレコード数: {inserted_count}")
print(f"エラー数: {len(errors)}")
if errors:
print("\nエラーログ:")
for error in errors:
print(f"- {error}")
if __name__ == "__main__":
main()

View File

@ -1,14 +1,24 @@
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, TeamViewSet,MemberViewSet,EntryViewSet,RegisterView, VerifyEmailView, NewEventListView,NewEvent2ListView,NewCategoryListView,CategoryListView, MemberUserDetailView, TeamMembersWithUserView,MemberAddView,UserActivationView,RegistrationView,TempUserRegistrationView,ResendInvitationEmailView,update_user_info,update_user_detail,ActivateMemberView, ActivateNewMemberView, PasswordResetRequestView, PasswordResetConfirmView, NewCategoryViewSet,LocationInBound2,UserLastGoalTimeView,TeamEntriesView,update_entry_status,get_events,get_zekken_numbers,get_team_info,get_checkins,update_checkins,export_excel,debug_urls
from django.urls import path, include
from knox import views as knox_views
from .views import TestActionViewSet
from .views import OwnerEntriesView, OwnerTeamsView, OwnerMembersView
from . import views
#from .views import NewEvent2AdminView
from django.conf import settings
from django.conf.urls.static import static
router = DefaultRouter()
router.register(r'newevent2', views.NewEvent2ViewSet)
router.register(prefix='location', viewset=LocationViewSet, basename='location')
router.register(prefix='location_line', viewset=Location_lineViewSet, basename="location_line")
@ -18,6 +28,32 @@ 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)
router.register(r'entry', EntryViewSet, basename='entry')
router.register(r'teams', TeamViewSet, basename='team')
router.register(r'members', MemberViewSet, basename='member')
router.register(r'teams/(?P<team_id>\d+)/members', MemberViewSet, basename='team-members')
router.register(r'categories-viewset', NewCategoryViewSet)
router.register(r'newevent2', views.NewEvent2ViewSet)
# Akira 追加
# GET /api/members/<member_id>/user/: 特定のメンバーのユーザー情報を取得
# GET /api/teams/<team_id>/members-with-user/: 特定のチームの全メンバーとそのユーザー情報を取得
app_name = 'rog' # 名前空間を追加
urlpatterns = router.urls
@ -25,6 +61,7 @@ urlpatterns += [
path('inperf/', LocationsInPerf, name="location_perf"),
path('insubperf', LocationsInSubPerf, name='location_subperf'),
path('inbound', LocationInBound, name='location_bound'),
path('inbound2', LocationInBound2, name='location_bound'),
path('customarea/', CustomAreaLocations, name='custom_area_location'),
path('subperfinmain/', SubPerfInMainPerf, name="sub_perf"),
path('allgifuareas/', GetAllGifuAreas, name="gifu_area"),
@ -33,7 +70,7 @@ urlpatterns += [
path('mainperfext/', ExtentForMainPerf, name="main_perf_ext"),
path('cats/', CatView, name='cats'),
path('catbycity/', CatByCity, name='cat_by_city'),
path('register/', RegistrationAPI.as_view()),
#path('register/', RegistrationAPI.as_view()),
path('login/', LoginAPI.as_view()),
path('user/', UserAPI.as_view()),
path('logout/', knox_views.LogoutView.as_view(), name='knox_logout'),
@ -47,6 +84,48 @@ urlpatterns += [
path('change-password/', ChangePasswordView.as_view(), name='change-password'),
path('delete-account/', DeleteAccount, name="delete-account"),
path('privacy/', PrivacyView, name='privacy-view'),
path('register', RegistrationView.as_view(), name='register'),
# path('goal-image/', GoalImageViewSet.as_view(), name='goal-image')
]
#path('goalimage/', GoalImageViewSet.as_view(), name='goalimage')
path('verify-email/<uuid:verification_code>/', VerifyEmailView.as_view(), name='verify-email'),
path('categories/', NewCategoryListView.as_view(), name='category-list'),
path('new-events/', NewEvent2ListView.as_view(), name='new-event-list'),
path('members/<int:pk>/user/', MemberUserDetailView.as_view(), name='member-user-detail'),
path('teams/<int:team_id>/members-with-user/', TeamMembersWithUserView.as_view(), name='team-members-with-user'),
path('teams/<int:team_id>/members/', MemberViewSet.as_view({'get': 'list', 'post': 'create'}), name='team-members'),
path('teams/<int:team_id>/members/<int:pk>/', MemberViewSet.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'}), name='team-member-detail'),
path('activate/<str:activation_token>/', UserActivationView.as_view(), name='user-activation'),
#path('register/', RegistrationView.as_view(), name='register'), #直接の登録はしない。
path('register/', TempUserRegistrationView.as_view(), name='temp-register'), # 仮登録
#path('register/temp/', RegisterView.as_view(), name='register'), # 古い仮登録
path('reactivate/<str:activation_token>/',ResendInvitationEmailView.as_view(),name='reactivate'),
path('userinfo/<int:user_id>/', update_user_info, name='update_user_info'),
path('userdetail/<int:user_id>/',update_user_detail, name='update_user_detail'),
path('activate-member/<int:user_id>/<int:team_id>/', ActivateMemberView.as_view(), name='activate-member'),
path('activate-new-member/<uuid:verification_code>/<int:team_id>/', ActivateNewMemberView.as_view(), name='activate-new-member'),
path('password-reset/', PasswordResetRequestView.as_view(), name='password_reset_request'),
path('reset-password/<uidb64>/<token>/', PasswordResetConfirmView.as_view(), name='password_reset_confirm'),
path('users/<int:user_id>/last-goal/', UserLastGoalTimeView.as_view(), name='user-last-goal-time'),
path('teams/<int:team_id>/entries/', TeamEntriesView.as_view(), name='team-entries'),
#path('admin/newevent2/', NewEvent2AdminView.as_view(), name='newevent2-admin'),
path('newevent2-list/', views.NewEvent2ListView.as_view(), name='newevent2-list'),
#path('admin/newevent2/csv-upload/', NewEvent2Admin.as_view({'get': 'csv_upload_view', 'post': 'csv_upload_view'}), name='rog_newevent2_csv-upload'),
#path('admin/', admin.site.urls),
path('entries/<int:entry_id>/update-status/', update_entry_status, name='update-entry-status'),
# for Supervisor Web app
path('events/', views.get_events, name='get_events'),
path('debug/urls/', views.debug_urls, name='debug_urls'),
path('zekken_numbers/<str:event_code>/', views.get_zekken_numbers, name='get_zekken_numbers'),
path('team_info/<int:zekken_number>/', views.get_team_info, name='get_team_info'),
path('checkins/<int:zekken_number>/<str:event_code>/', views.get_checkins, name='get_checkins'),
path('update_checkins/', views.update_checkins, name='update_checkins'),
path('export_excel/<int:zekken_number>/<str:event_code>/', views.export_excel, name='export_excel'),
# for Supervisor Web app
path('test/', views.test_api, name='test_api'),
]
if settings.DEBUG:
# 開発環境でのメディアファイル提供
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

7
rog/users.txt Normal file
View File

@ -0,0 +1,7 @@
yamadeus3776@yahoo.co.jp
yamatetsu.ori@gmail.com
yoko_ane@hotmail.com
kajima603@gmail.com
mac10.suzuki@docomo.ne.jp
matsushita.masahiro@icloud.com

113
rog/utils.py Normal file
View File

@ -0,0 +1,113 @@
import os
from django.template.loader import render_to_string
from django.conf import settings
import logging
from django.core.mail import send_mail
from django.urls import reverse
import uuid
logger = logging.getLogger(__name__)
def load_email_template(template_name, context):
template_path = os.path.join('email', template_name)
email_content = render_to_string(template_path, context)
# 件名と本文を分離
subject, _, body = email_content.partition('\n\n')
subject = subject.replace('件名: ', '').strip()
# 件名と本文を分離し、件名から改行を削除
subject, _, body = email_content.partition('\n\n')
subject = subject.replace('件名: ', '').strip().replace('\n', ' ')
return subject, body
def share_send_email(subject, body, recipient_email):
try:
send_mail(subject, body, settings.DEFAULT_FROM_EMAIL, [recipient_email], fail_silently=False)
logger.info(f"メールを送信しました。 受信者: {recipient_email}")
except Exception as e:
logger.error(f"メールの送信に失敗しました。 受信者: {recipient_email}, エラー: {str(e)}")
raise # エラーを再度発生させて、呼び出し元で処理できるようにします
# 自らユーザー登録した際に、メールの確認メールを送る。
#
def send_verification_email(user, activation_link):
context = {
'name': user.firstname or user.email,
'activation_link': activation_link,
}
logger.info(f"send_verification_email : {context}")
subject, body = load_email_template('verification_email.txt', context)
share_send_email(subject,body,user.email)
def send_reset_password_email(email,activation_link):
context = {
'name': email,
'activation_link': activation_link,
}
logger.info(f"send_reset_password_email : {context}")
subject, body = load_email_template('reset_password_email.txt', context)
share_send_email(subject,body,email)
# 既にユーザーになっている人にチームへの参加要請メールを出す。
#
def send_team_join_email(request,sender,user,team):
activation_link = request.build_absolute_uri(
reverse('activate-member', args=[user.id, team.id])
)
logger.info(f"request: {request}")
context = {
'name': user.lastname or user.email,
'invitor': sender.lastname,
'activation_link': activation_link,
'team_name': team.team_name,
}
subject, body = load_email_template('invitation_existing_email.txt', context)
share_send_email(subject,body,user.email)
# まだユーザーでない人にチームメンバー招待メールを送る
# その人がユーザー登録して、ユーザー登録されるとメンバーになる。
# アプリからユーザー登録するため、アプリのダウンロードリンクも送る。
#
def send_invitation_email(sender,request,user_email,team):
verification_code = uuid.uuid4() # UUIDを生成
activation_link = request.build_absolute_uri(
reverse('activate-new-member', args=[verification_code, team.id])
)
context = {
'name': user_email,
'invitor': sender.lastname,
'team_name': team.team_name,
'activation_link': activation_link,
'app_download_link': settings.APP_DOWNLOAD_LINK,
'android_download_link': settings.ANDROID_DOWNLOAD_LINK,
}
subject, body = load_email_template('invitation_new_email.txt', context)
share_send_email(subject,body,user_email)
# 招待された後にユーザー登録された場合、ヴェリフィケーションでチーム参加登録される。
#
def send_invitaion_and_verification_email(user, team, activation_link):
context = {
'name': user.firstname or user.email,
'activation_link': activation_link,
'team_name': team.team_name,
}
logger.info(f"send_invitation_and_verification_email : {context}")
subject, body = load_email_template('invitation_and_verification_email.txt', context)
share_send_email(subject,body,user.email)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,20 @@
FROM python:3.11-slim
# CUPSとその他必要なパッケージのインストール
RUN apt-get update && apt-get install -y \
cups \
cups-client \
gcc \
libcups2-dev \
python3-dev \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY ./app .
CMD ["python", "main.py"]

144
rogaining_autoprint/README Normal file
View File

@ -0,0 +1,144 @@
システム管理
1. 環境変数の設定
必要な環境変数:
envCopyAWS_ACCESS_KEY_ID=your_access_key
AWS_SECRET_ACCESS_KEY=your_secret_key
AWS_REGION=us-west-2
S3_BUCKET_NAME=sumasenrogaining
LOCATION=美濃加茂
PRINTER_NAME=Brother_HL_L3240CDW_series
2.プリンター設定
3.1 CUPSの設定
# CUPSの状態確認
systemctl status cups
# CUPSが動いていない場合は起動
sudo systemctl start cups
sudo systemctl enable cups
# プリンター一覧の確認
lpstat -p -d
3.2 プリンターの権限設定
# プリンターデバイスの確認
ls -l /dev/usb/lp*
# プリンターデバイスの権限設定
sudo chmod 666 /dev/usb/lp0
3.3 ネットワークプリンターの場合
config.yamlのprinterセクションを以下のように設定
yamlCopyprinter:
name: ipp://192.168.1.100:631/ipp/port1
4. Dockerイメージのビルドと起動
4.1 イメージのビルド
bashCopy# イメージのビルド
docker-compose build
4.2 コンテナの起動
bashCopy# コンテナの起動(バックグラウンド)
docker-compose up -d
# ログの確認
docker-compose logs -f
5. 動作確認
5.1 ログの確認
bashCopy# アプリケーションログの確認
tail -f logs/app.log
# エラーログの確認
tail -f logs/error.log
5.2 テスト印刷
bashCopy# テスト用PDFファイルをS3にアップロード
aws s3 cp test.pdf s3://sumasenrogaining/美濃加茂/scoreboard/test.pdf
5.3 プロセスの確認
bashCopy# コンテナの状態確認
docker-compose ps
# コンテナ内のプロセス確認
docker-compose exec printer ps aux
6. トラブルシューティング
6.1 印刷できない場合
CUPSの状態確認
bashCopydocker-compose exec printer lpstat -t
プリンターの権限確認
bashCopydocker-compose exec printer ls -l /dev/usb/lp0
プリンター接続の確認
bashCopydocker-compose exec printer lpinfo -v
6.2 S3接続エラーの場合
認証情報の確認
bashCopydocker-compose exec printer env | grep AWS
S3バケットへのアクセス確認
bashCopydocker-compose exec printer python3 -c "
import boto3
s3 = boto3.client('s3')
response = s3.list_objects_v2(Bucket='sumasenrogaining')
print(response)
"
6.3 ログの確認方法
bashCopy# 直近のエラーログ
docker-compose exec printer tail -f /logs/error.log
# アプリケーションログ
docker-compose exec printer tail -f /logs/app.log
7. 運用とメンテナンス
7.1 定期的なメンテナンス
bashCopy# ログローテーション確認
ls -l logs/
# ディスク使用量確認
du -sh data/ logs/
# 古いPDFファイルの削除
find data/files -name "*.pdf" -mtime +30 -delete
7.2 バックアップ
bashCopy# 設定ファイルのバックアップ
tar czf config-backup-$(date +%Y%m%d).tar.gz .env config.yaml
# 処理済みファイルログのバックアップ
cp data/${LOCATION}.filelog data/${LOCATION}.filelog.bak
7.3 アップデート手順
bashCopy# コンテナの停止
docker-compose down
# 新しいイメージのビルド
docker-compose build --no-cache
# コンテナの再起動
docker-compose up -d
8. セキュリティ考慮事項
8.1 認証情報の管理
.envファイルのパーミッションを600に設定
AWSアクセスキーは定期的にローテーション
本番環境では.envファイルをgitにコミットしない
8.2 ネットワークセキュリティ
コンテナネットワークは必要最小限の公開
プリンターアクセスは内部ネットワークに制限
AWS S3へのアクセスは特定のIPに制限
8.3 モニタリング
CloudWatchなどでS3アクセスログを監視
印刷ジョブの異常を検知して通知
エラーログの定期チェック

View File

@ -0,0 +1,174 @@
import os
import json
import time
from pathlib import Path
from typing import List, Optional
import boto3
import cups
import yaml
from dotenv import load_dotenv
from loguru import logger
class Config:
def __init__(self):
load_dotenv()
with open('config.yaml', 'r') as f:
self.config = yaml.safe_load(f)
# 環境変数で設定値を上書き
self.config['s3']['bucket'] = os.getenv('S3_BUCKET_NAME', self.config['s3']['bucket'])
self.config['s3']['prefix'] = self.config['s3']['prefix'].replace('${LOCATION}', os.getenv('LOCATION', ''))
self.config['printer']['name'] = os.getenv('PRINTER_NAME', self.config['printer']['name'])
class S3Manager:
def __init__(self, config: Config):
self.config = config
self.s3_client = boto3.client('s3')
def list_files(self) -> List[str]:
"""S3バケットのファイル一覧を取得"""
try:
files = []
paginator = self.s3_client.get_paginator('list_objects_v2')
for page in paginator.paginate(
Bucket=self.config.config['s3']['bucket'],
Prefix=self.config.config['s3']['prefix']
):
if 'Contents' in page:
files.extend([obj['Key'] for obj in page['Contents']])
return files
except Exception as e:
logger.error(f"S3ファイル一覧の取得に失敗: {e}")
return []
def download_file(self, key: str, local_path: str) -> bool:
"""S3からファイルをダウンロード"""
try:
self.s3_client.download_file(
self.config.config['s3']['bucket'],
key,
local_path
)
return True
except Exception as e:
logger.error(f"ファイルのダウンロードに失敗 {key}: {e}")
return False
class PrinterManager:
def __init__(self, config: Config):
self.config = config
self.conn = cups.Connection()
def print_file(self, filepath: str) -> bool:
"""ファイルを印刷"""
printer_config = self.config.config['printer']
max_attempts = printer_config['retry']['max_attempts']
delay = printer_config['retry']['delay_seconds']
for attempt in range(max_attempts):
try:
job_id = self.conn.printFile(
printer_config['name'],
filepath,
"Rogaining Score",
printer_config['options']
)
logger.info(f"印刷ジョブを送信: {filepath} (Job ID: {job_id})")
return True
except Exception as e:
logger.error(f"印刷に失敗 (試行 {attempt + 1}/{max_attempts}): {e}")
if attempt < max_attempts - 1:
time.sleep(delay)
else:
return False
class FileManager:
def __init__(self, config: Config):
self.config = config
self.file_log_path = Path(self.config.config['app']['save_path']) / self.config.config['app']['file_log']
self.processed_files = self._load_processed_files()
def _load_processed_files(self) -> List[str]:
"""処理済みファイルの一覧を読み込み"""
try:
if self.file_log_path.exists():
with open(self.file_log_path, 'r') as f:
return json.load(f)
return []
except Exception as e:
logger.error(f"処理済みファイル一覧の読み込みに失敗: {e}")
return []
def save_processed_files(self):
"""処理済みファイルの一覧を保存"""
try:
self.file_log_path.parent.mkdir(parents=True, exist_ok=True)
with open(self.file_log_path, 'w') as f:
json.dump(self.processed_files, f)
except Exception as e:
logger.error(f"処理済みファイル一覧の保存に失敗: {e}")
def get_new_files(self, current_files: List[str]) -> List[str]:
"""新規ファイルを特定"""
return list(set(current_files) - set(self.processed_files))
def setup_logging(config: Config):
"""ロギングの設定"""
log_path = Path(config.config['app']['log_path'])
log_path.mkdir(parents=True, exist_ok=True)
logger.add(
log_path / "app.log",
rotation="1 day",
retention="30 days",
level="INFO"
)
logger.add(
log_path / config.config['app']['error_log'],
level="ERROR"
)
def main():
config = Config()
setup_logging(config)
s3_manager = S3Manager(config)
printer_manager = PrinterManager(config)
file_manager = FileManager(config)
logger.info("スコアボード監視システムを開始")
while True:
try:
# S3のファイル一覧を取得
current_files = s3_manager.list_files()
# 新規ファイルを特定
new_files = file_manager.get_new_files(current_files)
for file_key in new_files:
if not file_key.endswith('.pdf'):
file_manager.processed_files.append(file_key)
continue
# 保存先パスの設定
local_path = Path(config.config['app']['save_path']) / Path(file_key).name
local_path.parent.mkdir(parents=True, exist_ok=True)
# ファイルのダウンロードと印刷
if s3_manager.download_file(file_key, str(local_path)):
if printer_manager.print_file(str(local_path)):
file_manager.processed_files.append(file_key)
file_manager.save_processed_files()
time.sleep(10) # ポーリング間隔
except Exception as e:
logger.error(f"予期せぬエラーが発生: {e}")
time.sleep(30) # エラー時は長めの待機
if __name__ == "__main__":
main()

Some files were not shown because too many files have changed in this diff Show More