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

gcivil-nyu-org / team4-wed-fall25 / 29

29 Oct 2025 06:35AM UTC coverage: 60.563% (+11.8%) from 48.812%
29

push

travis-pro

web-flow
Merge pull request #56 from gcivil-nyu-org/feature/validation-failure-handling

Feature/validation failure handling

88 of 136 new or added lines in 5 files covered. (64.71%)

1 existing line in 1 file now uncovered.

344 of 568 relevant lines covered (60.56%)

0.61 hits per line

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

28.51
/note2webapp/views.py
1
from django.contrib.auth import login, logout
1✔
2
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
1✔
3
from django.contrib.auth.decorators import login_required
1✔
4
from django.contrib.auth.models import Group
1✔
5
from django.shortcuts import render, redirect, get_object_or_404
1✔
6
from django.contrib import messages
1✔
7
from django.http import JsonResponse
1✔
8
from django.utils import timezone
1✔
9
from .forms import UploadForm, VersionForm
1✔
10
from .models import ModelUpload, ModelVersion
1✔
11
from .utils import validate_model
1✔
12
import os
1✔
13

14

15
# -------------------
16
# SIGNUP
17
# -------------------
18
def signup_view(request):
1✔
19
    if request.method == "POST":
×
20
        form = UserCreationForm(request.POST)
×
21
        if form.is_valid():
×
22
            user = form.save()
×
23

24
            # By default, add every new user to "ModelUploader"
25
            group, created = Group.objects.get_or_create(name="ModelUploader")
×
26
            user.groups.add(group)
×
27

28
            login(request, user)
×
29
            return redirect("dashboard")
×
30
    else:
31
        form = UserCreationForm()
×
32
    return render(request, "note2webapp/signup.html", {"form": form})
×
33

34

35
# -------------------
36
# LOGIN
37
# -------------------
38
def login_view(request):
1✔
39
    if request.method == "POST":
×
40
        form = AuthenticationForm(request, data=request.POST)
×
41
        if form.is_valid():
×
42
            user = form.get_user()
×
43
            login(request, user)
×
44
            return redirect("dashboard")
×
45
    else:
46
        form = AuthenticationForm()
×
47
    return render(request, "note2webapp/login.html", {"form": form})
×
48

49

50
# -------------------
51
# LOGOUT
52
# -------------------
53
def logout_view(request):
1✔
54
    logout(request)
×
55
    return redirect("login")
×
56

57

58
# -------------------
59
# DASHBOARD (Role-based)
60
# -------------------
61
@login_required
1✔
62
def dashboard(request):
1✔
63
    """Redirect based on user role"""
64
    role = getattr(request.user.profile, "role", "uploader")  # fallback uploader
1✔
65
    if role == "uploader":
1✔
66
        return model_uploader_dashboard(request)  # goes to home.html
1✔
67
    elif role == "reviewer":
×
68
        return reviewer_dashboard(request)
×
69
    else:
70
        return render(request, "note2webapp/other_dashboard.html")
×
71

72

73
@login_required
1✔
74
def validation_failed(request, version_id):
1✔
75
    """View for displaying validation failure details"""
NEW
76
    version = get_object_or_404(ModelVersion, id=version_id, upload__user=request.user)
×
NEW
77
    if version.status != "FAIL":
×
NEW
78
        return redirect("model_versions", model_id=version.upload.id)
×
79

NEW
80
    return render(
×
81
        request,
82
        "note2webapp/validation_failed.html",
83
        {
84
            "version": version,
85
        },
86
    )
87

88

89
# -------------------
90
# MODEL UPLOADER DASHBOARD
91
# -------------------
92
@login_required
1✔
93
def model_uploader_dashboard(request):
1✔
94
    page = request.GET.get("page", "list")
1✔
95
    pk = request.GET.get("pk")
1✔
96

97
    # 👇 only show this uploader's models
98
    uploads = ModelUpload.objects.filter(user=request.user).order_by("-created_at")
1✔
99

100
    # Add active_versions_count to EVERY upload object (only count PASSing versions)
