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

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

15 Apr 2025 04:44PM UTC coverage: 94.434%. Remained the same
451

push

travis-pro

mr2447
Revert to commit 70a810d

2 of 3 new or added lines in 2 files covered. (66.67%)

24 existing lines in 2 files now uncovered.

1018 of 1078 relevant lines covered (94.43%)

0.94 hits per line

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

73.45
/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
)
9
from django.db.models import OuterRef, Subquery, CharField, Q, Avg, Count
1✔
10
from django.db.models.functions import Cast
1✔
11
from .models import (
1✔
12
    DogRunNew,
13
    Review,
14
    ParkImage,
15
    ReviewReport,
16
    ImageReport,
17
    ParkPresence,
18
)
19
from django.forms.models import model_to_dict
1✔
20
from django.contrib.auth import login
1✔
21
from django.contrib.auth.decorators import login_required
1✔
22
from .forms import RegisterForm
1✔
23

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

32

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

40
    # Either update or create a new check-in record
41
    presence, created = ParkPresence.objects.update_or_create(
×
42
        user=request.user, park=park, defaults={"status": "current", "time": None}
43
    )
44

45
    return JsonResponse({"status": "checked in"})
×
46

47

48
@login_required
1✔
49
@require_POST
1✔
50
def bethere_view(request):
1✔
51
    try:
×
52
        data = json.loads(request.body)
×
53
        park_id = data.get("park_id")
×
54
        time_str = data.get("time")  # e.g. "17:30"
×
55

56
        if not park_id or not time_str:
×
57
            return JsonResponse({"error": "Missing park_id or time"}, status=400)
×
58

59
        # Parse and validate time
60
        try:
×
61
            arrival_time = datetime.datetime.strptime(time_str, "%H:%M").time()
×
62
        except ValueError:
×
63
            return JsonResponse({"error": "Invalid time format"}, status=400)
×
64

65
        current_datetime = now()
×
66
        today = current_datetime.date()
×
67
        arrival_datetime = timezone.make_aware(
×
68
            datetime.datetime.combine(today, arrival_time)
69
        )
70

UNCOV
71
        if arrival_datetime < current_datetime:
×
72
            return JsonResponse({"error": "Cannot select a past time"}, status=400)
×
73

UNCOV
74
        park = get_object_or_404(DogRunNew, id=park_id)
×
75

76
        # ✅ Save the full datetime, not just the time
UNCOV
77
        presence, created = ParkPresence.objects.update_or_create(
×
78
            user=request.user,
79
            park=park,
80
            defaults={"status": "On their way", "time": arrival_datetime},
81
        )
82

UNCOV
83
        formatted_time = arrival_datetime.strftime("%I:%M %p")
×
84
        return JsonResponse({"status": "on their way", "time": formatted_time})
×
85

UNCOV
86
    except Exception as e:
×
87
        import traceback
×
88

UNCOV
89
        print(traceback.format_exc())
×
UNCOV
90
        return JsonResponse({"error": str(e)}, status=500)
×
91

92

93
def register_view(request):
1✔
94
    if request.method == "POST":
1✔
95
        form = RegisterForm(request.POST)
1✔
96
        if form.is_valid():
1✔
97
            # Save but don't commit yet
98
            user = form.save(commit=False)
1✔
99
            # If they chose Admin, mark them as staff
100
            if form.cleaned_data["role"] == "admin":
1✔
101
                user.is_staff = True
1✔
102
            user.save()
1✔
103

104
            # Log the user in immediately
105
            login(request, user)
1✔
106
            request.session.save()
1✔
107
            return redirect("home")
1✔
108
    else:
109
        form = RegisterForm()
1✔
110

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

113

114
def home_view(request):
1✔
NEW
115
    return render(request, "parks/home.html")
×
116

117

118
@never_cache
1✔
119
def park_and_map(request):
1✔
120
    # Get filter values from GET request
121
    query = request.GET.get("query", "").strip()
1✔
122
    filter_value = request.GET.get("filter", "").strip()
1✔
123
    accessible_value = request.GET.get("accessible", "").strip()
1✔
124
    borough_value = request.GET.get("borough", "").strip().upper()
1✔
125

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

128
    # Fetch all dog runs from the database
