diff --git a/config/settings.py b/config/settings.py index a26b01c..69991cf 100644 --- a/config/settings.py +++ b/config/settings.py @@ -252,3 +252,5 @@ PASSWORD_HASHERS = [ 'django.contrib.auth.hashers.BCryptSHA256PasswordHasher', ] +BLACKLISTED_IPS = ['44.230.58.114'] # ブロックしたい IP アドレスをここにリストとして追加 + diff --git a/rog/admin.py b/rog/admin.py index bcfb85b..9274d55 100644 --- a/rog/admin.py +++ b/rog/admin.py @@ -6,11 +6,16 @@ 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 +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',] @@ -196,10 +201,42 @@ class TempLocationAdmin(LeafletGeoAdmin): 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'] - search_fields = ['event_name'] + 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): diff --git a/rog/forms.py b/rog/forms.py new file mode 100644 index 0000000..347bc66 --- /dev/null +++ b/rog/forms.py @@ -0,0 +1,7 @@ +from django import forms +from .models import NewEvent2 + +class CSVUploadForm(forms.Form): + event = forms.ModelChoiceField(queryset=NewEvent2.objects.all(), label="イベント選択") + csv_file = forms.FileField(label="CSVファイル") + diff --git a/rog/serializers.py b/rog/serializers.py index ff22e65..fb96b62 100644 --- a/rog/serializers.py +++ b/rog/serializers.py @@ -489,6 +489,17 @@ class EntrySerializer(serializers.ModelSerializer): event = data.get('event') category = data.get('category') entry_date = data.get('date') + if isinstance(entry_date, datetime): + entry_date = entry_date.date() + elif isinstance(entry_date, str): + entry_date = datetime.strptime(entry_date, "%Y-%m-%d").date() + + logger.debug(f"==== entry_date : {entry_date} ====") + + if entry_date < event.start_datetime.date() or entry_date > event.end_datetime.date(): + raise serializers.ValidationError(f"日付は{event.start_datetime.date()}から{event.end_datetime.date()}の間である必要があります。") + + owner = self.context['request'].user zekken_number = data.get('zekken_number') @@ -534,6 +545,11 @@ class EntrySerializer(serializers.ModelSerializer): ret['event'] = NewEvent2Serializer(instance.event).data ret['category'] = NewCategorySerializer(instance.category).data ret['owner'] = CustomUserSerializer(instance.owner).data + + if isinstance(ret['date'], datetime): + ret['date'] = ret['date'].date().isoformat() + elif isinstance(ret['date'], date): + ret['date'] = ret['date'].isoformat() return ret #if isinstance(ret['date'], datetime): diff --git a/rog/templates/admin/csv_upload.html b/rog/templates/admin/csv_upload.html new file mode 100644 index 0000000..9349cb4 --- /dev/null +++ b/rog/templates/admin/csv_upload.html @@ -0,0 +1,24 @@ +{% extends "admin/base_site.html" %} +{% load i18n admin_urls %} + +{% block content %} +
+
+ {% csrf_token %} +
+ {% for field in form %} +
+ {{ field.errors }} + {{ field.label_tag }} {{ field }} + {% if field.help_text %} +

{{ field.help_text|safe }}

