diff --git a/docker-compose.yaml b/docker-compose.yaml index adfff2e..e091c8f 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -37,6 +37,7 @@ services: #entrypoint: ["/app/wait-for.sh", "postgres-db:5432", "--", ""] #command: python3 manage.py runserver 0.0.0.0:8100 + networks: rog-api: driver: bridge diff --git a/rog/admin.py b/rog/admin.py index 9274d55..c23ba9a 100644 --- a/rog/admin.py +++ b/rog/admin.py @@ -1,318 +1,981 @@ -import email -from django.contrib import admin -from django.shortcuts import render -from leaflet.admin import LeafletGeoAdmin -from leaflet.admin import LeafletGeoAdminMixin -from leaflet_admin_list.admin import LeafletAdminListMixin -from .models import RogUser, Location, SystemSettings, JoinedEvent, Favorite, TravelList, TravelPoint, ShapeLayers, Event, Location_line, Location_polygon, JpnAdminMainPerf, Useractions, CustomUser, GifuAreas, UserTracks, templocation, UserUpload, EventUser, GoalImages, CheckinImages, NewEvent, NewEvent2, Team, NewCategory, Category, Entry, Member, TempUser -from django.contrib.auth.admin import UserAdmin -from django.urls import path,reverse -from django.shortcuts import render -from django import forms; -import requests - -from django.http import HttpResponseRedirect -from django.utils.html import format_html -from .forms import CSVUploadForm -from .views import process_csv_upload - -class RogAdmin(LeafletAdminListMixin, LeafletGeoAdminMixin, admin.ModelAdmin): - list_display=['title', 'venue', 'at_date',] - -class ShopAdmin(LeafletAdminListMixin, LeafletGeoAdminMixin, admin.ModelAdmin): - list_display=['name',] - -class EventRouteAdmin(LeafletAdminListMixin, LeafletGeoAdminMixin, admin.ModelAdmin): - list_display=['name',] - -class ShopRouteAdmin(LeafletAdminListMixin, LeafletGeoAdminMixin, admin.ModelAdmin): - list_display=['name',] - -class loadUserForm(forms.Form): - server_url = forms.CharField(label="Load Data from *" ,initial='https://natnats.mobilous.com/get_team_list', widget=forms.Textarea(attrs={"rows":2, "cols":95})) - - -class UserAdminConfig(UserAdmin): - search_fields = ('email', 'group', 'zekken_number', 'event_code', 'team_name', 'is_rogaining') - list_filter = ('email', 'group', 'is_rogaining') - ordering = ('email',) - list_display = ('email', 'group','zekken_number', 'event_code', 'team_name', 'is_active', 'is_staff', 'is_rogaining') - - def get_urls(self): - urls = super().get_urls() - new_url = [path('load-users/', self.loadUsers),] - return new_url + urls - - def loadUsers(self, request): - - if request.method == "POST": - frm = loadUserForm(request.POST) - if frm.is_valid(): - print(frm.cleaned_data['server_url']) - #load json from server - url = frm.cleaned_data['server_url'] - response = requests.get(url) - data = response.json() - print("-------Event code--------") - print(data) - print("-------Event code--------") - for i in data: - _exist = CustomUser.objects.filter(email=i["zekken_number"]).delete() - other_fields.setDefaut('zekken_number',i['zekken_number']) - other_fields.setdefault('is_staff', True) - other_fields.setdefault('is_superuser', False) - other_fields.setdefault('is_active', True) - other_fields.setdefault('event_code', i['event_code']) - other_fields.setdefault('team_name', i['team_name']) - other_fields.setdefault('group', '大垣-初心者') - - usr = CustomUser.objects.create_user( - email=i["zekken_number"], - password=i['password'], - **other_fields - ) - - form = loadUserForm() - data = {'form': form} - return render(request, 'admin/load_users.html', data) - - fieldsets = ( - (None, {'fields':('email', 'group', 'zekken_number', 'event_code', 'team_name',)}), - ('Permissions', {'fields':('is_staff', 'is_active', 'is_rogaining')}), - ) - - add_fieldsets = ( - (None, {'classes':('wide',), 'fields':('email', 'group','zekken_number', 'event_code', 'team_name', 'password1', 'password2')}), - ) - -class JpnSubPerfAdmin(LeafletGeoAdmin): - search_fields = ('adm0_ja', 'adm1_ja', 'adm2_ja', 'name_modified', 'area_name',) - list_filter = ('adm0_ja', 'adm0_ja', 'name_modified',) - ordering = ('adm0_ja',) - list_display = ('adm0_ja','adm1_ja','adm2_ja' ,'name_modified', 'area_name',) - -class LocationAdmin(LeafletGeoAdmin): - search_fields = ('location_id', 'cp', 'location_name', 'category', 'event_name','group',) - list_filter = ('event_name', 'group',) - ordering = ('location_id', 'cp',) - list_display = ('location_id','sub_loc_id', 'cp', 'location_name', 'photos', 'category', 'group', 'event_name', 'event_active', 'auto_checkin', 'checkin_radius', 'checkin_point', 'buy_point',) - - -def tranfer_to_location(modeladmin, request, queryset): - tmp_locs = templocation.objects.all(); - for l in tmp_locs : - found = Location.objects.filter(location_id = l.location_id).exists() - if found: - Location.objects.filter(location_id = l.location_id).update( - sub_loc_id = l.sub_loc_id, - cp = l.cp, - location_name = l.location_name, - category = l.category, - subcategory = l.subcategory, - zip = l.zip, - address = l.address, - prefecture = l.prefecture, - area = l.area, - city = l.city, - latitude = l.latitude, - longitude = l.longitude, - photos = l.photos, - videos = l.videos, - webcontents = l.webcontents, - status = l.status, - portal = l.portal, - group = l.group, - phone = l.phone, - fax = l.fax, - email = l.email, - facility = l.facility, - remark = l.remark, - tags = l.tags, - hidden_location = l.hidden_location, - auto_checkin = l.auto_checkin, - checkin_radius = l.checkin_radius, - checkin_point = l.checkin_point, - buy_point = l.buy_point, - evaluation_value = l.evaluation_value, - shop_closed = l.shop_closed, - shop_shutdown = l.shop_shutdown, - opening_hours_mon = l.opening_hours_mon, - opening_hours_tue = l.opening_hours_tue, - opening_hours_wed = l.opening_hours_wed, - opening_hours_thu = l.opening_hours_thu, - opening_hours_fri = l.opening_hours_fri, - opening_hours_sat = l.opening_hours_sat, - opening_hours_sun = l.opening_hours_sun, - geom=l.geom - ) - else: - loc = Location( - location_id=l.location_id, - sub_loc_id = l.sub_loc_id, - cp = l.cp, - location_name = l.location_name, - category = l.category, - subcategory = l.subcategory, - zip = l.zip, - address = l.address, - prefecture = l.prefecture, - area = l.area, - city = l.city, - latitude = l.latitude, - longitude = l.longitude, - photos = l.photos, - videos = l.videos, - webcontents = l.webcontents, - status = l.status, - portal = l.portal, - group = l.group, - phone = l.phone, - fax = l.fax, - email = l.email, - facility = l.facility, - remark = l.remark, - tags = l.tags, - hidden_location = l.hidden_location, - auto_checkin = l.auto_checkin, - checkin_radius = l.checkin_radius, - checkin_point = l.checkin_point, - buy_point = l.buy_point, - evaluation_value = l.evaluation_value, - shop_closed = l.shop_closed, - shop_shutdown = l.shop_shutdown, - opening_hours_mon = l.opening_hours_mon, - opening_hours_tue = l.opening_hours_tue, - opening_hours_wed = l.opening_hours_wed, - opening_hours_thu = l.opening_hours_thu, - opening_hours_fri = l.opening_hours_fri, - opening_hours_sat = l.opening_hours_sat, - opening_hours_sun = l.opening_hours_sun, - geom=l.geom - ) - loc.save() - l.delete() -tranfer_to_location.short_description = "Transfer all locations in temp table to location table" - - -class TempLocationAdmin(LeafletGeoAdmin): - search_fields = ('location_id', 'cp', 'location_name', 'category', 'event_name',) - list_filter = ('category', 'event_name',) - ordering = ('location_id', 'cp',) - list_display = ('location_id','cp', 'location_name', 'category', 'event_name', 'event_active', 'auto_checkin', 'checkin_radius', 'checkin_point', 'buy_point',) - actions = [tranfer_to_location,] - - -@admin.register(NewEvent2) -class NewEvent2Admin(admin.ModelAdmin): - list_display = ['event_name', 'start_datetime', 'end_datetime', 'csv_upload_button'] - - def get_urls(self): - urls = super().get_urls() - my_urls = [ - path('csv-upload/', self.admin_site.admin_view(self.csv_upload_view), name='newevent2_csv_upload'), - ] - return my_urls + urls - - def csv_upload_view(self, request): - if request.method == 'POST': - form = CSVUploadForm(request.POST, request.FILES) - if form.is_valid(): - csv_file = request.FILES['csv_file'] - event = form.cleaned_data['event'] - process_csv_upload(csv_file, event) - self.message_user(request, "CSV file has been processed successfully.") - return HttpResponseRedirect("../") - else: - form = CSVUploadForm() - - return render(request, 'admin/csv_upload.html', {'form': form}) - - def csv_upload_button(self, obj): - url = reverse('admin:newevent2_csv_upload') - return format_html('CSVアップロード', url) - csv_upload_button.short_description = 'CSV Upload' - - def changelist_view(self, request, extra_context=None): - extra_context = extra_context or {} - extra_context['csv_upload_url'] = reverse('admin:newevent2_csv_upload') - return super().changelist_view(request, extra_context=extra_context) - - -@admin.register(Team) -class TeamAdmin(admin.ModelAdmin): - list_display = ['team_name', 'owner'] - search_fields = ['team_name', 'owner__email'] - -@admin.register(NewCategory) -class NewCategoryAdmin(admin.ModelAdmin): - list_display = ['category_name', 'category_number', 'duration', 'num_of_member', 'family', 'female'] - list_filter = ['family', 'female'] - search_fields = ['category_name'] - -@admin.register(Entry) -class EntryAdmin(admin.ModelAdmin): - list_display = ['team', 'event', 'category', 'date'] - list_filter = ['event', 'category'] - search_fields = ['team__team_name', 'event__event_name'] - -@admin.register(Member) -class MemberAdmin(admin.ModelAdmin): - list_display = ['team', 'user'] - search_fields = ['team__team_name', 'user__email'] - -@admin.register(TempUser) -class TempUserAdmin(admin.ModelAdmin): - list_display = ['email', 'is_rogaining', 'zekken_number', 'event_code', 'team_name', 'group', 'created_at', 'expires_at'] - list_filter = ['is_rogaining', 'group'] - search_fields = ['email', 'zekken_number', 'team_name'] - - -# CustomUserAdmin の修正(既存のものを更新) -class CustomUserAdmin(UserAdmin): - list_display = ('email', 'is_staff', 'is_active', 'is_rogaining', 'zekken_number', 'event_code', 'team_name', 'group', 'firstname', 'lastname') - list_filter = ('is_staff', 'is_active', 'is_rogaining', 'group') - fieldsets = ( - (None, {'fields': ('email', 'password')}), - ('Personal info', {'fields': ('firstname', 'lastname', 'date_of_birth', 'female')}), - ('Permissions', {'fields': ('is_staff', 'is_active', 'is_rogaining','user_permissions')}), - ('Rogaining info', {'fields': ('zekken_number', 'event_code', 'team_name', 'group')}), - ) - add_fieldsets = ( - (None, { - 'classes': ('wide',), - #'fields': ('email', 'password1', 'password2', 'is_staff', 'is_active', 'is_rogaining')} - 'fields': ('email', 'password1', 'password2', 'lastname','firstname', 'date_of_birth', 'female','is_staff', 'is_active', 'is_rogaining')} - ), - ) - search_fields = ('email', 'firstname', 'lastname', 'zekken_number', 'team_name') - ordering = ('email',) - -admin.site.register(Useractions) -admin.site.register(RogUser, admin.ModelAdmin) -admin.site.register(Location, LocationAdmin) -admin.site.register(SystemSettings, admin.ModelAdmin) -admin.site.register(JoinedEvent, admin.ModelAdmin) -admin.site.register(Favorite, admin.ModelAdmin) -admin.site.register(TravelList, admin.ModelAdmin) -admin.site.register(TravelPoint, admin.ModelAdmin) -admin.site.register(Event, admin.ModelAdmin) -admin.site.register(Location_line, LeafletGeoAdmin) -admin.site.register(Location_polygon, LeafletGeoAdmin) -admin.site.register(JpnAdminMainPerf, LeafletGeoAdmin) -admin.site.register(UserTracks, LeafletGeoAdmin); -#admin.site.register(JpnAdminPerf, LeafletGeoAdmin) -admin.site.register(GifuAreas, LeafletGeoAdmin) -admin.site.register(ShapeLayers, admin.ModelAdmin) -admin.site.register(UserUpload, admin.ModelAdmin) -admin.site.register(EventUser, admin.ModelAdmin) -#admin.site.register(UserUploadUser, admin.ModelAdmin) -#admin.site.register(ShapeFileLocations, admin.ModelAdmin) - -admin.site.register(CustomUser, UserAdminConfig) -admin.site.register(templocation, TempLocationAdmin) -admin.site.register(GoalImages, admin.ModelAdmin) -admin.site.register(CheckinImages, admin.ModelAdmin) - - - - +import email +from django.contrib import admin +from django.shortcuts import render,redirect +from leaflet.admin import LeafletGeoAdmin +from leaflet.admin import LeafletGeoAdminMixin +from leaflet_admin_list.admin import LeafletAdminListMixin +from .models import RogUser, Location, SystemSettings, JoinedEvent, Favorite, TravelList, TravelPoint, ShapeLayers, Event, Location_line, Location_polygon, JpnAdminMainPerf, Useractions, CustomUser, GifuAreas, UserTracks, templocation, UserUpload, EventUser, GoalImages, CheckinImages, NewEvent2, Team, NewCategory, Entry, Member, TempUser,GifurogeRegister +from django.contrib.auth.admin import UserAdmin +from django.urls import path,reverse +from django.shortcuts import render +from django import forms; +import requests + +from django.http import HttpResponseRedirect +from django.utils.html import format_html +from .forms import CSVUploadForm +from .views import process_csv_upload + +from django.db.models import F # F式をインポート +from django.db import transaction +from django.contrib import messages +import csv +from io import StringIO,TextIOWrapper +from datetime import timedelta +from django.contrib.auth.hashers import make_password +from datetime import datetime, date +from django.core.exceptions import ValidationError + +from django.contrib.auth.forms import UserChangeForm, UserCreationForm +from django.utils.translation import gettext_lazy as _ + +@admin.register(GifurogeRegister) +class GifurogeRegisterAdmin(admin.ModelAdmin): + list_display = ('event_code', 'time', 'owner_name', 'email', 'team_name', 'department') + change_list_template = 'admin/rog/gifurogeregister/change_list.html' # この行を追加 + + def find_matching_category(self, time, department): + """ + 時間とdepartmentに基づいて適切なカテゴリを見つける + """ + try: + duration = timedelta(hours=time) + + # 検索前の情報出力 + print(f" Searching for category with parameters:") + print(f" - Duration: {duration}") + print(f" - Department: {department}") + + # 利用可能なカテゴリの一覧を出力 + all_categories = NewCategory.objects.all() + print(" Available categories:") + for cat in all_categories: + #print(f" - ID: {cat.id}") + print(f" - Name: {cat.category_name}") + print(f" - Duration: {cat.duration}") + print(f" - Number: {cat.category_number}") + + # カテゴリ検索のクエリをログ出力 + query = NewCategory.objects.filter( + duration=duration, + category_name__startswith=department + ) + print(f" Query SQL: {query.query}") + + # 検索結果の取得 + category = query.first() + + if category: + print(f" Found matching category:") + print(f" - Name: {category.category_name}") + print(f" - Duration: {category.duration}") + print(f" - Category Number: {getattr(category, 'category_number', 'N/A')}") + + else: + print(" No matching category found with the following filters:") + print(f" - Duration equals: {duration}") + print(f" - Category name starts with: {department}") + + return category + + except Exception as e: + print(f"Error finding category: {e}") + print(f"Exception type: {type(e)}") + import traceback + print(f"Traceback: {traceback.format_exc()}") + return None + + def create_entry_with_number(self, team, category, owner, event): + """ + カテゴリ番号をインクリメントしてエントリーを作成 + """ + try: + with transaction.atomic(): + # 事前バリデーション + try: + # チームメンバーの性別をチェック + if category.female: + for member in team.members.all(): + + print(f" Check existing member {member.user.lastname} {member.user.firstname} female:{member.user.female}") + if not member.user.female: + raise ValidationError(f"チーム '{team.team_name}' に男性メンバーが含まれているため、" + f"カテゴリー '{category.category_name}' には参加できません。") + except ValidationError as e: + print(f"Pre-validation error: {str(e)}") + raise + + # カテゴリを再度ロックして取得 + category_for_update = NewCategory.objects.select_for_update().get( + category_name=category.category_name + ) + + print(f" Creating entry with following details:") + print(f" - Category: {category_for_update.category_name}") + print(f" - Current category number: {category_for_update.category_number}") + + # イベントの日付を取得 + entry_date = event.start_datetime.date() + + # 既存のエントリーをチェック + existing_entry = Entry.objects.filter( + team=team, + event=event, + date=entry_date, + is_active=True # アクティブなエントリーのみをチェック + ).first() + + if existing_entry: + print(f" Found existing entry for team {team.team_name} on {entry_date}") + raise ValidationError( + f"Team {team.team_name} already has an entry for event {event.event_name} on {entry_date}" + ) + + # 現在の番号を取得してインクリメント + current_number = category_for_update.category_number + zekken_number = current_number + + # カテゴリ番号をインクリメント + category_for_update.category_number = F('category_number') + 1 + category_for_update.save() + + # 変更後の値を取得して表示 + category_for_update.refresh_from_db() + print(f" Updated category number: {category_for_update.category_number}") + + # エントリーの作成 + try: + entry = Entry.objects.create( + date=event.start_datetime, + team=team, + category=category, + owner=owner, + event=event, + zekken_number=zekken_number, + is_active=True + ) + # バリデーションを実行 + entry.full_clean() + # 問題なければ保存 + entry.save() + + print(f" Created entry:") + print(f" - Team: {team.team_name}") + print(f" - Event: {event.event_name}") + print(f" - Category: {category.category_name}") + print(f" - Zekken Number: {zekken_number}") + + return entry + + except ValidationError as e: + print(f"Entry validation error: {str(e)}") + raise + + except Exception as e: + print(f"Error creating entry: {e}") + print(f"Exception type: {type(e)}") + import traceback + print(f"Traceback: {traceback.format_exc()}") + raise + + def split_full_name(self, full_name): + """ + フルネームを姓と名に分割 + 半角または全角スペースに対応 + """ + try: + # 空白文字で分割(半角スペース、全角スペース、タブなど) + parts = full_name.replace(' ', ' ').split() + if len(parts) >= 2: + last_name = parts[0] + first_name = ' '.join(parts[1:]) # 名が複数単語の場合に対応 + return last_name, first_name + else: + # 分割できない場合は全体を姓とする + return full_name, '' + except Exception as e: + print(f"Error splitting name '{full_name}': {e}") + return full_name, '' + + def convert_japanese_date(self, date_text): + """ + 日本式の日付テキストをDateField形式に変換 + 例: '1990年1月1日' -> datetime.date(1990, 1, 1) + """ + try: + if not date_text or date_text.strip() == '': + return None + + # 全角数字を半角数字に変換 + date_text = date_text.translate(str.maketrans('0123456789', '0123456789')) + date_text = date_text.strip() + + # 区切り文字の判定と分割 + if '年' in date_text: + # 年月日形式の場合 + date_parts = date_text.replace('年', '-').replace('月', '-').replace('日', '').split('-') + elif '/' in date_text: + # スラッシュ区切りの場合 + date_parts = date_text.split('/') + elif '-' in date_text: + date_parts = date_text.split('-') + else: + print(f"Unsupported date format: {date_text}") + return None + + # 部分の数を確認 + if len(date_parts) != 3: + print(f"Invalid date parts count: {len(date_parts)} in '{date_text}'") + return None + + year = int(date_parts[0]) + month = int(date_parts[1]) + day = int(date_parts[2]) + + # 簡単な妥当性チェック + if not (1900 <= year <= 2100): + print(f"Invalid year: {year}") + return None + if not (1 <= month <= 12): + print(f"Invalid month: {month}") + return None + if not (1 <= day <= 31): # 月ごとの日数チェックは省略 + print(f"Invalid day: {day}") + return None + + print(f"Converted from {date_text} to year-{year} / month-{month} / day-{day}") + + return date(year, month, day) + + except Exception as e: + print(f"Error converting date '{date_text}': {str(e)}") + return None + + def create_owner_member( self,team,row ): + """ + オーナーをチームメンバー1として作成 + 既存のメンバーは更新 + """ + try: + owner_name = row.get('owner_name').strip() + # 姓名を分割 + last_name, first_name = self.split_full_name(owner_name) + print(f" Split name - Last: {last_name}, First: {first_name}") + # 誕生日の処理 + birthday = row.get(f'owner_birthday', '').strip() + birth_date = self.convert_japanese_date(birthday) + print(f" Converted birthday: {birth_date}") + + # 性別の処理 + sex = row.get(f'owner_sex', '').strip() + is_female = sex in ['女性','女','女子','female','girl','lady'] + print(f" Sex: {sex}, is_female: {is_female}") + + # メンバーを作成 + member,created = Member.objects.get_or_create( + team=team, + user=team.owner, + defaults={ + 'is_temporary': True # 仮登録 + } + ) + + + # 既存メンバーの場合は情報を更新 + if not created: + member.lastname = last_name + member.firstname = first_name + member.date_of_birth = birth_date + member.female = is_female + member.is_temporary = True + member.save() + print(f" Updated existing member {last_name} {first_name}") + else: + print(f" Created new member {last_name} {first_name}") + + return member + + except Exception as e: + print(f"Error creating/updating member: {e}") + raise + + def create_members(self, team, row): + """ + チームのメンバーを作成 + 既存のメンバーは更新 + """ + try: + created_members = [] + + # オーナーをメンバーに登録 + member = self.create_owner_member(team,row) + created_members.append(member) + + # メンバー2から5までを処理 + for i in range(2, 6): + member_name = row.get(f'member{i}', '').strip() + if member_name: + print(f"===== Processing member: {member_name} =====") + + # 姓名を分割 + last_name, first_name = self.split_full_name(member_name) + print(f" Split name - Last: {last_name}, First: {first_name}") + + # 誕生日の処理 + birthday = row.get(f'birthday{i}', '').strip() + birth_date = self.convert_japanese_date(birthday) + print(f" Converted birthday: {birth_date}") + + # 性別の処理 + sex = row.get(f'sex{i}', '').strip() + is_female = sex in ['女性','女','女子','female','girl','lady'] + print(f" Sex: {sex}, is_female: {is_female}") + + # メンバー用のユーザーを作成 + email = f"dummy_{team.id}_{i}@gifuai.net".lower() + member_user, created = CustomUser.objects.get_or_create( + email=email, + defaults={ + 'password': make_password('temporary_password'), + 'lastname': last_name, + 'firstname': first_name, + 'date_of_birth': birth_date, + 'female':is_female + } + ) + + # 既存ユーザーの場合も姓名を更新 + if not created: + member_user.lastname = last_name + member_user.firstname = first_name + member_user.date_of_birth = birth_date + member_user.female = is_female + member_user.save() + + try: + # メンバーを作成 + member,created = Member.objects.get_or_create( + team=team, + user=member_user, + defaults={ + 'is_temporary': True # 仮登録 + } + ) + + # 既存メンバーの場合は情報を更新 + if not created: + member.is_temporary = True + member.save() + print(f" Updated existing member {member_user.lastname} {member_user.firstname}") + else: + print(f" Created new member {member_user.lastname} {member_user.firstname}") + + created_members.append(member) + print(f" - Birthday: {member_user.date_of_birth}") + print(f" - Sex: {'Female' if member_user.female else 'Male'}") + + except Exception as e: + print(f"Error creating/updating member: {e}") + raise + + return created_members + + except Exception as e: + print(f"Error creating members: {e}") + print(f"Exception type: {type(e)}") + import traceback + print(f"Traceback: {traceback.format_exc()}") + raise + + def get_urls(self): + urls = super().get_urls() + custom_urls = [ + path('upload-csv/', self.upload_csv, name='gifuroge_register_upload_csv'), + ] + return custom_urls + urls + + def upload_csv(self, request): + print("upload_csv") + if request.method == 'POST': + print("POST") + if 'csv_file' not in request.FILES: + messages.error(request, 'No file was uploaded.') + return redirect('..') + + csv_file = request.FILES['csv_file'] + print(f"csv_file(1) = {csv_file}") + if not csv_file.name.endswith('.csv'): + messages.error(request, 'File is not CSV type') + return redirect('..') + + try: + # BOMを考慮してファイルを読み込む + file_content = csv_file.read() + # BOMがある場合は除去 + if file_content.startswith(b'\xef\xbb\xbf'): + file_content = file_content[3:] + + # デコード + file_content = file_content.decode('utf-8') + csv_file = StringIO(file_content) + reader = csv.DictReader(csv_file) + + print(f"csv_file(2) = {csv_file}") + print(f"reader = {reader}") + + with transaction.atomic(): + for row in reader: + print(f" row={row}") + + # オーナーの姓名を分割 + owner_lastname, owner_firstname = self.split_full_name(row['owner_name']) + + # パスワードをハッシュ化 + hashed_password = make_password(row['password']) + + # オーナーの誕生日の処理 + owner_birthday = row.get('owner_birthday', '').strip() + owner_birth_date = self.convert_japanese_date(owner_birthday) + print(f" Owner birthday: {owner_birth_date}") + + # オーナーの性別の処理 + owner_sex = row.get('owner_sex', '').strip() + owner_is_female = owner_sex in ['女性','女','女子','female','girl','lady'] + print(f" Owner sex: {owner_sex}, is_female: {owner_is_female}") + + # ユーザーの取得または作成 + user, created = CustomUser.objects.get_or_create( + email=row['email'], + defaults={ + 'password': hashed_password, # make_password(row['password']) + 'lastname': owner_lastname, + 'firstname': owner_firstname, + 'date_of_birth': owner_birth_date, + 'female': owner_is_female + } + ) + + if not created: + # 既存ユーザーの場合、空のフィールドがあれば更新 + should_update = False + update_fields = [] + + print(f" Checking existing user data for {user.email}:") + print(f" - Current lastname: '{user.lastname}'") + print(f" - Current firstname: '{user.firstname}'") + print(f" - Current birth date: {user.date_of_birth}") + print(f" - Current female: {user.female}") + + # 姓が空またはNoneの場合 + if not user.lastname or user.lastname.strip() == '': + user.lastname = owner_lastname + should_update = True + update_fields.append('lastname') + print(f" - Updating lastname to: {owner_lastname}") + + # 名が空またはNoneの場合 + if not user.firstname or user.firstname.strip() == '': + user.firstname = owner_firstname + should_update = True + update_fields.append('firstname') + print(f" - Updating firstname to: {owner_firstname}") + + # 生年月日が空またはNoneの場合 + if not user.date_of_birth and owner_birth_date: + user.date_of_birth = owner_birth_date + should_update = True + update_fields.append('date_of_birth') + print(f" - Updating birth date to: {owner_birth_date}") + + # 性別が空またはNoneの場合 + # Booleanフィールドなのでis None で判定 + if user.female is None: + user.female = owner_is_female + should_update = True + update_fields.append('female') + print(f" - Updating female to: {owner_is_female}") + + # パスワードが'登録済み'でない場合のみ更新 + if row['password'] != '登録済み': + user.password = hashed_password + should_update = True + update_fields.append('password') + print(f" - Updating password") + + # 変更があった場合のみ保存 + if should_update: + try: + # 特定のフィールドのみを更新 + user.save(update_fields=update_fields) + print(f" Updated user {user.email} fields: {', '.join(update_fields)}") + except Exception as e: + print(f" Error updating user {user.email}: {str(e)}") + raise + else: + print(f" No updates needed for user {user.email}") + + + print(f" user created...") + print(f" Owner member created: {user.lastname} {user.firstname}") + print(f" - Birthday: {user.date_of_birth}") + print(f" - Sex: {'Female' if user.female else 'Male'}") + + # 適切なカテゴリを見つける + category = self.find_matching_category( + time=int(row['time']), + department=row['department'] + ) + + if not category: + raise ValueError( + f"No matching category found for time={row['time']} minutes " + f"and department={row['department']}" + ) + + print(f" Using category: {category.category_name}") + + # Teamの作成(既存のチームがある場合は取得) + team, created = Team.objects.get_or_create( + team_name=row['team_name'], + defaults={ + 'owner': user, + 'category': category + } + ) + + # 既存のチームの場合でもカテゴリを更新 + if not created: + team.category = category + team.save() + + print(" team created/updated...") + + self.create_members(team, row) + + # イベントの検索 + try: + event_code = row['event_code'] + event = NewEvent2.objects.get(event_name=event_code) + print(f" Found event: {event.event_name}") + except NewEvent2.DoesNotExist: + raise ValueError(f"Event with code {event_code} does not exist") + + try: + # エントリーの作成 + entry = self.create_entry_with_number( + team=team, + category=category, + owner=user, + event=event, + ) + + print(" entry created...") + except ValidationError as e: + messages.error(request, str(e)) + return redirect('..') + + gifuroge_register = GifurogeRegister.objects.create( + event_code=row['event_code'], + time=int(row['time']), + owner_name_kana=row['owner_name_kana'], + owner_name=row['owner_name'], + owner_birthday=self.convert_japanese_date(row['owner_birthday']), + owner_sex=row['owner_sex'], + email=row['email'], + password=row['password'], + team_name=row['team_name'], + department=row['department'], + members_count=int(row['members_count']), + member2=row.get('member2', '') or None, + birthday2=self.convert_japanese_date(row.get('birthday2', '') ), + sex2=row.get('sex2', '') or None, + member3=row.get('member3', '') or None, + birthday3=self.convert_japanese_date(row.get('birthday3', '') ), + sex3=row.get('sex3', '') or None, + member4=row.get('member4', '') or None, + birthday4=self.convert_japanese_date(row.get('birthday4', '') ), + sex4=row.get('sex4', '') or None, + member5=row.get('member5', '') or None, + birthday5=self.convert_japanese_date(row.get('birthday5', '') ), + sex5=row.get('sex5', '') or None + ) + print(f" saved gifuroge_register...") + + except UnicodeDecodeError: + messages.error(request, 'File encoding error. Please ensure the file is UTF-8 encoded.') + return redirect('..') + except Exception as e: + print(f"Error processing row: {e}") + raise + + messages.success(request, 'CSV file uploaded successfully') + return redirect('..') + + return render(request, 'admin/rog/gifurogeregister/upload-csv.html') + +class RogAdmin(LeafletAdminListMixin, LeafletGeoAdminMixin, admin.ModelAdmin): + list_display=['title', 'venue', 'at_date',] + +class ShopAdmin(LeafletAdminListMixin, LeafletGeoAdminMixin, admin.ModelAdmin): + list_display=['name',] + +class EventRouteAdmin(LeafletAdminListMixin, LeafletGeoAdminMixin, admin.ModelAdmin): + list_display=['name',] + +class ShopRouteAdmin(LeafletAdminListMixin, LeafletGeoAdminMixin, admin.ModelAdmin): + list_display=['name',] + +class loadUserForm(forms.Form): + server_url = forms.CharField(label="Load Data from *" ,initial='https://natnats.mobilous.com/get_team_list', widget=forms.Textarea(attrs={"rows":2, "cols":95})) + + +class UserAdminConfig(UserAdmin): + search_fields = ('email', 'group', 'zekken_number', 'event_code', 'team_name', 'is_rogaining') + list_filter = ('email', 'group', 'is_rogaining') + ordering = ('email',) + list_display = ('email', 'group','zekken_number', 'event_code', 'team_name', 'is_active', 'is_staff', 'is_rogaining') + + def get_urls(self): + urls = super().get_urls() + new_url = [path('load-users/', self.loadUsers),] + return new_url + urls + + def loadUsers(self, request): + + if request.method == "POST": + frm = loadUserForm(request.POST) + if frm.is_valid(): + print(frm.cleaned_data['server_url']) + #load json from server + url = frm.cleaned_data['server_url'] + response = requests.get(url) + data = response.json() + print("-------Event code--------") + print(data) + print("-------Event code--------") + for i in data: + _exist = CustomUser.objects.filter(email=i["zekken_number"]).delete() + other_fields.setDefaut('zekken_number',i['zekken_number']) + other_fields.setdefault('is_staff', True) + other_fields.setdefault('is_superuser', False) + other_fields.setdefault('is_active', True) + other_fields.setdefault('event_code', i['event_code']) + other_fields.setdefault('team_name', i['team_name']) + other_fields.setdefault('group', '大垣-初心者') + + usr = CustomUser.objects.create_user( + email=i["zekken_number"], + password=i['password'], + **other_fields + ) + + form = loadUserForm() + data = {'form': form} + return render(request, 'admin/load_users.html', data) + + """ + fieldsets = ( + (None, {'fields':('email', 'group', 'zekken_number', 'event_code', 'team_name',)}), + ('Permissions', {'fields':('is_staff', 'is_active', 'is_rogaining')}), + ) + + add_fieldsets = ( + (None, {'classes':('wide',), 'fields':('email', 'group','zekken_number', 'event_code', 'team_name', 'password1', 'password2')}), + ) + """ + # readonly_fieldsを明示的に設定 + readonly_fields = ('date_joined',) # 変更不可のフィールドのみを指定=>Personal Infoも編集可能にする。 + + fieldsets = ( + (None, {'fields': ('email', 'password')}), + (_('Personal info'), { + 'fields': ('firstname', 'lastname', 'date_of_birth', 'female'), + 'classes': ('wide',) # フィールドの表示を広げる + }), + (_('Permissions'), {'fields': ('is_staff', 'is_active', 'is_rogaining','user_permissions')}), + (_('Rogaining info'), { + 'fields': ('zekken_number', 'event_code', 'team_name', 'group'), + 'classes': ('wide',) + }), + (_('Important dates'), { + 'fields': ('date_joined','last_login'), + 'classes': ('wide',) + }), # 読み取り専用 + ) + add_fieldsets = ( + (None, { + 'classes': ('wide',), + #'fields': ('email', 'password1', 'password2', 'is_staff', 'is_active', 'is_rogaining')} + 'fields': ('email', 'password1', 'password2', 'lastname','firstname', 'date_of_birth', 'female','is_staff', 'is_active', 'is_rogaining')} + ), + ) + search_fields = ('email', 'firstname', 'lastname', 'zekken_number', 'team_name') + ordering = ('email',) + +class JpnSubPerfAdmin(LeafletGeoAdmin): + search_fields = ('adm0_ja', 'adm1_ja', 'adm2_ja', 'name_modified', 'area_name',) + list_filter = ('adm0_ja', 'adm0_ja', 'name_modified',) + ordering = ('adm0_ja',) + list_display = ('adm0_ja','adm1_ja','adm2_ja' ,'name_modified', 'area_name',) + +class LocationAdmin(LeafletGeoAdmin): + search_fields = ('location_id', 'cp', 'location_name', 'category', 'event_name','group',) + list_filter = ('event_name', 'group',) + ordering = ('location_id', 'cp',) + list_display = ('location_id','sub_loc_id', 'cp', 'location_name', 'photos', 'category', 'group', 'event_name', 'event_active', 'auto_checkin', 'checkin_radius', 'checkin_point', 'buy_point',) + + +def tranfer_to_location(modeladmin, request, queryset): + tmp_locs = templocation.objects.all(); + for l in tmp_locs : + found = Location.objects.filter(location_id = l.location_id).exists() + if found: + Location.objects.filter(location_id = l.location_id).update( + sub_loc_id = l.sub_loc_id, + cp = l.cp, + location_name = l.location_name, + category = l.category, + subcategory = l.subcategory, + zip = l.zip, + address = l.address, + prefecture = l.prefecture, + area = l.area, + city = l.city, + latitude = l.latitude, + longitude = l.longitude, + photos = l.photos, + videos = l.videos, + webcontents = l.webcontents, + status = l.status, + portal = l.portal, + group = l.group, + phone = l.phone, + fax = l.fax, + email = l.email, + facility = l.facility, + remark = l.remark, + tags = l.tags, + hidden_location = l.hidden_location, + auto_checkin = l.auto_checkin, + checkin_radius = l.checkin_radius, + checkin_point = l.checkin_point, + buy_point = l.buy_point, + evaluation_value = l.evaluation_value, + shop_closed = l.shop_closed, + shop_shutdown = l.shop_shutdown, + opening_hours_mon = l.opening_hours_mon, + opening_hours_tue = l.opening_hours_tue, + opening_hours_wed = l.opening_hours_wed, + opening_hours_thu = l.opening_hours_thu, + opening_hours_fri = l.opening_hours_fri, + opening_hours_sat = l.opening_hours_sat, + opening_hours_sun = l.opening_hours_sun, + geom=l.geom + ) + else: + loc = Location( + location_id=l.location_id, + sub_loc_id = l.sub_loc_id, + cp = l.cp, + location_name = l.location_name, + category = l.category, + subcategory = l.subcategory, + zip = l.zip, + address = l.address, + prefecture = l.prefecture, + area = l.area, + city = l.city, + latitude = l.latitude, + longitude = l.longitude, + photos = l.photos, + videos = l.videos, + webcontents = l.webcontents, + status = l.status, + portal = l.portal, + group = l.group, + phone = l.phone, + fax = l.fax, + email = l.email, + facility = l.facility, + remark = l.remark, + tags = l.tags, + hidden_location = l.hidden_location, + auto_checkin = l.auto_checkin, + checkin_radius = l.checkin_radius, + checkin_point = l.checkin_point, + buy_point = l.buy_point, + evaluation_value = l.evaluation_value, + shop_closed = l.shop_closed, + shop_shutdown = l.shop_shutdown, + opening_hours_mon = l.opening_hours_mon, + opening_hours_tue = l.opening_hours_tue, + opening_hours_wed = l.opening_hours_wed, + opening_hours_thu = l.opening_hours_thu, + opening_hours_fri = l.opening_hours_fri, + opening_hours_sat = l.opening_hours_sat, + opening_hours_sun = l.opening_hours_sun, + geom=l.geom + ) + loc.save() + l.delete() +tranfer_to_location.short_description = "Transfer all locations in temp table to location table" + + +class TempLocationAdmin(LeafletGeoAdmin): + search_fields = ('location_id', 'cp', 'location_name', 'category', 'event_name',) + list_filter = ('category', 'event_name',) + ordering = ('location_id', 'cp',) + list_display = ('location_id','cp', 'location_name', 'category', 'event_name', 'event_active', 'auto_checkin', 'checkin_radius', 'checkin_point', 'buy_point',) + actions = [tranfer_to_location,] + + +@admin.register(NewEvent2) +class NewEvent2Admin(admin.ModelAdmin): + list_display = ['event_name', 'start_datetime', 'end_datetime', 'csv_upload_button'] + + def get_urls(self): + urls = super().get_urls() + my_urls = [ + path('csv-upload/', self.admin_site.admin_view(self.csv_upload_view), name='newevent2_csv_upload'), + ] + return my_urls + urls + + def csv_upload_view(self, request): + if request.method == 'POST': + form = CSVUploadForm(request.POST, request.FILES) + if form.is_valid(): + csv_file = request.FILES['csv_file'] + event = form.cleaned_data['event'] + process_csv_upload(csv_file, event) + self.message_user(request, "CSV file has been processed successfully.") + return HttpResponseRedirect("../") + else: + form = CSVUploadForm() + + return render(request, 'admin/csv_upload.html', {'form': form}) + + def csv_upload_button(self, obj): + url = reverse('admin:newevent2_csv_upload') + return format_html('CSVアップロード', url) + csv_upload_button.short_description = 'CSV Upload' + + def changelist_view(self, request, extra_context=None): + extra_context = extra_context or {} + extra_context['csv_upload_url'] = reverse('admin:newevent2_csv_upload') + return super().changelist_view(request, extra_context=extra_context) + + +@admin.register(Team) +class TeamAdmin(admin.ModelAdmin): + list_display = ['team_name', 'owner'] + search_fields = ['team_name', 'owner__email'] + +@admin.register(NewCategory) +class NewCategoryAdmin(admin.ModelAdmin): + list_display = ['category_name', 'category_number', 'duration', 'num_of_member', 'family', 'female'] + list_filter = ['family', 'female'] + search_fields = ['category_name'] + +@admin.register(Entry) +class EntryAdmin(admin.ModelAdmin): + list_display = ['team', 'event', 'category', 'date'] + list_filter = ['event', 'category'] + search_fields = ['team__team_name', 'event__event_name'] + +@admin.register(Member) +class MemberAdmin(admin.ModelAdmin): + list_display = ['team', 'user'] + search_fields = ['team__team_name', 'user__email'] + +@admin.register(TempUser) +class TempUserAdmin(admin.ModelAdmin): + list_display = ['email', 'is_rogaining', 'zekken_number', 'event_code', 'team_name', 'group', 'created_at', 'expires_at'] + list_filter = ['is_rogaining', 'group'] + search_fields = ['email', 'zekken_number', 'team_name'] + + +# CustomUserAdmin の修正(既存のものを更新) +class CustomUserChangeForm(UserChangeForm): + class Meta(UserChangeForm.Meta): + model = CustomUser + fields = '__all__' + +class CustomUserCreationForm(UserCreationForm): + class Meta(UserCreationForm.Meta): + model = CustomUser + fields = ('email', 'lastname', 'firstname', 'date_of_birth', 'female') + +class CustomUserAdmin(UserAdmin): + form = CustomUserChangeForm + add_form = CustomUserCreationForm + model = CustomUser + + list_display = ('email', 'is_staff', 'is_active', 'is_rogaining', 'zekken_number', 'event_code', 'team_name', 'group', 'firstname', 'lastname') + list_filter = ('is_staff', 'is_active', 'is_rogaining', 'group') + + # readonly_fieldsを明示的に設定 + readonly_fields = ('date_joined',) # 変更不可のフィールドのみを指定=>Personal Infoも編集可能にする。 + + fieldsets = ( + (None, {'fields': ('email', 'password')}), + (_('Personal info'), { + 'fields': ('firstname', 'lastname', 'date_of_birth', 'female'), + 'classes': ('wide',) # フィールドの表示を広げる + }), + (_('Permissions'), {'fields': ('is_staff', 'is_active', 'is_rogaining','user_permissions')}), + (_('Rogaining info'), { + 'fields': ('zekken_number', 'event_code', 'team_name', 'group'), + 'classes': ('wide',) + }), + (_('Important dates'), { + 'fields': ('date_joined','last_login'), + 'classes': ('wide',) + }), # 読み取り専用 + ) + add_fieldsets = ( + (None, { + 'classes': ('wide',), + #'fields': ('email', 'password1', 'password2', 'is_staff', 'is_active', 'is_rogaining')} + 'fields': ('email', 'password1', 'password2', 'lastname','firstname', 'date_of_birth', 'female','is_staff', 'is_active', 'is_rogaining')} + ), + ) + search_fields = ('email', 'firstname', 'lastname', 'zekken_number', 'team_name') + ordering = ('email',) + + def get_readonly_fields(self, request, obj=None): + # スーパーユーザーの場合は読み取り専用フィールドを最小限に + if request.user.is_superuser: + return self.readonly_fields + # 通常のスタッフユーザーの場合は追加の制限を設定可能 + return self.readonly_fields + ('is_staff', 'is_superuser') + + +admin.site.register(Useractions) +admin.site.register(RogUser, admin.ModelAdmin) +admin.site.register(Location, LocationAdmin) +admin.site.register(SystemSettings, admin.ModelAdmin) +admin.site.register(JoinedEvent, admin.ModelAdmin) +admin.site.register(Favorite, admin.ModelAdmin) +admin.site.register(TravelList, admin.ModelAdmin) +admin.site.register(TravelPoint, admin.ModelAdmin) +admin.site.register(Event, admin.ModelAdmin) +admin.site.register(Location_line, LeafletGeoAdmin) +admin.site.register(Location_polygon, LeafletGeoAdmin) +admin.site.register(JpnAdminMainPerf, LeafletGeoAdmin) +admin.site.register(UserTracks, LeafletGeoAdmin); +#admin.site.register(JpnAdminPerf, LeafletGeoAdmin) +admin.site.register(GifuAreas, LeafletGeoAdmin) +admin.site.register(ShapeLayers, admin.ModelAdmin) +admin.site.register(UserUpload, admin.ModelAdmin) +admin.site.register(EventUser, admin.ModelAdmin) +#admin.site.register(UserUploadUser, admin.ModelAdmin) +#admin.site.register(ShapeFileLocations, admin.ModelAdmin) + +admin.site.register(CustomUser, UserAdminConfig) +admin.site.register(templocation, TempLocationAdmin) +admin.site.register(GoalImages, admin.ModelAdmin) +admin.site.register(CheckinImages, admin.ModelAdmin) + + + + diff --git a/rog/migration_scripts.py b/rog/migration_scripts.py new file mode 100644 index 0000000..958ce04 --- /dev/null +++ b/rog/migration_scripts.py @@ -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() diff --git a/rog/models.py b/rog/models.py index d12d5d5..4339589 100644 --- a/rog/models.py +++ b/rog/models.py @@ -72,6 +72,31 @@ 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): @@ -345,7 +370,7 @@ class Member(models.Model): unique_together = ('team', 'user') def __str__(self): - return f"{self.team.zekken_number} - {self.user.lastname} {self.user.firstname}" + return f"{self.team.team_name} - {self.user.lastname} {self.user.firstname}" # class Category(models.Model): @@ -504,6 +529,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) diff --git a/rog/templates/admin/gifuroge_register_changelist.html b/rog/templates/admin/gifuroge_register_changelist.html new file mode 100644 index 0000000..93000f4 --- /dev/null +++ b/rog/templates/admin/gifuroge_register_changelist.html @@ -0,0 +1,11 @@ +{% extends "admin/change_list.html" %} +{% load i18n admin_urls %} + +{% block object-tools-items %} + {{ block.super }} +