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

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

03 Nov 2025 03:34PM UTC coverage: 56.582% (+7.8%) from 48.812%
55

push

travis-pro

web-flow
Syncing the develop and main branch

Syncing Prod and Dev

201 of 388 new or added lines in 11 files covered. (51.8%)

5 existing lines in 1 file now uncovered.

447 of 790 relevant lines covered (56.58%)

0.57 hits per line

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

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

16

17
# -------------------
18
# SIGNUP
19
# -------------------
20
def signup_view(request):
1✔
21
    if request.method == "POST":
×
NEW
22
        username = request.POST.get("username")
×
NEW
23
        password1 = request.POST.get("password1")
×
NEW
24
        password2 = request.POST.get("password2")
×
25
        # Validation
NEW
26
        errors = []
×
NEW
27
        if not username or not password1 or not password2:
×
NEW
28
            errors.append("All fields are required.")
×
NEW
29
        if password1 and password2 and password1 != password2:
×
NEW
30
            errors.append("Passwords do not match.")
×
NEW
31
        if User.objects.filter(username=username).exists():
×
NEW
32
            errors.append("Username already exists.")
×
NEW
33
        if password1 and len(password1) < 8:
×
NEW
34
            errors.append("Password must be at least 8 characters long.")
×
NEW
35
        if errors:
×
NEW
36
            for error in errors:
×
NEW
37
                messages.error(request, error)
×
NEW
38
            return render(request, "note2webapp/login.html")
×
NEW
39
        try:
×
40
            # Create user
NEW
41
            user = User.objects.create_user(username=username, password=password1)
×
42
            # Create profile with default role as 'uploader'
NEW
43
            Profile.objects.create(user=user, role="uploader")
×
44
            # Add to ModelUploader group by default
45
            group, created = Group.objects.get_or_create(name="ModelUploader")
×
46
            user.groups.add(group)
×
NEW
47
            messages.success(request, "Account created successfully! Please login.")
×
NEW
48
            return redirect("login")
×
NEW
49
        except Exception as e:
×
NEW
50
            messages.error(request, f"Error creating account: {str(e)}")
×
NEW
51
            return render(request, "note2webapp/login.html")
×
NEW
52
    return render(request, "note2webapp/login.html")
×
53

54

55
# -------------------
56
# LOGIN
57
# -------------------
58
def login_view(request):
1✔
59
    if request.method == "POST":
×
NEW
60
        username = request.POST.get("username")
×
NEW
61
        password = request.POST.get("password")
×
NEW
62
        user = authenticate(request, username=username, password=password)
×
NEW
63
        if user is not None:
×
64
            login(request, user)
×
65
            # Redirect based on user role
NEW
66
            if hasattr(user, "profile"):
×
NEW
67
                if user.profile.role == "reviewer":
×
NEW
68
                    return redirect("reviewer_dashboard")
×
69
                else:
NEW
70
                    return redirect("dashboard")
×
UNCOV
71
            return redirect("dashboard")
×
72
        else:
NEW
73
            messages.error(request, "Invalid username or password.")
×
NEW
74
    return render(request, "note2webapp/login.html")
×
75

76

77
# -------------------
78
# LOGOUT
79
# -------------------
80
def logout_view(request):
1✔
81
    logout(request)
×
NEW
82
    messages.success(request, "You have been logged out successfully.")
×
UNCOV
83
    return redirect("login")
×
84

85

86
# -------------------
87
# DASHBOARD (Role-based)
88
# -------------------
89
@login_required
1✔
90
def dashboard(request):
1✔
91
    """Redirect based on user role"""
92
    role = getattr(request.user.profile, "role", "uploader")  # fallback uploader
1✔
93
    if role == "uploader":
1✔
94
        return model_uploader_dashboard(request)  # goes to home.html
1✔
95
    elif role == "reviewer":
×
96
        return reviewer_dashboard(request)
×
97
    else:
98
        return render(request, "note2webapp/other_dashboard.html")
×
99

100

101
@login_required
1✔
102
def validation_failed(request, version_id):
1✔
103
    """View for displaying validation failure details"""
NEW
104
    version = get_object_or_404(ModelVersion, id=version_id, upload__user=request.user)
