• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

gcivil-nyu-org / team4-wed-spring25 / 468

16 Apr 2025 06:35AM UTC coverage: 94.995% (+0.5%) from 94.475%
468

push

travis-pro

mr2447
Fixed blank space

1025 of 1079 relevant lines covered (95.0%)

0.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.69
/parks/views.py
1
from django.shortcuts import render
1✔
2
from django.shortcuts import get_object_or_404
1✔
3
from django.shortcuts import redirect
1✔
4
from django.http import (  # noqa: F401  # Ignore "imported but unused"
1✔
5
    HttpResponseForbidden,
6
    HttpResponse,
7
    JsonResponse,
8
    HttpResponsePermanentRedirect,
9
)
10
from django.urls import reverse  # noqa: F401  # Ignore "imported but unused"
1✔
11
from django.db.models import OuterRef, Subquery, CharField, Q, Avg, Count
1✔
12
from django.db.models.functions import Cast
1✔
13
from .models import (
1✔
14
    DogRunNew,
15
    Review,
16
    ParkImage,
17
    ReviewReport,
18
    ImageReport,
19
    ParkPresence,
20
)
21
from django.forms.models import model_to_dict
1✔
22
from django.contrib.auth import login
1✔
23
from django.contrib.auth.decorators import login_required
1✔
24
from .forms import RegisterForm
1✔
25

26
import json
1✔
27
import datetime
1✔
28
from django.contrib import messages
1✔
29
from django.utils import timezone
1✔
30
from django.utils.timezone import now, localtime
1✔
31
from django.views.decorators.http import require_POST
1✔
32
from django.views.decorators.cache import never_cache
1✔
33
from datetime import timedelta
1✔
34

35

36
@login_required
1✔
37
@require_POST
1✔
38
def checkin_view(request):
1✔
39
    data = json.loads(request.body)
×
40
    park_id = data.get("park_id")
×
41
    park = get_object_or_404(DogRunNew, id=park_id)
×
42

43
    # Remove existing 'current' check-ins from other parks
44
    ParkPresence.objects.filter(user=request.user, status="current").exclude(
×
45
        park=park
46
    ).delete()
47

48
    # Check in to this park
49
    presence, created = ParkPresence.objects.update_or_create(
×
50
        user=request.user,
51
        park=park,
52
        defaults={"status": "current", "time": timezone.now()},
53
    )
54

55
    return JsonResponse({"status": "checked in", "new": created})
×
56

57

58
@login_required
1✔
59
@require_POST
1✔
60
def bethere_view(request):
1✔
61
    try:
×
62
        data = json.loads(request.body)
×
63
        park_id = data.get("park_id")
×
64
        time_str = data.get("time")  # e.g. "17:30"
×
65

66
        if not park_id or not time_str:
×
67
            return JsonResponse({"error": "Missing park_id or time"}, status=400)
×
68

69
        # Parse and validate time
70
        try:
×
71
            arrival_time = datetime.datetime.strptime(time_str, "%H:%M").time()
×
72
        except ValueError:
×
73
            return JsonResponse({"error": "Invalid time format"}, status=400)
×
74

75
        current_datetime = now()
×
76
        today = current_datetime.date()
×
77
        arrival_datetime = timezone.make_aware(
×
78
            datetime.datetime.combine(today, arrival_time)
79
        )
80

81
        if arrival_datetime < current_datetime:
×
82
            return JsonResponse({"error": "Cannot select a past time"}, status=400)
×
83

84
        park = get_object_or_404(DogRunNew, id=park_id)
×
85

86
        # ✅ Save the full datetime, not just the time
87
        presence, created = ParkPresence.objects.update_or_create(
×
88
            user=request.user,
89
            park=park,
90
            defaults={"status": "On their way", "time": arrival_datetime},
91
        )
92

93
        formatted_time = arrival_datetime.strftime("%I:%M %p")
×
94
        return JsonResponse({"status": "on their way", "time": formatted_time})
×
95

96
    except Exception as e:
97
        import traceback
98

99
        print(traceback.format_exc())
100
        return JsonResponse({"error": str(e)}, status=500)
101

102

103
def expire_old_checkins():
1✔
104
    expiration_time = timezone.now() - timedelta(hours=1)
1✔
105
    ParkPresence.objects.filter(status="current", time__lt=expiration_time).delete()
1✔
106

107

108
def register_view(request):
1✔
109
    if request.method == "POST":
1✔
110
        form = RegisterForm(request.POST)
1✔
111
        if form.is_valid():
1✔
112
            # Save but don't commit yet
113
            user = form.save(commit=False)
1✔
114
            # If they chose Admin, mark them as staff
115
            if form.cleaned_data["role"] == "admin":
1✔
116
                user.is_staff = True
1✔
117
            user.save()
1✔
118

119
            # Log the user in immediately
120
            login(request, user)
1✔
121
            request.session.save()
1✔
122
            return redirect("home")