+ {% endif %} +
+ {% endfor %} +
+
+ +
+
+
+{% endblock %} diff --git a/rog/urls.py b/rog/urls.py index ee5c2c6..e75a6ef 100644 --- a/rog/urls.py +++ b/rog/urls.py @@ -9,7 +9,13 @@ from .views import TestActionViewSet from .views import OwnerEntriesView, OwnerTeamsView, OwnerMembersView +from django.urls import path +from . import views +#from .views import NewEvent2AdminView + + router = DefaultRouter() +router.register(r'newevent2', views.NewEvent2ViewSet) router.register(prefix='location', viewset=LocationViewSet, basename='location') router.register(prefix='location_line', viewset=Location_lineViewSet, basename="location_line") @@ -37,6 +43,7 @@ router.register(r'members', MemberViewSet, basename='member') router.register(r'teams/(?P\d+)/members', MemberViewSet, basename='team-members') router.register(r'categories-viewset', NewCategoryViewSet) +router.register(r'newevent2', views.NewEvent2ViewSet) # Akira 追加 # GET /api/members//user/: 特定のメンバーのユーザー情報を取得 @@ -96,5 +103,9 @@ urlpatterns += [ path('reset-password///', PasswordResetConfirmView.as_view(), name='password_reset_confirm'), path('users//last-goal/', UserLastGoalTimeView.as_view(), name='user-last-goal-time'), path('teams//entries/', TeamEntriesView.as_view(), name='team-entries'), + #path('admin/newevent2/', NewEvent2AdminView.as_view(), name='newevent2-admin'), + path('newevent2-list/', views.NewEvent2ListView.as_view(), name='newevent2-list'), + #path('admin/newevent2/csv-upload/', NewEvent2Admin.as_view({'get': 'csv_upload_view', 'post': 'csv_upload_view'}), name='rog_newevent2_csv-upload'), + #path('admin/', admin.site.urls), ] diff --git a/rog/views.py b/rog/views.py index f0cf73e..32d5c0b 100644 --- a/rog/views.py +++ b/rog/views.py @@ -70,9 +70,127 @@ from django.contrib.gis.geos import Point from django.contrib.gis.db.models.functions import Distance from django.utils.dateparse import parse_date +from django.utils import timezone + +import csv +import io +from django.contrib import admin +from django.shortcuts import render +from django.http import HttpResponseRedirect +from django.urls import path +from django import forms +from .models import NewEvent2, CustomUser, Team, NewCategory, Entry, Member, TempUser +from django.core.mail import send_mail +from django.conf import settings +from django.db import transaction +from django.core.exceptions import ValidationError logger = logging.getLogger(__name__) +def process_csv_upload(csv_file, event): + decoded_file = csv_file.read().decode('utf-8') + io_string = io.StringIO(decoded_file) + reader = csv.reader(io_string) + next(reader) # ヘッダーをスキップ + + for row in reader: + try: + owner_email, owner_name, owner_grade, owner_gender, team_name, category_name, *member_data = row + # ここでデータを処理し、必要なオブジェクトを作成します + # 例: create_or_update_team(owner_email, owner_name, owner_grade, owner_gender, team_name, category_name, event) + # 例: process_members(member_data, team) + except ValidationError as e: + # エラーハンドリング + pass + + +class CSVUploadForm(forms.Form): + event = forms.ModelChoiceField(queryset=NewEvent2.objects.all()) + csv_file = forms.FileField() + +class NewEvent2Admin(admin.ModelAdmin): + list_display = ['event_name', 'start_datetime', 'end_datetime'] + + def get_urls(self): + urls = super().get_urls() + my_urls = [ + path('csv-upload/', self.admin_site.admin_view(self.csv_upload_view), name='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(): + event = form.cleaned_data['event'] + csv_file = request.FILES['csv_file'] + csv_data = csv_file.read().decode('utf-8') + csv_reader = csv.reader(io.StringIO(csv_data)) + next(csv_reader) # ヘッダーをスキップ + + for row in csv_reader: + self.process_team(event, row) + + self.message_user(request, "CSV file has been processed successfully.") + return HttpResponseRedirect("..") + else: + form = CSVUploadForm() + + context = { + 'form': form, + 'title': 'Upload CSV file', + } + return render(request, 'admin/csv_upload.html', context) + + @transaction.atomic + def process_team(self, event, row): + owner_data = row[:4] + team_name = row[4] + category_name = row[5] + members_data = row[6:] + + owner = self.create_or_get_user(*owner_data) + category = NewCategory.objects.get(category_name=category_name) + team, _ = Team.objects.get_or_create(owner=owner, team_name=team_name, category=category) + + entry, _ = Entry.objects.get_or_create( + team=team, + event=event, + category=category, + owner=owner, + date=event.start_datetime.date() + ) + + for i in range(0, len(members_data), 4): + member_data = members_data[i:i+4] + if member_data[0]: # メールアドレスがある場合のみ処理 + member = self.create_or_get_user(*member_data) + Member.objects.get_or_create(team=team, user=member) + + def create_or_get_user(self, email, name, grade, gender): + is_dummy = False + if not email: + email = f"dummy_{uuid.uuid4()}@example.com" + is_dummy = True + + user, created = CustomUser.objects.get_or_create(email=email) + if created or is_dummy: + user.firstname, user.lastname = name.split(' ', 1) if ' ' in name else (name, '') + user.grade = grade + user.gender = gender + user.is_active = not is_dummy + user.save() + + if not is_dummy: + self.send_invitation_email(user) + + return user + + def send_invitation_email(self, user): + subject = 'Invitation to join the event' + message = f'Please click the following link to activate your account: {settings.SITE_URL}/activate/{user.id}/' + send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, [user.email]) + class LocationViewSet(viewsets.ModelViewSet): queryset=Location.objects.all() @@ -1060,12 +1178,12 @@ class RegistrationView(APIView): class NewEvent2ViewSet(viewsets.ModelViewSet): queryset = NewEvent2.objects.all() serializer_class = NewEvent2Serializer - permission_classes = [IsAuthenticated] + permission_classes = [permissions.IsAuthenticated] class NewEvent2ListView(generics.ListAPIView): queryset = NewEvent2.objects.all() serializer_class = NewEvent2Serializer - permission_classes = [IsAuthenticated] + permission_classes = [permissions.IsAuthenticated] class NewEventViewSet(viewsets.ModelViewSet): queryset = NewEvent.objects.all() @@ -1370,6 +1488,15 @@ class EntryViewSet(viewsets.ModelViewSet): def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) + + # イベントの締め切り日時をチェック + event = serializer.validated_data['event'] + if event.deadlineDateTime and timezone.now() > event.deadlineDateTime: + return Response( + {"error": "締め切りを過ぎているため、エントリーを作成できません。"}, + status=status.HTTP_400_BAD_REQUEST + ) + try: self.perform_create(serializer) headers = self.get_success_headers(serializer.data)