×
NEW
105
    if version.status != "FAIL":
×
NEW
106
        return redirect("model_versions", model_id=version.upload.id)
×
107

NEW
108
    return render(
×
109
        request,
110
        "note2webapp/validation_failed.html",
111
        {
112
            "version": version,
113
        },
114
    )
115

116

117
# -------------------
118
# MODEL UPLOADER DASHBOARD
119
# -------------------
120
@login_required
1✔
121
def model_uploader_dashboard(request):
1✔
122
    page = request.GET.get("page", "list")
1✔
123
    pk = request.GET.get("pk")
1✔
124

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

128
    # Add active_versions_count to EVERY upload object (only count PASSing versions)
129
    for upload in uploads:
1✔
130
        upload.active_versions_count = upload.versions.filter(
1✔
131
            is_deleted=False, status="PASS"
132
        ).count()
133

134
    # ALWAYS add uploads to context first
135
    context = {"uploads": uploads, "page": page}
1✔
136

137
    # Create new upload
138
    if page == "create":
1✔
139
        if request.method == "POST":
×
140
            form = UploadForm(request.POST)
×
141
            if form.is_valid():
×
142
                model_name = form.cleaned_data["name"]
×
143

144
                # Check if model with same name already exists for this user
145
                if ModelUpload.objects.filter(
×
146
                    user=request.user, name=model_name
147
                ).exists():
148
                    messages.error(
×
149
                        request,
150
                        f"A model with the name '{model_name}' already exists. Please choose a different name.",
151
                    )
152
                    context["form"] = form
×
153
                    return render(request, "note2webapp/home.html", context)
×
154

155
                upload = form.save(commit=False)
×
156
                upload.user = request.user
×
157
                upload.save()
×
158
                messages.success(request, f"Model '{model_name}' created successfully!")
×
159
                return redirect(f"/dashboard/?page=detail&pk={upload.pk}")
×
160
        else:
161
            form = UploadForm()
×
162
        context["form"] = form
×
163

164
    # Upload details + versions
165
    elif page == "detail" and pk:
1✔
166
        upload = get_object_or_404(ModelUpload, pk=pk, user=request.user)
×
167
        versions = upload.versions.all().order_by("-created_at")
×
168

169
        # Add version status counts
NEW
170
        version_counts = {
×
171
            "total": versions.count(),
172
            "active": versions.filter(
173
                is_active=True, is_deleted=False, status="PASS"
174
            ).count(),
175
            "available": versions.filter(is_deleted=False, status="PASS").count(),
176
            "failed": versions.filter(is_deleted=False, status="FAIL").count(),
177
            "deleted": versions.filter(is_deleted=True).count(),
178
        }
179

NEW
180
        context.update(
×
181
            {"upload": upload, "versions": versions, "version_counts": version_counts}
182
        )
183

184
    # Add version (or retry failed version)
185
    elif page == "add_version" and pk:
1✔
186
        upload = get_object_or_404(ModelUpload, pk=pk, user=request.user)
×
NEW
187
        retry_version_id = request.GET.get("retry")
×
188

189
        if request.method == "POST":
×
190
            # Validate all files are present FIRST
NEW
191
            missing_files = []
×
NEW
192
            if not request.FILES.get("model_file"):
×
NEW
193
                missing_files.append("Model file (.pt)")
×
NEW
194
            if not request.FILES.get("predict_file"):
×
NEW
195
                missing_files.append("Predict file (.py)")
×
NEW
196
            if not request.FILES.get("schema_file"):
×
NEW
197
                missing_files.append("Schema file (.json)")
×
NEW
198
            if missing_files:
×
NEW
199
                messages.error(
×
200
                    request, f"Missing required files: {', '.join(missing_files)}"
201
                )
202
                # Create form with existing POST data to preserve user input
NEW
203
                form = VersionForm(
×
204
                    initial={
205
                        "tag": request.POST.get("tag", ""),
206
                        "category": request.POST.get("category", "research"),
207
                    }
208
                )
