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

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

08 Apr 2025 06:55PM UTC coverage: 96.987% (+0.3%) from 96.678%
393

push

travis-pro

web-flow
Merge pull request #217 from gcivil-nyu-org/marvin_dev

Marvin dev

35 of 35 new or added lines in 5 files covered. (100.0%)

1 existing line in 1 file now uncovered.

869 of 896 relevant lines covered (96.99%)

0.97 hits per line

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

87.29
/parks/views.py
1
from django.shortcuts import render, get_object_or_404, redirect
1✔
2
from django.http import (  # noqa: F401  # Ignore "imported but unused"
1✔
3
    HttpResponseForbidden,
4
    HttpResponse,
5
)
6
from django.db.models import OuterRef, Subquery, CharField, Q, Avg, Count
1✔
7
from django.db.models.functions import Cast
1✔
8
from .models import DogRunNew, Review, ParkImage, ReviewReport, ImageReport
1✔
9
from django.forms.models import model_to_dict
1✔
10
from django.contrib.auth import login
1✔
11
from django.contrib.auth.decorators import login_required
1✔
12

13
import folium
1✔
14
from folium.plugins import MarkerCluster
1✔
15

16
from .utilities import folium_cluster_styling
1✔
17
from .forms import RegisterForm
1✔
18

19
import json
1✔
20

21

22
def register_view(request):
1✔
23
    if request.method == "POST":
1✔
24
        form = RegisterForm(request.POST)
1✔
25
        if form.is_valid():
1✔
26
            user = form.save()  # Save user
1✔
27
            login(request, user)  # Log in the user immediately
1✔
28
            request.session.save()  # Ensure session is updated
1✔
29
            return redirect("home")  # Redirect to homepage
1✔
30
    else:
31
        form = RegisterForm()
1✔
32

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

35

36
def park_list(request):
1✔
37
    query = request.GET.get("query", "")
1✔
38
    parks = DogRunNew.objects.all()  # Fetch all dog runs from the database
1✔
39

40
    if query:
1✔
41
        parks = parks.filter(
×
42
            Q(name__icontains=query)
43
            | Q(google_name__icontains=query)
44
            | Q(zip_code__icontains=query)
45
        )
46

47
    return render(request, "parks/park_list.html", {"parks": parks, "query": query})
1✔
48

49

50
def home_view(request):
1✔
UNCOV
51
    return render(request, "parks/home.html")
×
52

53

54
def map(request):
1✔
55

56
    NYC_LAT_AND_LONG = (40.730610, -73.935242)
1✔
57
    # Create map centered on NYC
58
    m = folium.Map(location=NYC_LAT_AND_LONG, zoom_start=11)
1✔
59

60
    icon_create_function = folium_cluster_styling("rgb(0, 128, 0)")
1✔
61

62
    marker_cluster = MarkerCluster(icon_create_function=icon_create_function).add_to(m)
1✔
63

64
    # Fetch all dog runs from the database
65
    parks = DogRunNew.objects.all()
1✔
66

67
    # Mark every park on the map
68
    for park in parks:
1✔
69
        park_name = park.name
×
70

71
        folium.Marker(
×
72
            location=(park.latitude, park.longitude),
73
            icon=folium.Icon(icon="dog", prefix="fa", color="green"),
74
            popup=folium.Popup(park_name, max_width=200),
75
        ).add_to(marker_cluster)
76

77
    # represent map as html
78
    context = {"map": m._repr_html_()}
1✔
79
    return render(request, "parks/map.html", context)
1✔
80

81

82
def park_and_map(request):
1✔
83
    # Get filter values from GET request
84
    query = request.GET.get("query", "").strip()
1✔
85
    filter_value = request.GET.get("filter", "").strip()
1✔
86
    accessible_value = request.GET.get("accessible", "").strip()
1✔
87
    borough_value = request.GET.get("borough", "").strip().upper()
1✔
88

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

91
    # Fetch all dog runs from the database
92
    parks = (
1✔
93
        DogRunNew.objects.all()
94
        .order_by("id")
95
        .prefetch_related("images")
96
        .annotate(
97
            thumbnail_url=Cast(Subquery(thumbnail), output_field=CharField()),
98
            average_rating=Avg("reviews__rating"),
99
            review_count=Count("reviews"),
100
        )
101
    )
102

103
    # Search by ZIP, name, or Google name
104
    if query:
1✔
105
        parks = parks.filter(
×
106
            Q(name__icontains=query)
107
            | Q(google_name__icontains=query)
108
            | Q(zip_code__icontains=query)
109
        )
110

111
    # Filter by park type (e.g., "Off-Leash")
112
    if filter_value:
1✔
113
        parks = parks.filter(dogruns_type__iexact=filter_value)
×
114

115
    # Filter by accessibility only if explicitly set to "True" or "False"
116
    if accessible_value == "True":
1✔
117
        parks = parks.filter(accessible=True)
×
118
    elif accessible_value == "False":