101
    for upload in uploads:
1✔
102
        upload.active_versions_count = upload.versions.filter(
1✔
103
            is_deleted=False, status="PASS"
104
        ).count()
105

106
    # ALWAYS add uploads to context first
107
    context = {"uploads": uploads, "page": page}
1✔
108

109
    # Create new upload
110
    if page == "create":
1✔
111
        if request.method == "POST":
×
112
            form = UploadForm(request.POST)
×
113
            if form.is_valid():
×
114
                model_name = form.cleaned_data["name"]
×
115

116
                # Check if model with same name already exists for this user
117
                if ModelUpload.objects.filter(
×
118
                    user=request.user, name=model_name
119
                ).exists():
120
                    messages.error(
×
121
                        request,
122
                        f"A model with the name '{model_name}' already exists. Please choose a different name.",
123
                    )
124
                    context["form"] = form
×
125
                    return render(request, "note2webapp/home.html", context)
×
126

127
                upload = form.save(commit=False)
×
128
                upload.user = request.user
×
129
                upload.save()
×
130
                messages.success(request, f"Model '{model_name}' created successfully!")
×
131
                return redirect(f"/dashboard/?page=detail&pk={upload.pk}")
×
132
        else:
133
            form = UploadForm()
×
134
        context["form"] = form
×
135

136
    # Upload details + versions
137
    elif page == "detail" and pk:
1✔
138
        upload = get_object_or_404(ModelUpload, pk=pk, user=request.user)
×
139
        versions = upload.versions.all().order_by("-created_at")
×
140

141
        # Add version status counts
NEW
142
        version_counts = {
×
143
            "total": versions.count(),
144
            "active": versions.filter(
145
                is_active=True, is_deleted=False, status="PASS"
146
            ).count(),
147
            "available": versions.filter(is_deleted=False, status="PASS").count(),
148
            "failed": versions.filter(is_deleted=False, status="FAIL").count(),
149
            "deleted": versions.filter(is_deleted=True).count(),
150
        }
151

NEW
152
        context.update(
×
153
            {"upload": upload, "versions": versions, "version_counts": version_counts}
154
        )
155

156
    # Add version (or retry failed version)
157
    elif page == "add_version" and pk:
1✔
158
        upload = get_object_or_404(ModelUpload, pk=pk, user=request.user)
×
NEW
159
        retry_version_id = request.GET.get("retry")
×
160

161
        if request.method == "POST":
×
162
            form = VersionForm(request.POST, request.FILES)
×
163
            if form.is_valid():
×
164
                # If retrying, update the existing version
NEW
165
                if retry_version_id:
×
NEW
166
                    try:
×
NEW
167
                        version = ModelVersion.objects.get(
×
168
                            id=retry_version_id,
169
                            upload=upload,
170
                            status="FAIL",
171
                            is_deleted=False,
172
                        )
173
                        # Update the version with new files
NEW
174
                        version.model_file = form.cleaned_data["model_file"]
×
NEW
175
                        version.predict_file = form.cleaned_data["predict_file"]
×
NEW
176
                        version.schema_file = form.cleaned_data["schema_file"]
×
NEW
177
                        version.status = "PENDING"
×
NEW
178
                        version.log = ""
×
NEW
179
                        version.save()
×
NEW
180
                        messages.info(
×
181
                            request, f"Retrying upload for version '{version.tag}'"
182
                        )
NEW
183
                    except ModelVersion.DoesNotExist:
×
NEW
184
                        messages.error(request, "Invalid version to retry")
×
NEW
185
                        return redirect(f"/dashboard/?page=detail&pk={upload.pk}")
×
186
                else:
187
                    # Create new version
NEW
188
                    version = form.save(commit=False)
×
NEW
189
                    version.upload = upload
×
NEW
190
                    version.save()
×
191

192
                # Run validation in background
193
                validate_model(version)
×
194

NEW
195
                if version.status == "FAIL":
×
NEW
196
                    return redirect("validation_failed", version_id=version.id)