129
    parks = (
1✔
130
        DogRunNew.objects.all()
131
        .order_by("id")
132
        .prefetch_related("images")
133
        .annotate(
134
            thumbnail_url=Cast(Subquery(thumbnail), output_field=CharField()),
135
            average_rating=Avg("reviews__rating"),
136
            review_count=Count("reviews"),
137
        )
138
    )
139

140
    # Search by ZIP, name, or Google name
141
    if query:
1✔
UNCOV
142
        parks = parks.filter(
×
143
            Q(name__icontains=query)
144
            | Q(google_name__icontains=query)
145
            | Q(zip_code__icontains=query)
146
        )
147

148
    # Filter by park type (e.g., "Off-Leash")
149
    if filter_value:
1✔
UNCOV
150
        parks = parks.filter(dogruns_type__iexact=filter_value)
×
151

152
    # Filter by accessibility only if explicitly set to "True" or "False"
153
    if accessible_value == "True":
1✔
UNCOV
154
        parks = parks.filter(accessible=True)
×
155
    elif accessible_value == "False":
1✔
156
        parks = parks.filter(accessible=False)
×
157

158
    if borough_value:
1✔
159
        parks = parks.filter(borough=borough_value)
1✔
160

161
    # Convert parks to JSON (for JS use)
162
    parks_json = json.dumps(list(parks.values()))
1✔
163

164
    # Render the template
165
    return render(
1✔
166
        request,
167
        "parks/combined_view.html",
168
        {
169
            "parks": parks,
170
            "parks_json": parks_json,
171
            "query": query,
172
            "selected_type": filter_value,
173
            "selected_accessible": accessible_value,
174
            "selected_borough": borough_value,
175
        },
176
    )
177

178

179
@never_cache
1✔
180
def park_detail(request, slug, id):
1✔
181
    park = get_object_or_404(DogRunNew, id=id)
1✔
182
    images = ParkImage.objects.filter(park=park)
1✔
183
    reviews = park.reviews.all()
1✔
184
    average_rating = reviews.aggregate(Avg("rating"))["rating__avg"]
1✔
185

186
    # 🧹 Clean up expired "On their way" entries
187
    now = localtime()
1✔
188

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

191
    # Updated counts after cleanup
192
    current_count = ParkPresence.objects.filter(park=park, status="current").count()
1✔
193
    on_the_way_count = ParkPresence.objects.filter(
1✔
194
        park=park, status="On their way", time__isnull=False, time__gte=now
195
    ).count()
196

197
    if request.user.is_authenticated and request.method == "POST":
1✔
198
        form_type = request.POST.get("form_type")
1✔
199

200
        if form_type == "submit_review":
1✔
201
            review_text = request.POST.get("text", "").strip()
1✔
202
            rating_value = request.POST.get("rating", "").strip()
1✔
203

204
            if not rating_value.isdigit():
1✔
UNCOV
205
                messages.error(request, "Please select a rating before submitting.")
×
UNCOV
206
                return redirect("park_detail", slug=park.slug, id=park.id)
×
207

208
            rating = int(rating_value)
1✔
209
            if rating < 1 or rating > 5:
1✔
UNCOV
210
                return render(
×
211
                    request,
212
                    "parks/park_detail.html",
213
                    {
214
                        "park": park,
215
                        "images": images,
216
                        "reviews": reviews,
217
                        "error_message": "Rating must be between 1 and 5 stars!",
218
                        "average_rating": average_rating,
219
                        "current_count": current_count,
220
                        "on_the_way_count": on_the_way_count,
221
                    },
222
                )
223

224
            review = Review.objects.create(
1✔
225
                park=park,
226
                text=review_text if review_text else "",
227
                rating=rating,
228
                user=request.user,
229
            )
230
            images = request.FILES.getlist("images")
1✔
231
            ALLOWED_IMAGE_TYPES = ["image/jpeg", "image/png", "image/webp"]
1✔
232

233
            invalid_type = any(
1✔
234
                img.content_type not in ALLOWED_IMAGE_TYPES for img in images
235
            )
236

237
            if invalid_type:
1✔
UNCOV
238
                messages.error(request, "Only JPEG, PNG, or WebP images are allowed.")
×
UNCOV
239
                review.delete()
×
240
                return redirect("park_detail", slug=park.slug, id=park.id)
×
241

242
            MAX_IMAGE_SIZE = 5 * 1024 * 1024  # 5 MB
1✔
243

244
            invalid_images = [img for img in images if img.size > MAX_IMAGE_SIZE]
1✔
245