NEW
209
                context.update(
×
210
                    {
211
                        "form": form,
212
                        "upload": upload,
213
                        "retry_version_id": (
214
                            retry_version_id if retry_version_id else None
215
                        ),
216
                        "page": "add_version",
217
                        "pk": pk,
218
                    }
219
                )
NEW
220
                return render(request, "note2webapp/home.html", context)
×
221
            # Now validate the form
222
            form = VersionForm(request.POST, request.FILES)
×
223
            if form.is_valid():
×
224
                # If retrying, update the existing version
NEW
225
                if retry_version_id:
×
NEW
226
                    try:
×
NEW
227
                        version = ModelVersion.objects.get(
×
228
                            id=retry_version_id,
229
                            upload=upload,
230
                            status="FAIL",
231
                            is_deleted=False,
232
                        )
233
                        # Update the version with new files
NEW
234
                        version.model_file = form.cleaned_data["model_file"]
×
NEW
235
                        version.predict_file = form.cleaned_data["predict_file"]
×
NEW
236
                        version.schema_file = form.cleaned_data["schema_file"]
×
NEW
237
                        version.status = "PENDING"
×
NEW
238
                        version.log = ""
×
NEW
239
                        version.save()
×
NEW
240
                        messages.info(
×
241
                            request, f"Retrying upload for version '{version.tag}'"
242
                        )
NEW
243
                    except ModelVersion.DoesNotExist:
×
NEW
244
                        messages.error(request, "Invalid version to retry")
×
NEW
245
                        return redirect(f"/dashboard/?page=detail&pk={upload.pk}")
×
246
                else:
247
                    # Create new version
NEW
248
                    version = form.save(commit=False)
×
NEW
249
                    version.upload = upload
×
NEW
250
                    version.save()
×
251

252
                # Run validation in background
253
                validate_model(version)
×
254

NEW
255
                if version.status == "FAIL":
×
NEW
256
                    return redirect("validation_failed", version_id=version.id)
×
257

NEW
258
                if not retry_version_id:
×
NEW
259
                    messages.success(
×
260
                        request, f"Version '{version.tag}' uploaded successfully!"
261
                    )
262

UNCOV
263
                return redirect(f"/dashboard/?page=detail&pk={upload.pk}")
×
264
            else:
265
                # Form validation failed (e.g., invalid tag)
NEW
266
                messages.error(request, "Please correct the errors below.")
×
267
        else:
NEW
268
            initial = {}
×
NEW
269
            if retry_version_id:
×
NEW
270
                try:
×
NEW
271
                    retry_version = ModelVersion.objects.get(
×
272
                        id=retry_version_id,
273
                        upload=upload,
274
                        status="FAIL",
275
                        is_deleted=False,
276
                    )
NEW
277
                    initial = {
×
278
                        "tag": retry_version.tag,
279
                        "category": retry_version.category,
280
                    }
NEW
281
                    context["retrying"] = True
×
NEW
282
                except ModelVersion.DoesNotExist:
×
NEW
283
                    messages.error(request, "Invalid version to retry")
×
NEW
284
                    return redirect(f"/dashboard/?page=detail&pk={upload.pk}")
×
285

NEW
286
            form = VersionForm(initial=initial)
×
287

NEW
288
        context.update(
×
289
            {
290
                "form": form,
291
                "upload": upload,
292
                "retry_version_id": retry_version_id if retry_version_id else None,
293
            }
294
        )
295

296
    return render(request, "note2webapp/home.html", context)
1✔
297

298

299
# -------------------
300
# REVIEWER DASHBOARD (multi-mode like uploader)
301
# -------------------
302
@login_required
1✔
303
def reviewer_dashboard(request):
1✔
304
    """
305
    Reviewer dashboard with 3 modes:
306

307
    1. /reviewer/?page=list
308
       Show all models that currently have an active, passing version.
309

310
    2. /reviewer/?page=detail&pk=<model_id>
311
       Show a single model and its active version (the one serving/live).
312
       This is where we render the "Model / Version Information" card.
313

314
    3. /reviewer/?page=add_feedback&pk=<version_id>
315
       Let reviewer submit feedback on a specific version.
316
    """
317

318
    page = request.GET.get("page", "list")
×
319
    pk = request.GET.get("pk")
