Files
rogaining_srv/rog/admin.py

1208 lines
51 KiB
Python
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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'))
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('<a class="button" href="{}">CSVアップロード</a>', 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', 'sub_loc_id', 'subcategory',
'total_point', 'has_photos', 'has_videos', 'is_active',
'csv_upload_date', 'created_at'
]
list_filter = [
'event', 'is_active', 'shop_closed', 'shop_shutdown',
'subcategory', 'hidden_location',
'csv_upload_date', 'created_at'
]
search_fields = [
'cp_name', 'address', 'description', 'remark', 'tags',
'sub_loc_id', 'subcategory', 'evaluation_value'
]
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', 'sub_loc_id', 'subcategory', 'is_active', 'sort_order')
}),
('位置情報', {
'fields': ('latitude', 'longitude', 'location', 'address')
}),
('ポイント設定', {
'fields': ('checkin_point', 'buy_point')
}),
('チェックイン設定', {
'fields': ('checkin_radius', 'auto_checkin')
}),
('営業情報', {
'fields': ('shop_closed', 'shop_shutdown', 'opening_hours')
}),
('詳細情報', {
'fields': ('phone', 'website', 'description', 'remark')
}),
('メディア・タグ情報', {
'fields': ('photos', 'videos', 'tags', 'evaluation_value'),
'classes': ('wide',)
}),
('高度設定', {
'fields': ('hidden_location',),
'classes': ('collapse',)
}),
('CSV情報', {
'fields': ('csv_source_file', 'csv_upload_date', 'csv_upload_user'),
'classes': ('collapse',)
}),
('管理情報', {
'fields': ('created_at', 'updated_at', 'created_by', 'updated_by'),
'classes': ('collapse',)
}),
)
def has_photos(self, obj):
"""写真データ有無の表示"""
return bool(obj.photos and obj.photos.strip())
has_photos.boolean = True
has_photos.short_description = '写真'
def has_videos(self, obj):
"""動画データ有無の表示"""
return bool(obj.videos and obj.videos.strip())
has_videos.boolean = True
has_videos.short_description = '動画'
# 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_enhanced_{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', 'checkin_point',
'buy_point', 'address', 'phone', 'description',
'sub_loc_id', 'subcategory', 'photos', 'videos', 'tags',
'evaluation_value', 'remark', 'hidden_location'
])
queryset = self.get_queryset(request)
for obj in queryset:
writer.writerow([
obj.cp_number, obj.cp_name, obj.latitude, obj.longitude,
obj.checkin_point, obj.buy_point,
obj.address or '', obj.phone or '', obj.description or '',
obj.sub_loc_id or '', obj.subcategory or '', obj.photos or '',
obj.videos or '', obj.tags or '', obj.evaluation_value or '',
obj.remark or '', obj.hidden_location
])
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)