×
197

NEW
198
                if not retry_version_id:
×
NEW
199
                    messages.success(
×
200
                        request, f"Version '{version.tag}' uploaded successfully!"
201
                    )
202

UNCOV
203
                return redirect(f"/dashboard/?page=detail&pk={upload.pk}")
×
204
        else:
NEW
205
            initial = {}
×
NEW
206
            if retry_version_id:
×
NEW
207
                try:
×
NEW
208
                    retry_version = ModelVersion.objects.get(
×
209
                        id=retry_version_id,
210
                        upload=upload,
211
                        status="FAIL",
212
                        is_deleted=False,
213
                    )
NEW
214
                    initial = {
×
215
                        "tag": retry_version.tag,
216
                        "category": retry_version.category,
217
                    }
NEW
218
                    context["retrying"] = True
×
NEW
219
                except ModelVersion.DoesNotExist:
×
NEW
220
                    messages.error(request, "Invalid version to retry")
×
NEW
221
                    return redirect(f"/dashboard/?page=detail&pk={upload.pk}")
×
222

NEW
223
            form = VersionForm(initial=initial)
×
224

NEW
225
        context.update(
×
226
            {
227
                "form": form,
228
                "upload": upload,
229
                "retry_version_id": retry_version_id if retry_version_id else None,
230
            }
231
        )
232

233
    return render(request, "note2webapp/home.html", context)
1✔
234

235

236
# -------------------
237
# REVIEWER DASHBOARD (multi-mode like uploader)
238
# -------------------
239
@login_required
1✔
240
def reviewer_dashboard(request):
1✔
241
    """Unified reviewer dashboard: list, detail, feedback"""
242
    page = request.GET.get("page", "list")
×
243
    pk = request.GET.get("pk")
×
244

245
    context = {"page": page}
×
246

247
    # ---List all uploaded model versions---
248
    if page == "list":
×
249
        versions = ModelVersion.objects.all().order_by("-created_at")
×
250
        context["versions"] = versions
×
251

252
    # ---View details of a specific model version---
253
    elif page == "detail" and pk:
×
254
        version = get_object_or_404(ModelVersion, pk=pk)
×
255
        context["version"] = version
×
256
        # In future, we'll show validation logs, files, predictions, etc.
257

258
    # ---Add feedback (integrated, not separate view)---
259
    elif page == "add_feedback" and pk:
×
260
        version = get_object_or_404(ModelVersion, pk=pk)
×
261
        if request.method == "POST":
×
262
            comment = request.POST.get("comment", "")
×
263
            print(f"📝 Feedback for version {version.id}: {comment}")
×
264
            # Later, store in DB via Feedback model
265
            return redirect(f"/dashboard/?page=detail&pk={version.pk}")
×
266
        context["version"] = version
×
267

268
    return render(request, "note2webapp/reviewer.html", context)
×
269

270

271
# -------------------
272
# VERSION MANAGEMENT
273
# -------------------
274
@login_required
1✔
275
def model_versions(request, model_id):
1✔
276
    """View all versions of a model including deleted ones"""
277
    model_upload = get_object_or_404(ModelUpload, pk=model_id, user=request.user)
1✔
278
    versions = ModelVersion.objects.filter(upload=model_upload).order_by("-created_at")
1✔
279

280
    # Counts for summary
281
    total_count = versions.count()
1✔
282
    active_count = versions.filter(
1✔
283
        is_active=True, is_deleted=False, status="PASS"
284
    ).count()
285
    available_count = versions.filter(is_deleted=False, status="PASS").count()
1✔
286
    deleted_count = versions.filter(is_deleted=True).count()
1✔
287
    failed_count = versions.filter(is_deleted=False, status="FAIL").count()
1✔
288

289
    context = {
1✔
290
        "model_upload": model_upload,
291
        "versions": versions,
292
        "total_count": total_count,
293
        "active_count": active_count,
294
        "available_count": available_count,
295
        "deleted_count": deleted_count,
296
        "failed_count": failed_count,
297
    }
