diff --git a/rog/admin.py b/rog/admin.py index 8641492..9bdc5c4 100755 --- a/rog/admin.py +++ b/rog/admin.py @@ -1067,7 +1067,7 @@ class Location2025Admin(LeafletGeoAdmin): 'fields': ('latitude', 'longitude', 'location', 'address') }), ('ポイント設定', { - 'fields': ('cp_point', 'photo_point', 'buy_point') + 'fields': ('checkin_point', 'buy_point') }), ('チェックイン設定', { 'fields': ('checkin_radius', 'auto_checkin') @@ -1167,29 +1167,35 @@ class Location2025Admin(LeafletGeoAdmin): }) def export_csv_view(self, request): - """CSVエクスポート""" + """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"' + 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', 'cp_point', - 'photo_point', 'buy_point', 'address', 'phone', 'description' + '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.cp_point, obj.photo_point, obj.buy_point, - obj.address, obj.phone, obj.description + 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 diff --git a/rog/migrations/0011_auto_20250830_0426.py b/rog/migrations/0011_auto_20250830_0426.py new file mode 100644 index 0000000..0ada611 --- /dev/null +++ b/rog/migrations/0011_auto_20250830_0426.py @@ -0,0 +1,28 @@ +# Generated by Django 3.2.9 on 2025-08-29 19:26 + +import django.contrib.gis.db.models.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('rog', '0010_add_missing_fields_to_location2025'), + ] + + operations = [ + # Location2025 field changes only - removing non-existent GpsCheckin operations + migrations.RemoveField( + model_name='location2025', + name='cp_point', + ), + migrations.RemoveField( + model_name='location2025', + name='photo_point', + ), + migrations.AddField( + model_name='location2025', + name='checkin_point', + field=models.IntegerField(default=10, verbose_name='チェックイン得点'), + ), + ] diff --git a/rog/migrations/0011_auto_20250830_0426.py.backup b/rog/migrations/0011_auto_20250830_0426.py.backup new file mode 100644 index 0000000..f15ce6f --- /dev/null +++ b/rog/migrations/0011_auto_20250830_0426.py.backup @@ -0,0 +1,184 @@ +# Generated by Django 3.2.9 on 2025-08-29 19:26 + +import django.contrib.gis.db.models.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('rog', '0010_add_missing_fields_to_location2025'), + ] + + operations = [ + migrations.AlterModelOptions( + name='gpscheckin', + options={'managed': True}, + ), + migrations.RemoveIndex( + model_name='gpscheckin', + name='idx_create_at', + ), + migrations.RemoveIndex( + model_name='gpscheckin', + name='idx_zekken_event', + ), + migrations.RemoveField( + model_name='gpscheckin', + name='buy_flag', + ), + migrations.RemoveField( + model_name='gpscheckin', + name='checkpoint', + ), + migrations.RemoveField( + model_name='gpscheckin', + name='colabo_company_memo', + ), + migrations.RemoveField( + model_name='gpscheckin', + name='create_at', + ), + migrations.RemoveField( + model_name='gpscheckin', + name='create_user', + ), + migrations.RemoveField( + model_name='gpscheckin', + name='goal_time', + ), + migrations.RemoveField( + model_name='gpscheckin', + name='image_qr', + ), + migrations.RemoveField( + model_name='gpscheckin', + name='image_receipt', + ), + migrations.RemoveField( + model_name='gpscheckin', + name='late_point', + ), + migrations.RemoveField( + model_name='gpscheckin', + name='minus_photo_flag', + ), + migrations.RemoveField( + model_name='gpscheckin', + name='points', + ), + migrations.RemoveField( + model_name='gpscheckin', + name='team', + ), + migrations.RemoveField( + model_name='gpscheckin', + name='team_name', + ), + migrations.RemoveField( + model_name='gpscheckin', + name='update_at', + ), + migrations.RemoveField( + model_name='gpscheckin', + name='update_user', + ), + migrations.RemoveField( + model_name='gpscheckin', + name='validate_location', + ), + migrations.RemoveField( + model_name='gpscheckin', + name='validated_at', + ), + migrations.RemoveField( + model_name='gpscheckin', + name='validated_by', + ), + migrations.RemoveField( + model_name='gpscheckin', + name='validation_comment', + ), + migrations.RemoveField( + model_name='gpscheckin', + name='validation_status', + ), + migrations.RemoveField( + model_name='location2025', + name='cp_point', + ), + migrations.RemoveField( + model_name='location2025', + name='photo_point', + ), + migrations.AddField( + model_name='gpscheckin', + name='checkpoint_id', + field=models.BigIntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='gpscheckin', + name='team_id', + field=models.BigIntegerField(blank=True, null=True), + ), + migrations.AddField( + model_name='location2025', + name='checkin_point', + field=models.IntegerField(default=10, verbose_name='チェックイン得点'), + ), + migrations.AlterField( + model_name='gpscheckin', + name='checkin_time', + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AlterField( + model_name='gpscheckin', + name='cp_number', + field=models.CharField(blank=True, max_length=20, null=True), + ), + migrations.AlterField( + model_name='gpscheckin', + name='event_code', + field=models.CharField(default='', max_length=255), + ), + migrations.AlterField( + model_name='gpscheckin', + name='event_id', + field=models.BigIntegerField(blank=True, null=True), + ), + migrations.AlterField( + model_name='gpscheckin', + name='lat', + field=models.FloatField(blank=True, null=True), + ), + migrations.AlterField( + model_name='gpscheckin', + name='lng', + field=models.FloatField(blank=True, null=True), + ), + migrations.AlterField( + model_name='gpscheckin', + name='location', + field=django.contrib.gis.db.models.fields.PointField(blank=True, null=True, srid=4326), + ), + migrations.AlterField( + model_name='gpscheckin', + name='mobserver_id', + field=models.IntegerField(blank=True, null=True), + ), + migrations.AlterField( + model_name='gpscheckin', + name='record_time', + field=models.DateTimeField(blank=True, null=True), + ), + migrations.AlterField( + model_name='gpscheckin', + name='serial_number', + field=models.CharField(blank=True, max_length=20, null=True), + ), + migrations.AlterField( + model_name='gpscheckin', + name='zekken', + field=models.CharField(blank=True, max_length=20, null=True), + ), + ] diff --git a/rog/models.py b/rog/models.py index 0d63737..d9504f2 100755 --- a/rog/models.py +++ b/rog/models.py @@ -1100,8 +1100,7 @@ class Location2025(models.Model): location = models.PointField(_('位置'), srid=4326, null=True, blank=True) # ポイント情報 - cp_point = models.IntegerField(_('チェックポイント得点'), default=10) - photo_point = models.IntegerField(_('写真ポイント'), default=0) + checkin_point = models.IntegerField(_('チェックイン得点'), default=10) buy_point = models.IntegerField(_('買い物ポイント'), default=0) # チェックイン設定 @@ -1171,19 +1170,21 @@ class Location2025(models.Model): @property def total_point(self): """総得点を計算""" - return self.cp_point + self.photo_point + self.buy_point + return self.checkin_point + self.buy_point @classmethod def import_from_csv(cls, csv_file, event, user=None): """ - CSVファイルからチェックポイントデータをインポート + CSVファイルからチェックポイントデータをインポート(全フィールド対応) CSV形式: - cp_number,cp_name,latitude,longitude,cp_point,photo_point,buy_point,address,phone,description + 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 """ import csv import io from django.utils import timezone + from django.contrib.gis.geos import Point if isinstance(csv_file, str): # ファイルパスの場合 @@ -1205,16 +1206,42 @@ class Location2025(models.Model): errors.append(f"行{row_num}: CP番号が無効です") continue + # 緯度経度から位置情報を作成 + latitude = float(row['latitude']) if row.get('latitude') else None + longitude = float(row['longitude']) if row.get('longitude') else None + location = None + if latitude and longitude: + location = Point(longitude, latitude) + + # hidden_locationのブール値変換 + hidden_location = False + if row.get('hidden_location'): + hidden_str = row.get('hidden_location', '').lower() + hidden_location = hidden_str in ['true', '1', 'yes', 'on'] + defaults = { + # 基本フィールド 'cp_name': row.get('cp_name', f'CP{cp_number}'), - 'latitude': float(row['latitude']) if row.get('latitude') else None, - 'longitude': float(row['longitude']) if row.get('longitude') else None, - 'cp_point': int(row.get('cp_point', 10)), - 'photo_point': int(row.get('photo_point', 0)), + 'latitude': latitude, + 'longitude': longitude, + 'location': location, + 'checkin_point': int(row.get('checkin_point', row.get('cp_point', 10))), # 後方互換性のためcp_pointもサポート 'buy_point': int(row.get('buy_point', 0)), 'address': row.get('address', ''), 'phone': row.get('phone', ''), 'description': row.get('description', ''), + + # 新しいフィールド + 'sub_loc_id': row.get('sub_loc_id', ''), + 'subcategory': row.get('subcategory', ''), + 'photos': row.get('photos', ''), + 'videos': row.get('videos', ''), + 'tags': row.get('tags', ''), + 'evaluation_value': row.get('evaluation_value', ''), + 'remark': row.get('remark', ''), + 'hidden_location': hidden_location, + + # 管理フィールド 'csv_source_file': getattr(csv_file, 'name', 'uploaded_file.csv'), 'csv_upload_date': timezone.now(), 'csv_upload_user': user, diff --git a/rog/serializers.py b/rog/serializers.py index a386b07..e28f878 100755 --- a/rog/serializers.py +++ b/rog/serializers.py @@ -60,7 +60,7 @@ class LocationSerializer(serializers.ModelSerializer): # 基本フィールド 'id', 'cp_number', 'event', 'cp_name', 'sub_loc_id', 'subcategory', 'latitude', 'longitude', 'location', 'address', - 'cp_point', 'photo_point', 'buy_point', + 'checkin_point', 'buy_point', 'checkin_radius', 'auto_checkin', 'shop_closed', 'shop_shutdown', 'opening_hours', 'phone', 'website', 'description', 'remark', diff --git a/templates/admin/location2025/upload_csv.html b/templates/admin/location2025/upload_csv.html index c53bac9..b61c123 100644 --- a/templates/admin/location2025/upload_csv.html +++ b/templates/admin/location2025/upload_csv.html @@ -38,9 +38,9 @@

CSVフォーマット

以下の形式でCSVファイルを作成してください:

-cp_number,cp_name,latitude,longitude,cp_point,photo_point,buy_point,address,phone,description
-1,岐阜駅前,35.4091,136.7581,10,0,0,岐阜県岐阜市橋本町1-10-1,058-123-4567,JR岐阜駅前広場
-2,岐阜城,35.4329,136.7817,15,5,0,岐阜県岐阜市金華山天守閣18,058-263-4853,金華山の頂上にある歴史ある城
+cp_number,cp_name,latitude,longitude,cp_point,photo_point,buy_point,address,phone,description,sub_loc_id,subcategory,photos,videos,tags,evaluation_value,remark,hidden_location
+1,岐阜駅前,35.4091,136.7581,10,0,0,岐阜県岐阜市橋本町1-10-1,058-123-4567,JR岐阜駅前広場,#1(10),交通,IMG_001.JPG,,駅を背景に撮影,50,岐阜市の中心駅です,false
+2,岐阜城,35.4329,136.7817,15,5,0,岐阜県岐阜市金華山天守閣18,058-263-4853,金華山の頂上にある歴史ある城,#2(20),史跡,IMG_002.JPG,VID_001.MP4,城と景色を撮影,85,織田信長ゆかりの名城,false
     

項目説明

@@ -54,8 +54,30 @@ cp_number,cp_name,latitude,longitude,cp_point,photo_point,buy_point,address,phon
  • buy_point: 買い物ポイント(デフォルト: 0)
  • address: 住所(任意)
  • phone: 電話番号(任意)
  • -
  • description: 説明(任意)
  • +
  • description: 基本説明(任意)
  • +
  • sub_loc_id: サブロケーションID(任意、例: #1(10))
  • +
  • subcategory: サブカテゴリ(任意、例: 史跡、観光名所、神社など)
  • +
  • photos: 写真ファイル名(任意、複数の場合はカンマ区切り)
  • +
  • videos: 動画ファイル名(任意、複数の場合はカンマ区切り)
  • +
  • tags: 撮影タグ(任意、例: 建物を背景に撮影)
  • +
  • evaluation_value: 評価値(任意、数値)
  • +
  • remark: 詳細説明(任意、長文可)
  • +
  • hidden_location: 隠しロケーション(true/false、デフォルト: false)
  • + +
    +

    📝 新フィールドの使用例

    + +
    {% endblock %} diff --git a/test_checkpoints_enhanced.csv b/test_checkpoints_enhanced.csv new file mode 100644 index 0000000..ea90c13 --- /dev/null +++ b/test_checkpoints_enhanced.csv @@ -0,0 +1,3 @@ +cp_number,cp_name,latitude,longitude,cp_point,photo_point,buy_point,address,phone,description,sub_loc_id,subcategory,photos,videos,tags,evaluation_value,remark,hidden_location +9999,テスト用チェックポイント,35.4091,136.7581,50,20,5,岐阜県岐阜市テスト町1-1,058-999-9999,CSVアップロードテスト用のチェックポイントです,#9999(75),テスト,TEST_IMG.JPG,TEST_VID.MP4,テスト用撮影タグ,95,これはCSVアップロード機能のテスト用データです。新しいフィールドがすべて正しくインポートされることを確認します。,false +9998,隠しテストポイント,35.4000,136.7500,100,50,0,岐阜県岐阜市隠れ町2-2,058-888-8888,隠しロケーションのテスト,#9998(150),秘密,SECRET_IMG.JPG,,秘密の撮影,100,隠しロケーションとして設定されたテスト用チェックポイント,true