1✔
119
        parks = parks.filter(accessible=False)
×
120

121
    if borough_value:
1✔
122
        parks = parks.filter(borough=borough_value)
1✔
123

124
    # Convert parks to JSON (for JS use)
125
    parks_json = json.dumps(list(parks.values()))
1✔
126

127
    # Render the template
128
    return render(
1✔
129
        request,
130
        "parks/combined_view.html",
131
        {
132
            "parks": parks,
133
            "parks_json": parks_json,
134
            "query": query,
135
            "selected_type": filter_value,
136
            "selected_accessible": accessible_value,
137
            "selected_borough": borough_value,
138
        },
139
    )
140

141

142
def park_detail(request, slug, id):
1✔
143
    park = get_object_or_404(DogRunNew, id=id)
1✔
144
    images = ParkImage.objects.filter(park=park)
1✔
145
    reviews = park.reviews.all()
1✔
146
    average_rating = reviews.aggregate(Avg("rating"))["rating__avg"]
1✔
147

148
    if request.user.is_authenticated and request.method == "POST":
1✔
149
        form_type = request.POST.get("form_type")
1✔
150

151
        if form_type == "upload_image" and request.FILES.getlist("images"):
1✔
152
            for image in request.FILES.getlist("images"):
×
153
                ParkImage.objects.create(park=park, image=image, user=request.user)
×
154
            return redirect("park_detail", slug=park.slug, id=park.id)
×
155

156
        elif form_type == "submit_review":
1✔
157
            review_text = request.POST.get("text", "").strip()
1✔
158
            rating_value = request.POST.get("rating", "").strip()
1✔
159

160
            if not rating_value.isdigit():
1✔
161
                return render(
×
162
                    request,
163
                    "parks/park_detail.html",
164
                    {
165
                        "park": park,
166
                        "images": images,
167
                        "reviews": reviews,
168
                        "error_message": "Please select a valid rating!",
169
                        "average_rating": average_rating,
170
                    },
171
                )
172

173
            rating = int(rating_value)
1✔
174
            if rating < 1 or rating > 5:
1✔
175
                return render(
×
176
                    request,
177
                    "parks/park_detail.html",
178
                    {
179
                        "park": park,
180
                        "images": images,
181
                        "reviews": reviews,
182
                        "error_message": "Rating must be between 1 and 5 stars!",
183
                        "average_rating": average_rating,
184
                    },
185
                )
186

187
            Review.objects.create(
1✔
188
                park=park, text=review_text, rating=rating, user=request.user
189
            )
190
            return redirect("park_detail", slug=park.slug, id=park.id)
1✔
191
        # report reviews
192
        elif form_type == "report_review":
1✔
193
            if request.user.is_authenticated:
1✔
194
                review_id = request.POST.get("review_id")
1✔
195
                reason = request.POST.get("reason", "").strip()
1✔
196
            if review_id and reason:
1✔
197
                review = get_object_or_404(Review, id=review_id)
1✔
198
                ReviewReport.objects.create(
1✔
199
                    review=review, reported_by=request.user, reason=reason
200
                )
201
                return redirect("park_detail", slug=park.slug, id=park.id)
1✔
202

203
    park_json = json.dumps(model_to_dict(park))
1✔
204

205
    return render(
1✔
206
        request,
207
        "parks/park_detail.html",
208
        {
209
            "park": park,
210
            "images": images,
211
            "reviews": reviews,
212
            "park_json": park_json,
213
            "average_rating": average_rating,
214
        },
215
    )
216

217

218
@login_required
1✔
219
def delete_review(request, review_id):
1✔
220
    review = get_object_or_404(Review, id=review_id)
1✔
221
    if request.user == review.user:
1✔
222
        review.delete()
1✔
223
        return redirect("park_detail", slug=review.park.slug, id=review.park.id)
1✔
224
    else:
225
        return HttpResponseForbidden("You are not allowed to delete this review.")
×
226

227

228
@login_required
1✔
229
def delete_image(request, image_id):
1✔
230
    image = get_object_or_404(ParkImage, id=image_id)
1✔
231
    if image.user == request.user:
1✔
232
        park_id = image.park.id
1✔
233
        image.delete()
1✔
234
        return redirect("park_detail", slug=image.park.slug, id=park_id)
1✔
235
    return HttpResponseForbidden("You are not allowed to delete this image.")
×
236

237

238
def contact_view(request):
1✔
239
    return render(request, "parks/contact.html")
1✔
240

241

242
@login_required
1✔
243
def report_image(request, image_id):
1✔
244
    image = get_object_or_404(ParkImage, id=image_id)
1✔
245
    if request.method == "POST":
1✔
246
        reason = request.POST.get("reason", "").strip()
1✔
247
        if reason:
1✔
248
            ImageReport.objects.create(user=request.user, image=image, reason=reason)
1✔
249
            return redirect("park_detail", slug=image.park.slug, id=image.park.id)
1✔
250
    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