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, Location2025, 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, GpsLog, GpsCheckin, Checkpoint, Waypoint 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 _ from .services.csv_processor import EntryCSVProcessor @admin.register(Entry) class EntryAdmin(admin.ModelAdmin): list_display = ['team', 'zekken_number', 'event', 'category', 'date', 'is_active'] # change_list_templateの追加 change_list_template = 'admin/entry/change_list.html' # この行を追加 def get_urls(self): from django.urls import path urls = super().get_urls() custom_urls = [ path('upload-csv/', self.upload_csv_view, name='entry_upload_csv'), ] return custom_urls + urls def upload_csv_view(self, request): processor = EntryCSVProcessor() return processor.process_upload(request) @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 = Location2025.objects.filter(location_id = l.location_id).exists() if found: Location2025.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 = Location2025( 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() return True #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') @admin.register(CustomUser) 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') search_fields = ('egit mail', 'firstname', 'lastname', 'zekken_number') list_filter = ('is_staff', 'is_active', 'is_rogaining', 'group') ordering = ('email',) # 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_old(self, request, obj=None): # スーパーユーザーの場合は読み取り専用フィールドを最小限に if request.user.is_superuser: return self.readonly_fields # 通常のスタッフユーザーの場合は追加の制限を設定可能 return self.readonly_fields + ('is_staff', 'is_superuser') def get_readonly_fields(self, request, obj=None): if request.user.is_superuser: return ('date_joined', 'last_login') return ('date_joined', 'last_login', 'is_staff', 'is_superuser') admin.site.register(Useractions) admin.site.register(RogUser, admin.ModelAdmin) 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) # 古いtemplocationは無効化 - Location2025を使用 #admin.site.register(templocation, TempLocationAdmin) admin.site.register(GoalImages, admin.ModelAdmin) admin.site.register(CheckinImages, admin.ModelAdmin) # GpsLogとその他の新しいモデルの登録 @admin.register(GpsLog) class GpsLogAdmin(admin.ModelAdmin): list_display = ['id', 'serial_number', 'zekken_number', 'event_code', 'cp_number', 'checkin_time'] list_filter = ['event_code', 'checkin_time', 'buy_flag', 'is_service_checked'] search_fields = ['zekken_number', 'event_code', 'cp_number'] readonly_fields = ['checkin_time'] @admin.register(GpsCheckin) class GpsCheckinAdmin(admin.ModelAdmin): list_display = ['id', 'zekken', 'event_code', 'cp_number', 'checkin_time'] list_filter = ['event_code', 'checkin_time'] search_fields = ['zekken', 'event_code', 'cp_number'] readonly_fields = ['checkin_time', 'record_time'] @admin.register(Checkpoint) class CheckpointAdmin(admin.ModelAdmin): list_display = ['id', 'cp_name', 'cp_number', 'photo_point', 'buy_point'] search_fields = ['cp_name', 'cp_number'] list_filter = ['photo_point', 'buy_point'] readonly_fields = ['created_at', 'updated_at'] @admin.register(Waypoint) class WaypointAdmin(admin.ModelAdmin): list_display = ['id', 'entry', 'latitude', 'longitude', 'recorded_at'] search_fields = ['entry__team_name'] list_filter = ['recorded_at'] readonly_fields = ['created_at'] @admin.register(Location2025) class Location2025Admin(LeafletGeoAdmin): """Location2025の管理画面""" list_display = [ 'cp_number', 'cp_name', 'event', 'total_point', 'is_active', 'csv_upload_date', 'created_at' ] list_filter = [ 'event', 'is_active', 'shop_closed', 'shop_shutdown', 'csv_upload_date', 'created_at' ] search_fields = ['cp_name', 'address', 'description'] readonly_fields = [ 'csv_source_file', 'csv_upload_date', 'csv_upload_user', 'created_at', 'updated_at', 'created_by', 'updated_by' ] fieldsets = ( ('基本情報', { 'fields': ('cp_number', 'event', 'cp_name', 'is_active', 'sort_order') }), ('位置情報', { 'fields': ('latitude', 'longitude', 'location', 'address') }), ('ポイント設定', { 'fields': ('cp_point', 'photo_point', 'buy_point') }), ('チェックイン設定', { 'fields': ('checkin_radius', 'auto_checkin') }), ('営業情報', { 'fields': ('shop_closed', 'shop_shutdown', 'opening_hours') }), ('詳細情報', { 'fields': ('phone', 'website', 'description') }), ('CSV情報', { 'fields': ('csv_source_file', 'csv_upload_date', 'csv_upload_user'), 'classes': ('collapse',) }), ('管理情報', { 'fields': ('created_at', 'updated_at', 'created_by', 'updated_by'), 'classes': ('collapse',) }), ) # CSV一括アップロード機能 change_list_template = 'admin/location2025/change_list.html' def get_urls(self): from django.urls import path urls = super().get_urls() custom_urls = [ path('upload-csv/', self.upload_csv_view, name='location2025_upload_csv'), path('export-csv/', self.export_csv_view, name='location2025_export_csv'), ] return custom_urls + urls def upload_csv_view(self, request): """CSVアップロード画面""" if request.method == 'POST': if 'csv_file' in request.FILES and 'event' in request.POST: csv_file = request.FILES['csv_file'] event_id = request.POST['event'] try: from .models import NewEvent2 event = NewEvent2.objects.get(id=event_id) # CSVインポート実行 result = Location2025.import_from_csv( csv_file, event, user=request.user ) # 結果メッセージ if result['errors']: messages.warning( request, f"インポート完了: 作成{result['created']}件, 更新{result['updated']}件, " f"エラー{len(result['errors'])}件 - {'; '.join(result['errors'][:5])}" ) else: messages.success( request, f"CSVインポートが完了しました。作成: {result['created']}件, 更新: {result['updated']}件" ) except Exception as e: messages.error(request, f"CSVインポートエラー: {str(e)}") return redirect('..') # フォーム表示 - Location2025システム用 from .models import NewEvent2 # statusフィールドベースでアクティブなイベントを取得 events = NewEvent2.objects.filter(status='public').order_by('-start_datetime') return render(request, 'admin/location2025/upload_csv.html', { 'events': events, 'title': 'チェックポイントCSVアップロード' }) def export_csv_view(self, request): """CSVエクスポート""" import csv from django.http import HttpResponse from django.utils import timezone response = HttpResponse(content_type='text/csv; charset=utf-8') response['Content-Disposition'] = f'attachment; filename="checkpoints_{timezone.now().strftime("%Y%m%d_%H%M%S")}.csv"' # BOM付きUTF-8で出力 response.write('\ufeff') writer = csv.writer(response) writer.writerow([ 'cp_number', 'cp_name', 'latitude', 'longitude', 'cp_point', 'photo_point', 'buy_point', 'address', 'phone', 'description' ]) queryset = self.get_queryset(request) for obj in queryset: writer.writerow([ obj.cp_number, obj.cp_name, obj.latitude, obj.longitude, obj.cp_point, obj.photo_point, obj.buy_point, obj.address, obj.phone, obj.description ]) return response def save_model(self, request, obj, form, change): """保存時にユーザー情報を自動設定""" if not change: # 新規作成時 obj.created_by = request.user obj.updated_by = request.user super().save_model(request, obj, form, change)