views.py
1 import json 2 import logging 3 from datetime import datetime, timedelta 4 5 import b4mad_racing_website.fastlap_app # noqa: F401 6 import b4mad_racing_website.pitcrew_app # noqa: F401 7 from django.contrib import messages 8 from django.contrib.auth.mixins import LoginRequiredMixin 9 from django.forms.models import BaseModelForm 10 from django.http import Http404, HttpResponse, HttpResponseNotAllowed, JsonResponse 11 from django.shortcuts import get_object_or_404, redirect, render 12 from django.urls import reverse 13 from django.views.generic.base import RedirectView, TemplateView 14 from django.views.generic.detail import DetailView 15 from django.views.generic.edit import UpdateView 16 from django.views.generic.list import ListView 17 18 from telemetry.models import Car, Coach, Driver, Game, Lap, Session, Track 19 from telemetry.racing_stats import RacingStats 20 21 from .forms import ProfileForm 22 from .models import Copilot, Profile 23 24 logger = logging.getLogger(__name__) 25 26 27 class HomePageView(TemplateView): 28 template_name = "site/home.html" 29 30 # add the 15 most current sessions to the context 31 def get_context_data(self, **kwargs): 32 context = super().get_context_data(**kwargs) 33 34 stats = RacingStats() 35 combos = stats.combos(range=0.25) 36 context["combos"] = combos 37 38 return context 39 40 41 class AboutPageView(TemplateView): 42 template_name = "site/about.html" 43 44 45 class HelpPageView(TemplateView): 46 template_name = "site/help.html" 47 48 49 class HelpInstallPageView(TemplateView): 50 template_name = "site/help-install.html" 51 52 53 class CopilotsOverviewView(ListView): 54 model = Copilot 55 template_name = "copilots/overview.html" 56 57 58 class CopilotDetailsView(DetailView): 59 model = Copilot 60 template_name = "copilots/details.html" 61 62 63 class ProfileRedirectView(RedirectView): 64 """This view will redirect the user to their own profile page or, if the user is not logged in, to the home page.""" 65 66 def get(self, request, *args, **kwargs): 67 # lets check if the user is logged in 68 if request.user.is_authenticated: 69 # if the user is logged in, redirect to their profile page 70 return redirect(reverse("profile", kwargs={"slug": request.user.username})) 71 72 # if not, redirect to the home page 73 return redirect(reverse("home")) 74 75 76 class ProfileDetailView(DetailView): 77 model = Profile 78 template_name = "profile/details.html" 79 80 def get(self, request, *args, **kwargs): 81 # if the profile cant be found, redirect to the home page 82 try: 83 # if the user is logged in, create a profile if it doesn't exist 84 if request.user.is_authenticated: 85 self.object, _created = Profile.objects.get_or_create(user=request.user) 86 else: 87 raise Http404 88 except Http404: 89 return redirect(reverse("home")) 90 91 # if the profile is not publicly visible and the user is not the owner of the profile, return 403 92 if (not self.object.publicly_visible) and (not request.user == self.object.user): 93 return redirect(reverse("home")) 94 95 context = self.get_context_data(object=self.object) 96 context["is_myself"] = self.object.user == request.user 97 98 # Let's use the logged in user's profile name to find a Driver 99 if self.request.user.is_authenticated: 100 try: 101 context["driver"] = Driver.objects.get(name=self.request.user.profile.mqtt_drivername) 102 except Driver.DoesNotExist: 103 context["driver"] = None 104 messages.add_message( 105 request, messages.WARNING, "We CANT find you mqtt driver name in the telemetry database." 106 ) 107 108 # now get the last 5 sessons for this driver 109 if context["driver"]: 110 context["sessions"] = Session.objects.filter(driver=context["driver"]).order_by("-created")[:5] 111 112 stats = RacingStats() 113 circuit_combos = stats.driver_combos(context["driver"]) 114 context["circuit_combos"] = circuit_combos 115 116 rally_combos = stats.driver_combos(context["driver"], type="rally") 117 context["rally_combos"] = rally_combos 118 119 return self.render_to_response(context) 120 121 122 class ProfileUpdateView(LoginRequiredMixin, UpdateView): 123 model = Profile 124 # fields = ["newsletter_allowed", "publicly_visible", "mqtt_drivername"] 125 form_class = ProfileForm 126 127 def form_valid(self, form: BaseModelForm) -> HttpResponse: 128 # This method is called when valid form data has been POSTed. 129 130 # FIXME: this has to be done in the model instead of the view 131 # enable the coach and set it to copilots mode 132 mqtt_drivername = self.object.mqtt_drivername 133 driver = Driver.objects.filter(name=mqtt_drivername).first() 134 if driver: 135 self.object.driver = driver 136 self.object.save() 137 coach = Coach.objects.get_or_create(driver=driver)[0] 138 coach.enabled = True 139 coach.mode = Coach.MODE_COPILOTS 140 coach.save() 141 return super().form_valid(form) 142 143 144 class ProfileSubscriptionsUpdateView(LoginRequiredMixin, UpdateView): 145 model = Profile 146 fields = ["mqtt_drivername"] 147 148 def get(self, request, *args, **kwargs): 149 return HttpResponseNotAllowed(["PUT", "DELETE"]) 150 151 def post(self, request, *args, **kwargs): 152 return HttpResponseNotAllowed(["PUT", "DELETE"]) 153 154 # pylint: disable=arguments-differ 155 def put(self, request, *args, **kwargs): 156 # Check if the user is updating their own profile 157 if request.user.username != kwargs["slug"]: 158 return JsonResponse({"status": "error", "message": "You can't update another user's profile"}, status=403) 159 160 # Check if the content type is application/json 161 if request.content_type != "application/json": 162 return JsonResponse({"status": "error", "message": "Invalid content type"}, status=415) 163 164 # Get the data from the request 165 try: 166 data = json.loads(request.body) 167 copilot_id = data.get("copilot_id") 168 except json.JSONDecodeError: 169 return JsonResponse({"status": "error", "message": "Invalid JSON data"}, status=400) 170 171 # Check if the copilot exists 172 try: 173 _ = Copilot.objects.get(pk=copilot_id) 174 except Copilot.DoesNotExist: 175 return JsonResponse({"status": "error", "message": "Copilot does not exist"}, status=404) 176 177 # Update the user's subscriptions based on the received data 178 request.user.profile.subscribe_copilot(copilot_id) 179 logger.debug("subscribe copilot %s to user %s, saving...", copilot_id, request.user.username) 180 181 # Return a JSON response with status code 204 182 return JsonResponse({}, status=204) 183 184 def delete(self, request, *args, **kwargs): 185 """This method will remove the copilot from the user's profile.subscriptions.""" 186 187 # Check if the user is updating their own profile 188 if request.user.username != kwargs["slug"]: 189 return JsonResponse({"status": "error", "message": "You can't update another user's profile"}, status=403) 190 191 # Get the data from the request 192 try: 193 data = json.loads(request.body) 194 copilot_id = data.get("copilot_id") 195 except json.JSONDecodeError: 196 return JsonResponse({"status": "error", "message": "Invalid JSON data"}, status=400) 197 198 # Check if the copilot exists 199 try: 200 _ = Copilot.objects.get(pk=copilot_id) 201 except Copilot.DoesNotExist: 202 return JsonResponse({"status": "error", "message": "Copilot does not exist"}, status=404) 203 204 # delete the copilot from the user's subscriptions 205 request.user.profile.unsubscribe_copilot(copilot_id) 206 logger.debug("subsubscript copilot %s from user %s, saving...", copilot_id, request.user.username) 207 208 # Return a JSON response with status code 204 209 return JsonResponse({}, status=204) 210 211 212 def fastlap(request, template_name="fastlap.html", fastlap_id="", **kwargs): 213 driver_name = "" 214 if request.user.is_authenticated: 215 user_name = request.user.first_name 216 driver = Driver.objects.filter(name=user_name).first() 217 if driver: 218 driver_name = driver.name 219 context = dict() 220 dash_context = request.session.get("django_plotly_dash", dict()) 221 dash_context["driver_name"] = driver_name 222 request.session["django_plotly_dash"] = dash_context 223 return render(request, template_name=template_name, context=context) 224 225 226 def session(request, template_name="session.html", **kwargs): 227 session_id = kwargs.get("session_id", None) 228 lap = kwargs.get("lap", None) 229 session = get_object_or_404(Session, session_id=session_id) 230 context = {} 231 # if the session has any laps 232 if session.laps.count() > 0: 233 # get all laps with the same game_id / car_id / track_id 234 lap = session.laps.first() 235 track_id = lap.track_id 236 car_id = lap.car_id 237 context["track"] = lap.track 238 context["car"] = lap.car 239 240 compare_laps = ( 241 Lap.objects.filter(car_id=car_id, track_id=track_id) 242 .filter(valid=True) 243 .filter(time__gte=0) 244 .filter(fast_lap__isnull=False) 245 .order_by("time")[:5] 246 ) 247 else: 248 compare_laps = [] 249 250 game = session.game 251 if game.name in ["Richard Burns Rally"]: 252 map_data = True 253 else: 254 map_data = False 255 256 context["session"] = session 257 context["lap_number"] = lap 258 context["compare_laps"] = compare_laps 259 context["map_data"] = map_data 260 261 return render(request, template_name=template_name, context=context) 262 263 264 def sessions(request, template_name="sessions.html", **kwargs): 265 game_id = kwargs.get("game_id", None) 266 car_id = kwargs.get("car_id", None) 267 track_id = kwargs.get("track_id", None) 268 269 context = {} 270 271 sessions = [] 272 filter = {} 273 if game_id: 274 filter["game_id"] = game_id 275 context["game"] = Game.objects.get(pk=game_id) 276 if car_id: 277 filter["laps__car_id"] = car_id 278 context["car"] = Car.objects.get(pk=car_id) 279 if track_id: 280 filter["laps__track_id"] = track_id 281 context["track"] = Track.objects.get(pk=track_id) 282 283 # Calculate the start date based on the range 284 start_date = datetime.now() - timedelta(days=14) 285 286 # Filter laps based on the end time within the range 287 filter["end__gte"] = start_date 288 289 # get the sessions that are 290 # eager load laps and game 291 sessions = Session.objects.filter(**filter).order_by("-created") 292 sessions = sessions.prefetch_related("game", "laps__car", "laps__track") 293 sessions = sessions.distinct() 294 295 context["sessions"] = sessions 296 297 return render(request, template_name=template_name, context=context) 298 299 300 def pitcrew_view(request, template_name="pitcrew.html", driver_name="", **kwargs): 301 "Example view that inserts content into the dash context passed to the dash application" 302 303 # driver_name = request.GET.get("driver", None) 304 if driver_name.lower() == "jim": 305 raise Exception("no jim allowed") 306 307 driver = get_object_or_404(Driver, name=driver_name) 308 coach = Coach.objects.get_or_create(driver=driver)[0] 309 310 # https://github.com/GibbsConsulting/django-plotly-dash/issues/378 311 context = {"init_args": {"power-switch": {"value": coach.enabled}}} 312 313 # create some context to send over to Dash: 314 dash_context = request.session.get("django_plotly_dash", dict()) 315 dash_context["driver_pk"] = driver.pk 316 dash_context["enabled"] = coach.enabled 317 request.session["django_plotly_dash"] = dash_context 318 319 return render(request, template_name=template_name, context=context) 320 321 322 def pitcrew_index(request, template_name="pitcrew_index.html", **kwargs): 323 drivers = Driver.objects.order_by("name") 324 drivers_total = drivers.count() 325 context = { 326 "drivers": drivers, 327 "drivers_total": drivers_total, 328 } 329 330 return render(request, template_name=template_name, context=context)