298
    return render(request, "note2webapp/model_versions.html", context)
1✔
299

300

301
@login_required
1✔
302
def soft_delete_version(request, version_id):
1✔
303
    """Soft delete a model version"""
304
    version = get_object_or_404(ModelVersion, id=version_id)
×
305

306
    # Check permission - only uploader (owner) can delete
307
    if request.user != version.upload.user and not request.user.is_staff:
×
308
        if request.headers.get("X-Requested-With") == "XMLHttpRequest":
×
309
            return JsonResponse(
×
310
                {"success": False, "error": "Permission denied"}, status=403
311
            )
312
        messages.error(request, "You don't have permission to delete this version.")
×
313
        return redirect("dashboard")
×
314

315
    # Check if this is the active version
316
    if version.is_active:
×
317
        # Check if there are other non-deleted versions
318
        other_versions = ModelVersion.objects.filter(
×
319
            upload=version.upload, is_deleted=False
320
        ).exclude(id=version_id)
321

322
        if other_versions.exists():
×
323
            if request.headers.get("X-Requested-With") == "XMLHttpRequest":
×
324
                return JsonResponse(
×
325
                    {
326
                        "success": False,
327
                        "error": "Cannot delete active version. Please activate another version first.",
328
                    },
329
                    status=400,
330
                )
331
            messages.error(
×
332
                request,
333
                "Cannot delete active version. Please activate another version first.",
334
            )
335
            return redirect("dashboard")
×
336

337
    if request.method == "POST":
×
338
        # Soft delete
339
        version.is_deleted = True
×
340
        version.deleted_at = timezone.now()
×
341
        version.is_active = False
×
342
        version.save()
×
343

344
        # Delete physical files from media folder
345
        files_to_delete = [
×
346
            version.model_file,
347
            version.predict_file,
348
            version.schema_file,
349
        ]
350

351
        for file_field in files_to_delete:
×
352
            if file_field:
×
353
                try:
×
354
                    if os.path.isfile(file_field.path):
×
355
                        os.remove(file_field.path)
×
356
                except Exception as e:
×
357
                    print(f"Error deleting file: {e}")
×
358

359
        if request.headers.get("X-Requested-With") == "XMLHttpRequest":
×
360
            return JsonResponse(
×
361
                {
362
                    "success": True,
363
                    "message": f"Version (Tag: {version.tag}) deleted successfully",
364
                }
365
            )
366

367
        messages.success(
×
368
            request, f"Version with tag '{version.tag}' has been deleted successfully."
369
        )
370
        return redirect(f"/model-versions/{version.upload.id}/")
×
371

372
    return redirect("dashboard")
×
373

374

375
@login_required
1✔
376
def activate_version(request, version_id):
1✔
377
    """Activate a specific version and deactivate others"""
378
    version = get_object_or_404(ModelVersion, id=version_id)
1✔
379

380
    # Check permission - only uploader (owner) can activate
381
    if request.user != version.upload.user and not request.user.is_staff:
1✔
382
        messages.error(request, "You don't have permission to activate this version.")
×
383
        return redirect("dashboard")
×
384

385
    # Don't allow activating deleted versions
386
    if version.is_deleted:
1✔
387
        messages.error(request, "Cannot activate a deleted version.")
×
NEW
388
        return redirect("model_versions", model_id=version.upload.id)
×
389

390
    # Don't allow activating if validation failed or is pending
391
    if version.status != "PASS":
1✔
392
        status_msg = (
1✔
393
            "pending validation"
394
            if version.status == "PENDING"
395
            else "that failed validation"
396
        )
397
        messages.error(
1✔
398
            request,
399
            f"Cannot activate a version that is {status_msg}. "
400
            f"Please wait for validation to complete or upload a new version.",
401
        )
402
        return redirect("model_versions", model_id=version.upload.id)
1✔
403

404
    # Set all versions of this model to inactive first
NEW
405
    ModelVersion.objects.filter(upload=version.upload).update(is_active=False)
×
406