×
UNCOV
320
    context = {"page": page}
×
321

322
    # -------------------------------------------------
323
    # MODE: LIST (default)
324
    # -------------------------------------------------
325
    if page == "list":
×
326
        # Get models that have at least one active, PASS, not-deleted version
NEW
327
        uploads = (
×
328
            ModelUpload.objects.filter(
329
                versions__is_active=True,
330
                versions__status="PASS",
331
                versions__is_deleted=False,
332
            )
333
            .distinct()
334
            .order_by("-created_at")
335
        )
336

337
        # Attach the active version object to each upload for convenience
NEW
338
        for upload in uploads:
×
NEW
339
            upload.active_version = upload.versions.filter(
×
340
                is_active=True,
341
                status="PASS",
342
                is_deleted=False,
343
            ).first()
344

NEW
345
        context["uploads"] = uploads
×
NEW
346
        return render(request, "note2webapp/reviewer.html", context)
×
347

348
    # -------------------------------------------------
349
    # MODE: DETAIL (model-level view)
350
    # -------------------------------------------------
NEW
351
    if page == "detail" and pk:
×
352
        # pk here is the model id (ModelUpload.pk)
NEW
353
        try:
×
NEW
354
            upload = ModelUpload.objects.get(pk=pk)
×
NEW
355
        except ModelUpload.DoesNotExist:
×
NEW
356
            messages.error(request, "Model not found.")
×
NEW
357
            return redirect("/reviewer/?page=list")
×
358

NEW
359
        active_version = upload.versions.filter(
×
360
            is_active=True,
361
            status="PASS",
362
            is_deleted=False,
363
        ).first()
364

NEW
365
        if not active_version:
×
NEW
366
            messages.warning(request, "This model has no active version.")
×
NEW
367
            return redirect("/reviewer/?page=list")
×
368

NEW
369
        context.update(
×
370
            {
371
                "upload": upload,
372
                "active_version": active_version,
373
            }
374
        )
NEW
375
        return render(request, "note2webapp/reviewer.html", context)
×
376

377
    # -------------------------------------------------
378
    # MODE: ADD_FEEDBACK (version-level view)
379
    # -------------------------------------------------
NEW
380
    if page == "add_feedback" and pk:
×
381
        # pk here is the version id (ModelVersion.pk)
NEW
382
        try:
×
NEW
383
            version = ModelVersion.objects.get(pk=pk)
×
NEW
384
        except ModelVersion.DoesNotExist:
×
NEW
385
            messages.error(request, "Version not found.")
×
NEW
386
            return redirect("/reviewer/?page=list")
×
387

UNCOV
388
        if request.method == "POST":
×
NEW
389
            comment = request.POST.get("comment", "").strip()
×
NEW
390
            if comment:
×
391
                # TODO: persist reviewer feedback somewhere (VersionFeedback model, etc.)
NEW
392
                messages.success(request, "Feedback submitted successfully!")
×
393
                # after submitting feedback, go back to model detail view
NEW
394
                return redirect(f"/reviewer/?page=detail&pk={version.upload.pk}")
×
395
            else:
NEW
396
                messages.error(request, "Please provide feedback comment.")
×
397

NEW
398
        context["version"] = version
×
NEW
399
        return render(request, "note2webapp/reviewer.html", context)
×
400

401
    # -------------------------------------------------
402
    # FALLBACK
403
    # -------------------------------------------------
NEW
404
    return redirect("/reviewer/?page=list")
×
405

406

407
@login_required
1✔
408
def soft_delete_version(request, version_id):
1✔
409
    """Soft delete a model version"""
410
    version = get_object_or_404(ModelVersion, id=version_id)
×
411

412
    # Check permission - only uploader (owner) can delete
413
    if request.user != version.upload.user and not request.user.is_staff:
×
414
        if request.headers.get("X-Requested-With") == "XMLHttpRequest":
×
415
            return JsonResponse(
×
416
                {"success": False, "error": "Permission denied"}, status=403
417
            )
418
        messages.error(request, "You don't have permission to delete this version.")
×
419
        return redirect("dashboard")
×
420

421
    # Check if this is the active version