1✔
123
    else:
124
        form = RegisterForm()
1✔
125

126
    return render(request, "parks/register.html", {"form": form})
1✔
127

128

129
def home_view(request):
1✔
130
    return render(request, "parks/home.html")
×
131

132

133
@never_cache
1✔
134
def park_and_map(request):
1✔
135
    # Get filter values from GET request
136
    query = request.GET.get("query", "").strip()
1✔
137
    filter_value = request.GET.get("filter", "").strip()
1✔
138
    accessible_value = request.GET.get("accessible", "").strip()
1✔
139
    borough_value = request.GET.get("borough", "").strip().upper()
1✔
140

141
    thumbnail = ParkImage.objects.filter(park_id=OuterRef("pk")).values("image")[:1]
1✔
142

143
    # Fetch all dog runs from the database
144
    parks = (
1✔
145
        DogRunNew.objects.all()
146
        .order_by("id")
147
        .prefetch_related("images")
148
        .annotate(
149
            thumbnail_url=Cast(Subquery(thumbnail), output_field=CharField()),
150
            average_rating=Avg("reviews__rating"),
151
            review_count=Count("reviews"),
152
        )
153
    )
154

155
    # Search by ZIP, name, or Google name
156
    if query:
1✔
157
        parks = parks.filter(
×
158
            Q(name__icontains=query)
159
            | Q(google_name__icontains=query)
160
            | Q(zip_code__icontains=query)
161
        )
162

163
    # Filter by park type (e.g., "Off-Leash")
164
    if filter_value:
1✔
165
        parks = parks.filter(dogruns_type__iexact=filter_value)
×
166

167
    # Filter by accessibility only if explicitly set to "True" or "False"
168
    if accessible_value == "True":
1✔
169
        parks = parks.filter(accessible=True)
×
170
    elif accessible_value == "False":
1✔
171
        parks = parks.filter(accessible=False)
×
172

173
    if borough_value:
1✔
174
        parks = parks.filter(borough=borough_value)
1✔
175

176
    # Convert parks to JSON (for JS use)
177
    # parks_json = json.dumps(list(parks.values()))
178

179
    parks_json = json.dumps(
1✔
180
        [
181
            {
182
                **model_to_dict(park),
183
                "url": park.detail_page_url(),
184
            }
185
            for park in parks
186
        ]
187
    )
188

189
    # Render the template
190
    return render(
1✔
191
        request,
192
        "parks/combined_view.html",
193
        {
194
            "parks": parks,
195
            "parks_json": parks_json,
196
            "query": query,
197
            "selected_type": filter_value,
198
            "selected_accessible": accessible_value,
199
            "selected_borough": borough_value,
200
        },
201
    )
202

203

204
@never_cache
1✔
205
def park_detail(request, slug, id):
1✔
206
    park = get_object_or_404(DogRunNew, id=id)
1✔
207

208
    # Check slug, if incorrect, redirect to correct one
209
    if slug != park.slug:
1✔
210
        return HttpResponsePermanentRedirect(park.detail_page_url())
1✔
211

212
    images = ParkImage.objects.filter(park=park)
1✔
213
    reviews = park.reviews.all()
1✔
214
    average_rating = reviews.aggregate(Avg("rating"))["rating__avg"]
1✔
215

216
    # Clean up expired "On their way" entries
217
    now = localtime()
1✔
218
    # Call the function to expire old check-ins
219
    expire_old_checkins()
1✔
220

221
    ParkPresence.objects.filter(park=park, status="On their way", time__lt=now).delete()
1✔
222

223
    # Updated counts after cleanup
224
    current_count = ParkPresence.objects.filter(park=park, status="current").count()
1✔
225
    on_the_way_count = ParkPresence.objects.filter(
1✔
226
        park=park, status="On their way", time__isnull=False, time__gte=now
227
    ).count()
228

229
    if request.user.is_authenticated and request.method == "POST":
1✔
230
        form_type = request.POST.get("form_type")
1✔
231

232
        if form_type == "submit_review":
1✔
233
            review_text = request.POST.get("text", "").strip()
1✔
234
            rating_value = request.POST.get("rating", "").strip()
1✔
235

236
            if not rating_value.isdigit():
1✔
237
                messages.error(request, "Please select a rating before submitting.")
×
238
                return redirect(park.detail_page_url())
×
239

240
            rating = int(rating_value)
1✔
241
            if rating < 1 or rating > 5:
1✔
242
                return render(
×
243
                    request,
244
                    "parks/park_detail.html",
245
                    {
246
                        "park": park,
247
                        "images": images,
248
                        "reviews": reviews,
249
                        "error_message": "Rating must be between 1 and 5 stars!",
250
                        "average_rating": average_rating,
251
                        "current_count": current_count,
252
                        "on_the_way_count": on_the_way_count,
253
                    },
254
                )
255

256
            review = Review.objects.create(
1✔
257
                park=park,
258
                text=review_text if review_text else "",
259
                rating=rating,
260
                user=request.user,
261
            )