407
    # Activate this version
NEW
408
    version.is_active = True
×
NEW
409
    version.save()
×
410

NEW
411
    messages.success(
×
412
        request,
413
        f"Version '{version.tag}' is now active. Other versions have been deactivated.",
414
    )
415

NEW
416
    return redirect("model_versions", model_id=version.upload.id)
×
417

418

419
@login_required
1✔
420
def delete_model(request, model_id):
1✔
421
    """Permanently delete a model if it has no non-deleted versions"""
422
    model_upload = get_object_or_404(ModelUpload, id=model_id)
1✔
423

424
    # Check permission - only owner or staff
425
    if request.user != model_upload.user and not request.user.is_staff:
1✔
426
        if request.headers.get("X-Requested-With") == "XMLHttpRequest":
×
427
            return JsonResponse(
×
428
                {"success": False, "error": "Permission denied"}, status=403
429
            )
430
        messages.error(request, "You don't have permission to delete this model.")
×
431
        return redirect("dashboard")
×
432

433
    # Check if model has any NON-DELETED versions
434
    active_version_count = ModelVersion.objects.filter(
1✔
435
        upload=model_upload, is_deleted=False
436
    ).count()
437

438
    if active_version_count > 0:
1✔
439
        if request.headers.get("X-Requested-With") == "XMLHttpRequest":
1✔
440
            return JsonResponse(
×
441
                {
442
                    "success": False,
443
                    "error": "Cannot delete model with active versions. Please delete all versions first.",
444
                },
445
                status=400,
446
            )
447
        messages.error(
1✔
448
            request,
449
            "Cannot delete model with active versions. Please delete all versions first.",
450
        )
451
        return redirect("dashboard")
1✔
452

453
    if request.method == "POST":
×
454
        model_name = model_upload.name
×
455
        model_upload.delete()
×
456

457
        if request.headers.get("X-Requested-With") == "XMLHttpRequest":
×
458
            return JsonResponse(
×
459
                {
460
                    "success": True,
461
                    "message": f'Model "{model_name}" has been permanently deleted.',
462
                }
463
            )
464

465
        messages.success(request, f'Model "{model_name}" has been permanently deleted.')
×
466
        return redirect("dashboard")
×
467

468
    return redirect("dashboard")
×
469

470

471
@login_required
1✔
472
def deprecate_version(request, version_id):
1✔
473
    """Deprecate (deactivate) a version"""
474
    version = get_object_or_404(ModelVersion, id=version_id)
×
475

476
    # Check permission - only uploader (owner) can deprecate
477
    if request.user != version.upload.user and not request.user.is_staff:
×
478
        if request.headers.get("X-Requested-With") == "XMLHttpRequest":
×
479
            return JsonResponse(
×
480
                {"success": False, "error": "Permission denied"}, status=403
481
            )
482
        messages.error(request, "You don't have permission to deprecate this version.")
×
483
        return redirect("dashboard")
×
484

485
    # Check if version is deleted
486
    if version.is_deleted:
×
487
        if request.headers.get("X-Requested-With") == "XMLHttpRequest":
×
488
            return JsonResponse(
×
489
                {"success": False, "error": "Cannot deprecate deleted version"},
490
                status=400,
491
            )
492
        messages.error(request, "Cannot deprecate a deleted version.")
×
493
        return redirect("dashboard")
×
494

495
    if request.method == "POST":
×
496
        # Deactivate this version
497
        version.is_active = False
×
498
        version.save()
×
499

500
        if request.headers.get("X-Requested-With") == "XMLHttpRequest":
×
501
            return JsonResponse(
×
502
                {
503
                    "success": True,
504
                    "message": f'Version with tag "{version.tag}" has been deprecated (deactivated)',
505
                    "version_id": version.id,
506
                }
507
            )
508

509
        messages.success(
×
510
            request, f"Version with tag '{version.tag}' has been deprecated."
511
        )
512
        return redirect(f"/model-versions/{version.upload.id}/")
×
513

514
    return redirect("dashboard")
×
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