422
    if version.is_active:
×
423
        # Check if there are other non-deleted versions
424
        other_versions = ModelVersion.objects.filter(
×
425
            upload=version.upload, is_deleted=False
426
        ).exclude(id=version_id)
427

428
        if other_versions.exists():
×
429
            if request.headers.get("X-Requested-With") == "XMLHttpRequest":
×
430
                return JsonResponse(
×
431
                    {
432
                        "success": False,
433
                        "error": "Cannot delete active version. Please activate another version first.",
434
                    },
435
                    status=400,
436
                )
437
            messages.error(
×
438
                request,
439
                "Cannot delete active version. Please activate another version first.",
440
            )
441
            return redirect("dashboard")
×
442

443
    if request.method == "POST":
×
444
        # Soft delete
445
        version.is_deleted = True
×
446
        version.deleted_at = timezone.now()
×
447
        version.is_active = False
×
448
        version.save()
×
449

450
        # Delete physical files
451
        files_to_delete = [
×
452
            version.model_file,
453
            version.predict_file,
454
            version.schema_file,
455
        ]
456

457
        for file_field in files_to_delete:
×
458
            if file_field:
×
459
                try:
×
460
                    if os.path.isfile(file_field.path):
×
461
                        os.remove(file_field.path)
×
462
                except Exception as e:
×
463
                    print(f"Error deleting file: {e}")
×
464

465
        if request.headers.get("X-Requested-With") == "XMLHttpRequest":
×
466
            return JsonResponse(
×
467
                {
468
                    "success": True,
469
                    "message": f"Version (Tag: {version.tag}) deleted successfully",
470
                    "reload": True,  # Add this flag
471
                }
472
            )
473

474
        messages.success(
×
475
            request, f"Version with tag '{version.tag}' has been deleted successfully."
476
        )
477
        return redirect(f"/model-versions/{version.upload.id}/")
×
478

479
    return redirect("dashboard")
×
480

481

482
@login_required
1✔
483
def activate_version(request, version_id):
1✔
484
    """Activate a specific version and deactivate others"""
485
    version = get_object_or_404(ModelVersion, id=version_id)
1✔
486

487
    # Check permission
488
    if request.user != version.upload.user and not request.user.is_staff:
1✔
489
        if request.headers.get("X-Requested-With") == "XMLHttpRequest":
×
490
            return JsonResponse(
×
491
                {
492
                    "success": False,
493
                    "error": "You don't have permission to activate this version.",
494
                },
495
                status=403,
496
            )
497
        messages.error(request, "You don't have permission to activate this version.")
×
498
        return redirect("dashboard")
×
499

500
    # Don't allow activating deleted versions
501
    if version.is_deleted:
1✔
502
        if request.headers.get("X-Requested-With") == "XMLHttpRequest":
×
503
            return JsonResponse(
×
504
                {"success": False, "error": "Cannot activate a deleted version."},
505
                status=400,
506
            )
507
        messages.error(request, "Cannot activate a deleted version.")
×
NEW
508
        return redirect("model_versions", model_id=version.upload.id)
×
509

510
    # Don't allow activating if validation failed or is pending
511
    if version.status != "PASS":
1✔
512
        status_msg = (
1✔
513
            "pending validation"
514
            if version.status == "PENDING"
515
            else "that failed validation"
516
        )
517
        if request.headers.get("X-Requested-With") == "XMLHttpRequest":
1✔
518
            return JsonResponse(
×
519
                {
520
                    "success": False,
521
                    "error": f"Cannot activate a version that is {status_msg}.",
522
                },
523
                status=400,
524
            )
525
        messages.error(
1✔
526
            request,
527
            f"Cannot activate a version that is {status_msg}. "
528
            f"Please wait for validation to complete or upload a new version.",
529
        )
530
        return redirect("model_versions", model_id=version.upload.id)
1✔
531

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

535
    # Activate this version
NEW
536
    version.is_active = True
×
NEW
537
    version.save()
×
538

NEW
539
    if request.headers.get("X-Requested-With") == "XMLHttpRequest":
×
NEW
540
        return JsonResponse(
×
541
            {"success": True, "message": f"Version '{version.tag}' is now active."}
542
        )
543

NEW
544
    messages.success(
×
545
        request,
546
        f"Version '{version.tag}' is now active. Other versions have been deactivated.",
547
    )
548

NEW
549
    return redirect("model_versions", model_id=version.upload.id)
×
550

551

552
@login_required
1✔
553
def delete_model(request, model_id):
1✔
554
    """Permanently delete a model if it has no non-deleted versions"""
555
    model_upload = get_object_or_404(ModelUpload, id=model_id)
1✔
556

557
    # Check permission - only owner or staff
558
    if request.user != model_upload.user and not request.user.is_staff:
1✔
559
        if request.headers.get("X-Requested-With") == "XMLHttpRequest":
×
560
            return JsonResponse(
×
561
                {"success": False, "error": "Permission denied"}, status=403
562
            )
563
        messages.error(request, "You don't have permission to delete this model.")
×
564
        return redirect("dashboard")
×
565

566
    # Check if model has any NON-DELETED versions
567
    active_version_count = ModelVersion.objects.filter(
1✔
568
        upload=model_upload, is_deleted=False
569
    ).count()
570

571
    if active_version_count > 0:
1✔
572
        if request.headers.get("X-Requested-With") == "XMLHttpRequest":
1✔
573
            return JsonResponse(
×
574
                {
575
                    "success": False,
576
                    "error": "Cannot delete model with active versions. Please delete all versions first.",
577
                },
578
                status=400,
579
            )
580
        messages.error(
1✔
581
            request,
582
            "Cannot delete model with active versions. Please delete all versions first.",
583
        )
584
        return redirect("dashboard")
1✔
585

586
    if request.method == "POST":
×
587
        model_name = model_upload.name
×
588
        model_upload.delete()
×
589

590
        if request.headers.get("X-Requested-With") == "XMLHttpRequest":
×
591
            return JsonResponse(
×
592
                {
593
                    "success": True,
594
                    "message": f'Model "{model_name}" has been permanently deleted.',
595
                }
596
            )
597

598
        messages.success(request, f'Model "{model_name}" has been permanently deleted.')
×
599
        return redirect("dashboard")
×
600

601
    return redirect("dashboard")
×
602

603

604
@login_required
1✔
605
def deprecate_version(request, version_id):
1✔
606
    """Deprecate (deactivate) a version"""
607
    version = get_object_or_404(ModelVersion, id=version_id)
×
608

609
    # Check permission - only uploader (owner) can deprecate
610
    if request.user != version.upload.user and not request.user.is_staff:
×
611
        if request.headers.get("X-Requested-With") == "XMLHttpRequest":
×
612
            return JsonResponse(
×
613
                {"success": False, "error": "Permission denied"}, status=403
614
            )
615
        messages.error(request, "You don't have permission to deprecate this version.")
×
616
        return redirect("dashboard")
×
617

618
    # Check if version is deleted
619
    if version.is_deleted:
×
620
        if request.headers.get("X-Requested-With") == "XMLHttpRequest":
×
621
            return JsonResponse(
×
622
                {"success": False, "error": "Cannot deprecate deleted version"},
623
                status=400,
624
            )
625
        messages.error(request, "Cannot deprecate a deleted version.")
×
626
        return redirect("dashboard")
×
627

628
    if request.method == "POST":
×
629
        # Deactivate this version
630
        version.is_active = False
×
631
        version.save()
×
632

633
        if request.headers.get("X-Requested-With") == "XMLHttpRequest":
×
634
            return JsonResponse(
×
635
                {
636
                    "success": True,
637
                    "message": f'Version with tag "{version.tag}" has been deprecated (deactivated)',
638
                    "version_id": version.id,
639
                }
640
            )
641

642
        messages.success(
×
643
            request, f"Version with tag '{version.tag}' has been deprecated."
644
        )
645
        return redirect(f"/model-versions/{version.upload.id}/")
×
646

647
    return redirect("dashboard")
×
648

649

650
@login_required
1✔
651
def test_model_cpu(request, version_id):
1✔
652
    """
653
    Allow uploaders and reviewers to test a specific version on CPU.
654
    """
655
    version = get_object_or_404(ModelVersion, id=version_id)
1✔
656
    user_role = getattr(request.user.profile, "role", None)
1✔
657
    if user_role not in ["uploader", "reviewer"]:
1✔
NEW
658
        return JsonResponse({"error": "Permission denied"}, status=403)
×
659

660
    # Load schema to prefill JSON
661
    schema_json = None
1✔
662
    if version.schema_file:
1✔
NEW
663
        try:
×
NEW
664
            with open(version.schema_file.path, "r") as f:
×
NEW
665
                schema_json = json.load(f)
×
NEW
666
        except Exception as e:
×
NEW
667
            schema_json = {"error": str(e)}
×
668

669
    result = None
1✔
670
    if request.method == "POST":
1✔
671
        try:
1✔
672
            input_json = json.loads(request.POST.get("input_data", "{}"))
1✔
673
            result = test_model_on_cpu(version, input_json)
1✔
NEW
674
        except json.JSONDecodeError:
×
NEW
675
            result = {"status": "error", "error": "Invalid JSON format"}
×
676

677
    return render(
1✔
678
        request,
679
        "note2webapp/test_model.html",
680
        {
681
            "version": version,
682
            "schema_json": json.dumps(schema_json, indent=2) if schema_json else None,
683
            "result": result,
684
        },
685
    )
686

687

688
@login_required
1✔
689
def model_versions(request, model_id):
1✔
690
    """View for managing all versions of a specific model"""
691
    model_upload = get_object_or_404(ModelUpload, pk=model_id, user=request.user)
1✔
692
    versions = model_upload.versions.all().order_by("-created_at")
1✔
693
    # Calculate version counts
694
    total_count = versions.count()
1✔
695
    active_count = versions.filter(
1✔
696
        is_active=True, is_deleted=False, status="PASS"
697
    ).count()
698
    available_count = versions.filter(is_deleted=False, status="PASS").count()
1✔
699
    failed_count = versions.filter(is_deleted=False, status="FAIL").count()
1✔
700
    deleted_count = versions.filter(is_deleted=True).count()
1✔
701
    context = {
1✔
702
        "model_upload": model_upload,
703
        "versions": versions,
704
        "total_count": total_count,
705
        "active_count": active_count,
706
        "available_count": available_count,
707
        "failed_count": failed_count,
708
        "deleted_count": deleted_count,
709
    }
710
    return render(request, "note2webapp/model_versions.html", context)
1✔
711

712

713
# Create a simple form for editing information only
714
class VersionInformationForm(forms.ModelForm):
1✔
715
    class Meta:
1✔
716
        model = ModelVersion
1✔
717
        fields = ["information"]
1✔
718
        widgets = {
1✔
719
            "information": forms.Textarea(
720
                attrs={
721
                    "rows": 6,
722
                    "placeholder": "Enter information about this model version...",
723
                }
724
            ),
725
        }
726

727
    def __init__(self, *args, **kwargs):
1✔
NEW
728
        super().__init__(*args, **kwargs)
×
NEW
729
        self.fields["information"].required = True
×
NEW
730
        self.fields["information"].label = "Model Information"
×
731

732

733
@login_required
1✔
734
def edit_version_information(request, version_id):
1✔
NEW
735
    version = get_object_or_404(ModelVersion, id=version_id)
×
736
    # Check if user owns this model
NEW
737
    if version.upload.user != request.user:
×
NEW
738
        messages.error(request, "You don't have permission to edit this version.")
×
NEW
739
        return redirect("dashboard")
×
NEW
740
    if request.method == "POST":
×
NEW
741
        form = VersionInformationForm(request.POST, instance=version)
×
NEW
742
        if form.is_valid():
×
NEW
743
            form.save()
×
NEW
744
            messages.success(
×
745
                request, f"Information for version {version.tag} updated successfully!"
746
            )
NEW
747
            return redirect("model_versions", model_id=version.upload.id)
×
748
    else:
NEW
749
        form = VersionInformationForm(instance=version)
×
NEW
750
    return render(
×
751
        request,
752
        "note2webapp/edit_version_information.html",
753
        {"form": form, "version": version},
754
    )
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