246
            if invalid_images:
1✔
UNCOV
247
                messages.error(request, "Each image must be under 5 MB.")
×
UNCOV
248
                review.delete()
×
249
                return redirect("park_detail", slug=park.slug, id=park.id)
×
250

251
            # Save valid images
252
            for image in images:
1✔
253
                ParkImage.objects.create(
1✔
254
                    park=park, image=image, review=review, user=request.user
255
                )
256

257
            messages.success(request, "Your review was submitted successfully!")
1✔
258
            return redirect("park_detail", slug=park.slug, id=park.id)
1✔
259

260
        elif form_type == "check_in":
1✔
261
            ParkPresence.objects.create(
1✔
262
                user=request.user,
263
                park=park,
264
                status="current",
265
                time=now,
266
            )
267

268
        elif form_type == "be_there_at":
1✔
269
            time_str = request.POST.get("time")
1✔
270
            try:
1✔
271
                arrival_time = timezone.datetime.combine(
1✔
272
                    now.date(), timezone.datetime.strptime(time_str, "%H:%M").time()
273
                )
274
                arrival_time = timezone.make_aware(
1✔
275
                    arrival_time
276
                )  # Make it timezone aware
UNCOV
277
            except (ValueError, TypeError):
×
UNCOV
278
                arrival_time = None
×
279

280
            if arrival_time and arrival_time >= now:
1✔
281
                ParkPresence.objects.create(
1✔
282
                    user=request.user,
283
                    park=park,
284
                    status="on_the_way",
285
                    time=arrival_time,
286
                )
287

288
        elif form_type == "report_review":
1✔
289
            if request.user.is_authenticated:
1✔
290
                review_id = request.POST.get("review_id")
1✔
291
                reason = request.POST.get("reason", "").strip()
1✔
292
                if review_id and reason:
1✔
293
                    review = get_object_or_404(Review, id=review_id)
1✔
294
                    ReviewReport.objects.create(
1✔
295
                        review=review, reported_by=request.user, reason=reason
296
                    )
297
                    messages.success(
1✔
298
                        request, "Your review report was submitted successfully."
299
                    )
300
                    return redirect("park_detail", slug=park.slug, id=park.id)
1✔
301

302
    park_json = json.dumps(model_to_dict(park))
1✔
303

304
    return render(
1✔
305
        request,
306
        "parks/park_detail.html",
307
        {
308
            "park": park,
309
            "images": images,
310
            "reviews": reviews,
311
            "park_json": park_json,
312
            "average_rating": average_rating,
313
            "current_count": current_count,
314
            "on_the_way_count": on_the_way_count,
315
        },
316
    )
317

318

319
@login_required
1✔
320
def delete_review(request, review_id):
1✔
321
    review = get_object_or_404(Review, id=review_id)
1✔
322
    if request.user == review.user:
1✔
323
        review.delete()
1✔
324
        messages.success(request, "You have successfully deleted the review!")
1✔
325
        return redirect("park_detail", slug=review.park.slug, id=review.park.id)
1✔
326
    else:
UNCOV
327
        return HttpResponseForbidden("You are not allowed to delete this review.")
×
328

329

330
@login_required
1✔
331
def delete_image(request, image_id):
1✔
332
    image = get_object_or_404(ParkImage, id=image_id)
1✔
333
    if image.user == request.user:
1✔
334
        park_id = image.park.id
1✔
335
        image.delete()
1✔
336
        messages.success(request, "You have successfully deleted the image!")
1✔
337
        return redirect("park_detail", slug=image.park.slug, id=park_id)
1✔
UNCOV
338
    return HttpResponseForbidden("You are not allowed to delete this image.")
×
339

340

341
def contact_view(request):
1✔
UNCOV
342
    return render(request, "parks/contact.html")
×
343

344

345
@login_required
1✔
346
def report_image(request, image_id):
1✔
347
    image = get_object_or_404(ParkImage, id=image_id)
1✔
348
    if request.method == "POST":
1✔
349
        reason = request.POST.get("reason", "").strip()
1✔
350
        if reason:
1✔
351
            ImageReport.objects.create(user=request.user, image=image, reason=reason)
1✔
352
            messages.success(request, "You have successfully reported the image!")
1✔
353
            return redirect("park_detail", slug=image.park.slug, id=image.park.id)
1✔
354
    return redirect("park_detail", slug=image.park.slug, id=image.park.id)
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