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

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

16 Apr 2025 02:32PM UTC coverage: 97.744% (+0.04%) from 97.7%
476

push

travis-pro

web-flow
Merge pull request #250 from gcivil-nyu-org/ian_dev

Adjust code to account for soft deleted reviews and images

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

1 existing line in 1 file now uncovered.

1213 of 1241 relevant lines covered (97.74%)

0.98 hits per line

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

88.89
/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
    HttpResponsePermanentRedirect,
6
)
7
from django.urls import reverse  # noqa: F401  # Ignore "imported but unused"
1✔
8
from django.db.models import OuterRef, Subquery, CharField, Q, Avg, Count, Prefetch
1✔
9
from django.db.models.functions import Cast
1✔
10
from .models import DogRunNew, Review, ParkImage, ReviewReport, ImageReport
1✔
11
from django.forms.models import model_to_dict
1✔
12
from django.contrib.auth import login
1✔
13
from django.contrib.auth.decorators import login_required
1✔
14

15
from .forms import RegisterForm
1✔
16

17
import json
1✔
18
from django.contrib import messages
1✔
19

20

21
def register_view(request):
1✔
22
    if request.method == "POST":
1✔
23
        form = RegisterForm(request.POST)
1✔
24
        if form.is_valid():
1✔
25
            # Save but don't commit yet
26
            user = form.save(commit=False)
1✔
27
            # If they chose Admin, mark them as staff
28
            if form.cleaned_data["role"] == "admin":
1✔
29
                user.is_staff = True
1✔
30
            user.save()
1✔
31

32
            # Log the user in immediately
33
            login(request, user)
1✔
34
            request.session.save()
1✔
35
            return redirect("home")
1✔
36
    else:
37
        form = RegisterForm()
1✔
38

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

41

42
def home_view(request):
1✔
43
    return render(request, "parks/home.html")
×
44

45

46
def park_and_map(request):
1✔
47
    # Get filter values from GET request
48
    query = request.GET.get("query", "").strip()
1✔
49
    filter_value = request.GET.get("filter", "").strip()
1✔
50
    accessible_value = request.GET.get("accessible", "").strip()
1✔
51
    borough_value = request.GET.get("borough", "").strip().upper()
1✔
52

53
    thumbnail = ParkImage.objects.filter(
1✔
54
        park_id=OuterRef("pk"), is_removed=False, review__is_removed=False
55
    ).values("image")[:1]
56

57
    # Fetch all dog runs from the database
58
    parks = (
1✔
59
        DogRunNew.objects.all()
60
        .order_by("id")
61
        .prefetch_related("images")
62
        .annotate(
63
            thumbnail_url=Cast(Subquery(thumbnail), output_field=CharField()),
64
            average_rating=Avg("reviews__rating", filter=Q(reviews__is_removed=False)),
65
            review_count=Count("reviews", filter=Q(reviews__is_removed=False)),
66
        )
67
    )
68

69
    # Search by ZIP, name, or Google name
70
    if query:
1✔
71
        parks = parks.filter(
×
72
            Q(name__icontains=query)
73
            | Q(google_name__icontains=query)
74
            | Q(zip_code__icontains=query)
75
        )
76

77
    # Filter by park type (e.g., "Off-Leash")
78
    if filter_value:
1✔
79
        parks = parks.filter(dogruns_type__iexact=filter_value)
×
80

81
    # Filter by accessibility only if explicitly set to "True" or "False"
82
    if accessible_value == "True":
1✔
83
        parks = parks.filter(accessible=True)
×
84
    elif accessible_value == "False":
1✔
85
        parks = parks.filter(accessible=False)
×
86

87
    if borough_value:
1✔
88
        parks = parks.filter(borough=borough_value)
1✔
89

90
    # Convert parks to JSON (for JS use)
91
    # parks_json = json.dumps(list(parks.values()))
92

93
    parks_json = json.dumps(
1✔
94
        [
95
            {
96
                **model_to_dict(park),
97
                "thumbnail_url": park.thumbnail_url,
98
                "average_rating": park.average_rating,
99
                "review_count": park.review_count,
100
                "url": park.detail_page_url(),
101
            }
102
            for park in parks
103
        ]
104
    )
105

106
    # Render the template
107
    return render(
1✔
108
        request,
109
        "parks/combined_view.html",
110
        {
111
            "parks": parks,
112
            "parks_json": parks_json,
113
            "query": query,
114
            "selected_type": filter_value,
115
            "selected_accessible": accessible_value,
116
            "selected_borough": borough_value,
117
        },
118
    )
119

120

121
def park_detail(request, slug, id):
1✔
122
    park = get_object_or_404(DogRunNew, id=id)
1✔
123

124
    # Check slug, if incorrect, redirect to correct one
125
    if slug != park.slug:
1✔
126
        return HttpResponsePermanentRedirect(park.detail_page_url())
1✔
127

128
    images = ParkImage.objects.filter(
1✔
129
        park=park, is_removed=False, review__is_removed=False
130
    )
131

132
    # Prefetch only non-removed images for each review