262
            images = request.FILES.getlist("images")
1✔
263
            ALLOWED_IMAGE_TYPES = ["image/jpeg", "image/png", "image/webp"]
1✔
264

265
            invalid_type = any(
1✔
266
                img.content_type not in ALLOWED_IMAGE_TYPES for img in images
267
            )
268

269
            if invalid_type:
1✔
270
                messages.error(request, "Only JPEG, PNG, or WebP images are allowed.")
×
271
                review.delete()
×
272
                return redirect("park_detail", slug=park.slug, id=park.id)
×
273

274
            MAX_IMAGE_SIZE = 5 * 1024 * 1024  # 5 MB
1✔
275

276
            invalid_images = [img for img in images if img.size > MAX_IMAGE_SIZE]
1✔
277

278
            if invalid_images:
1✔
279
                messages.error(request, "Each image must be under 5 MB.")
×
280
                review.delete()
×
281
                return redirect("park_detail", slug=park.slug, id=park.id)
×
282

283
            # Save valid images
284
            for image in images:
1✔
285
                ParkImage.objects.create(
1✔
286
                    park=park, image=image, review=review, user=request.user
287
                )
288

289
            messages.success(request, "Your review was submitted successfully!")
1✔
290

291
        elif form_type == "check_in":
1✔
292
            ParkPresence.objects.create(
1✔
293
                user=request.user,
294
                park=park,
295
                status="current",
296
                time=now,
297
            )
298

299
        elif form_type == "be_there_at":
1✔
300
            time_str = request.POST.get("time")
1✔
301
            try:
1✔
302
                arrival_time = timezone.datetime.combine(
1✔
303
                    now.date(), timezone.datetime.strptime(time_str, "%H:%M").time()
304
                )
305
                arrival_time = timezone.make_aware(
1✔
306
                    arrival_time
307
                )  # Make it timezone aware
308
            except (ValueError, TypeError):
×
309
                arrival_time = None
×
310

311
            if arrival_time and arrival_time >= now:
1✔
312
                ParkPresence.objects.create(
1✔
313
                    user=request.user,
314
                    park=park,
315
                    status="on_the_way",
316
                    time=arrival_time,
317
                )
318
        # report reviews
319
        elif form_type == "report_review":
1✔
320
            if request.user.is_authenticated:
1✔
321
                review_id = request.POST.get("review_id")
1✔
322
                reason = request.POST.get("reason", "").strip()
1✔
323

324
                if review_id and reason:
1✔
325
                    review = get_object_or_404(Review, id=review_id)
1✔
326
                    ReviewReport.objects.create(
1✔
327
                        review=review, reported_by=request.user, reason=reason
328
                    )
329
                    messages.success(
1✔
330
                        request, "Your review report was submitted successfully."
331
                    )
332
                return redirect(park.detail_page_url())
1✔
333
        return redirect(park.detail_page_url())
1✔
334
    park_json = json.dumps(model_to_dict(park))
1✔
335

336
    return render(
1✔
337
        request,
338
        "parks/park_detail.html",
339
        {
340
            "park": park,
341
            "images": images,
342
            "reviews": reviews,
343
            "park_json": park_json,
344
            "average_rating": average_rating,
345
            "current_count": current_count,
346
            "on_the_way_count": on_the_way_count,
347
        },
348
    )
349

350

351
@login_required
1✔
352
def delete_review(request, review_id):
1✔
353
    review = get_object_or_404(Review, id=review_id)
1✔
354
    if request.user == review.user:
1✔
355
        review.delete()
1✔
356
        messages.success(request, "You have successfully deleted the review!")
1✔
357
        return redirect(review.park.detail_page_url())
1✔
358
    else:
359
        return HttpResponseForbidden("You are not allowed to delete this review.")
×
360

361

362
@login_required
1✔
363
def delete_image(request, image_id):
1✔
364
    image = get_object_or_404(ParkImage, id=image_id)
1✔
365
    if image.user == request.user:
1✔
366
        image.delete()
1✔
367
        messages.success(request, "You have successfully deleted the image!")
1✔
368
        return redirect(image.park.detail_page_url())
1✔
369
    return HttpResponseForbidden("You are not allowed to delete this image.")
×
370

371

372
def contact_view(request):
1✔
373
    return render(request, "parks/contact.html")
×
374

375

376
@login_required
1✔
377
def report_image(request, image_id):
1✔
378
    image = get_object_or_404(ParkImage, id=image_id)
1✔
379
    if request.method == "POST":
1✔
380
        reason = request.POST.get("reason", "").strip()
1✔
381
        if reason:
1✔
382
            ImageReport.objects.create(user=request.user, image=image, reason=reason)
1✔
383
            messages.success(request, "You have successfully reported the image!")
1✔
384
            return redirect(image.park.detail_page_url())
1✔
385
    return redirect(image.park.detail_page_url())
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc