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

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

10 Nov 2025 07:18PM UTC coverage: 75.054% (+11.0%) from 64.094%
63

push

travis-pro

web-flow
Merge pull request #77 from gcivil-nyu-org/feature/run_test_models_locally

fixed some UI changes and test cases

403 of 431 new or added lines in 11 files covered. (93.5%)

269 existing lines in 4 files now uncovered.

1047 of 1395 relevant lines covered (75.05%)

0.75 hits per line

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

90.63
/note2webapp/models.py
1
from django.db import models
1✔
2
from django.contrib.auth.models import User
1✔
3
from django.db.models.signals import post_save
1✔
4
from django.dispatch import receiver
1✔
5
from django.db.models import Max
1✔
6

7

8
# -----------------------
9
# USER PROFILE WITH ROLE
10
# -----------------------
11
class Profile(models.Model):
1✔
12
    ROLE_CHOICES = [
1✔
13
        ("admin", "Admin"),
14
        ("uploader", "Model Uploader"),
15
        ("reviewer", "Model Reviewer"),
16
    ]
17

18
    user = models.OneToOneField(User, on_delete=models.CASCADE)
1✔
19
    # default stays "uploader" for normal signups
20
    role = models.CharField(max_length=20, choices=ROLE_CHOICES, default="uploader")
1✔
21

22
    def __str__(self):
1✔
UNCOV
23
        return f"{self.user.username} ({self.role})"
×
24

25

26
@receiver(post_save, sender=User)
1✔
27
def create_or_update_profile(sender, instance, created, **kwargs):
1✔
28
    """
29
    Ensure every User has a Profile.
30
    If the user is staff/superuser, force role="admin".
31
    """
32
    if created:
1✔
33
        profile = Profile.objects.create(user=instance)
1✔
34
    else:
35
        profile, _ = Profile.objects.get_or_create(user=instance)
1✔
36

37
    # force admin role for Django admins
38
    if instance.is_superuser or instance.is_staff:
1✔
39
        if profile.role != "admin":
1✔
40
            profile.role = "admin"
1✔
41
            profile.save()
1✔
42
    else:
43
        # just save whatever non-admin role it has
44
        profile.save()
1✔
45

46

47
# -----------------------
48
# MODEL UPLOAD
49
# -----------------------
50
class ModelUpload(models.Model):
1✔
51
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name="uploads")
1✔
52
    name = models.CharField(max_length=200)
1✔
53
    created_at = models.DateTimeField(auto_now_add=True)
1✔
54

55
    def __str__(self):
1✔
56
        return self.name
1✔
57

58

59
# -----------------------
60
# MODEL VERSION
61
# -----------------------
62
class ModelVersion(models.Model):
1✔
63
    upload = models.ForeignKey(
1✔
64
        ModelUpload,
65
        on_delete=models.CASCADE,
66
        related_name="versions",
67
    )
68

69
    # uploaded files
70
    model_file = models.FileField(upload_to="models/")
1✔
71
    predict_file = models.FileField(upload_to="predict/")
1✔
72
    schema_file = models.FileField(upload_to="schemas/", blank=True, null=True)
1✔
73

74
    tag = models.CharField(max_length=100)
1✔
75
    information = models.TextField(blank=True, null=True)
1✔
76

77
    CATEGORY_CHOICES = [
1✔
78
        ("sentiment", "Sentiment"),
79
        ("recommendation", "Recommendation"),
80
        ("text-classification", "Text Classification"),
81
    ]
82
    category = models.CharField(
1✔
83
        max_length=50,
84
        choices=CATEGORY_CHOICES,
85
        default="sentiment",
86
    )
87

88
    status = models.CharField(
1✔
89
        max_length=20,
90
        choices=[
91
            ("PENDING", "Pending"),
92
            ("PASS", "Pass"),
93
            ("FAIL", "Fail"),
94
        ],
95
        default="PENDING",
96
    )
97

98
    is_active = models.BooleanField(default=False)
1✔
99
    log = models.TextField(blank=True, null=True)
1✔
100
    created_at = models.DateTimeField(auto_now_add=True)
1✔
101

102
    # soft delete
103
    is_deleted = models.BooleanField(default=False)
1✔
104
    deleted_at = models.DateTimeField(null=True, blank=True)
1✔
105

106
    # per-upload version number
107
    version_number = models.IntegerField(default=1)
1✔
108

109
    # hashes for dedupe
110
    model_hash = models.CharField(max_length=64, blank=True, null=True)
1✔
111
    predict_hash = models.CharField(max_length=64, blank=True, null=True)
1✔
112
    schema_hash = models.CharField(max_length=64, blank=True, null=True)
1✔
113
    bundle_hash = models.CharField(max_length=64, blank=True, null=True)
1✔
114

115
    class Meta:
1✔
116
        ordering = ["-created_at"]
1✔
117

118
    def save(self, *args, **kwargs):
1✔
119
        # assign next version number only on first save
120
        if not self.pk:
1✔
121
            last_version = ModelVersion.objects.filter(upload=self.upload).aggregate(
1✔
122
                Max("version_number")
123
            )["version_number__max"]
124
            self.version_number = (last_version or 0) + 1
1✔
125
        super().save(*args, **kwargs)
1✔
126

127
    def __str__(self):
1✔
128
        try:
1✔
129
            return f"{self.upload.name} - v{self.version_number} ({self.status})"
1✔
UNCOV
130
        except Exception:
×
UNCOV
131
            return f"ModelVersion (unsaved) - {self.status}"
×
132

133
    def get_version_number(self):
1✔
NEW
134
        return self.version_number
×
135

136
    def get_media_dir(self):
1✔
137
        """
138
        media/<category>/<upload.name>/v<version_number>/
139
        """
UNCOV
140
        safe_name = getattr(self.upload, "name", f"upload-{self.upload_id}")
×
UNCOV
141
        return f"{self.category}/{safe_name}/v{self.version_number}/"
×
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