133
    visible_images = Prefetch(
1✔
134
        "images",
135
        queryset=ParkImage.objects.filter(is_removed=False),
136
        to_attr="visible_images",
137
    )
138
    reviews = park.reviews.filter(is_removed=False).prefetch_related(visible_images)
1✔
139

140
    average_rating = reviews.aggregate(Avg("rating"))["rating__avg"]
1✔
141

142
    if request.user.is_authenticated and request.method == "POST":
1✔
143
        form_type = request.POST.get("form_type")
1✔
144

145
        if form_type == "submit_review":
1✔
146
            review_text = request.POST.get("text", "").strip()
1✔
147
            rating_value = request.POST.get("rating", "").strip()
1✔
148

149
            if not rating_value.isdigit():
1✔
150
                messages.error(request, "Please select a rating before submitting.")
×
151
                return redirect(park.detail_page_url())
×
152

153
            rating = int(rating_value)
1✔
154
            if rating < 1 or rating > 5:
1✔
155
                return render(
×
156
                    request,
157
                    "parks/park_detail.html",
158
                    {
159
                        "park": park,
160
                        "images": images,
161
                        "reviews": reviews,
162
                        "error_message": "Rating must be between 1 and 5 stars!",
163
                        "average_rating": average_rating,
164
                    },
165
                )
166

167
            review = Review.objects.create(
1✔
168
                park=park,
169
                text=review_text if review_text else "",
170
                rating=rating,
171
                user=request.user,
172
            )
173

174
            images = request.FILES.getlist("images")
1✔
175

176
            if images:
1✔
177
                for image in images:
×
178
                    ParkImage.objects.create(
×
179
                        park=park, image=image, review=review, user=request.user
180
                    )
181

182
            messages.success(request, "Your review was submitted successfully!")
1✔
183
            return redirect(park.detail_page_url())
1✔
184
        # report reviews
185
        elif form_type == "report_review":
1✔
186
            if request.user.is_authenticated:
1✔
187
                review_id = request.POST.get("review_id")
1✔
188
                reason = request.POST.get("reason", "").strip()
1✔
189
            if review_id and reason:
1✔
190
                review = get_object_or_404(Review, id=review_id)
1✔
191

192
                # prevent duplicate reports by the same user
193
                exists = ReviewReport.objects.filter(
1✔
194
                    review=review, reported_by=request.user
195
                ).exists()
196
                if exists:
1✔
197
                    messages.error(
1✔
198
                        request, "You have already reported this review before."
199
                    )
200
                else:
201
                    ReviewReport.objects.create(
1✔
202
                        review=review, reported_by=request.user, reason=reason
203
                    )
204
                    messages.success(
1✔
205
                        request, "Your review report was submitted successfully."
206
                    )
207
                return redirect(park.detail_page_url())
1✔
208

209
    park_json = json.dumps(model_to_dict(park))
1✔
210

211
    return render(
1✔
212
        request,
213
        "parks/park_detail.html",
214
        {
215
            "park": park,
216
            "images": images,
217
            "reviews": reviews,
218
            "park_json": park_json,
219
            "average_rating": average_rating,
220
        },
221
    )
222

223

224
@login_required
1✔
225
def delete_review(request, review_id):
1✔
226
    review = get_object_or_404(Review, id=review_id)
1✔
227
    if request.user == review.user:
1✔
228
        review.delete()
1✔
229
        messages.success(request, "You have successfully deleted the review!")
1✔
230
        return redirect(review.park.detail_page_url())
1✔
231
    else:
232
        return HttpResponseForbidden("You are not allowed to delete this review.")
×
233

234

235
@login_required
1✔
236
def delete_image(request, image_id):
1✔
237
    image = get_object_or_404(ParkImage, id=image_id)
1✔
238
    if image.user == request.user:
1✔
239
        image.delete()
1✔
240
        messages.success(request, "You have successfully deleted the image!")
1✔
241
        return redirect(image.park.detail_page_url())
1✔
242
    return HttpResponseForbidden("You are not allowed to delete this image.")
×
243

244

245
def contact_view(request):
1✔
246
    return render(request, "parks/contact.html")
1✔
247

248

249
@login_required
1✔
250
def report_image(request, image_id):
1✔
251
    image = get_object_or_404(ParkImage, id=image_id)
1✔
252

253
    if request.method == "POST":
1✔
254
        reason = request.POST.get("reason", "").strip()
1✔
255
        if reason:
1✔
256
            # Check if this user already reported this image
257
            already_reported = ImageReport.objects.filter(
1✔
258
                user=request.user, image=image
259
            ).exists()
260
            if already_reported:
1✔
261
                messages.error(request, "You have already reported this image before.")
1✔
262
            else:
263
                ImageReport.objects.create(
1✔
264
                    user=request.user, image=image, reason=reason
265
                )
266
                messages.success(request, "You have successfully reported the image!")
1✔
267
        return redirect(image.park.detail_page_url())
1✔
268

UNCOV
269
    return redirect(image.park.detail_page_url())
×
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