projects.py 6.72 KB
Newer Older
James Lee's avatar
James Lee committed
1
from django.utils import timezone
2
from django.utils.functional import cached_property
James Lee's avatar
James Lee committed
3
4
5
6
from django.db import models
from imagekit.models import ProcessedImageField
from imagekit.processors import ResizeToFill
from django.conf import settings
7
from django.core.exceptions import ValidationError
8
from markdownx.models import MarkdownxField
James Lee's avatar
James Lee committed
9
from requests import get
10
import secrets
James Lee's avatar
James Lee committed
11
12
import markdown

13
14
15
16
17
18
19
20
21
22
23
24
25
def validate_project_slug(value):
    # We def don't want certain values to work as slugs!
    disallowed = [
        'about',
        'hooks',
        'admin',
    ]
    if value in disallowed:
        raise ValidationError("reserved url -- cannot be any of [{}]".format(', '.join(disallowed)))

def make_api_key():
    return secrets.token_urlsafe(8)

26
27
class ProjectManagerQS(models.QuerySet):
    def by_user(self, user):
28
        return self.filter(models.Q(author=user) | models.Q(groups__users=user) | models.Q(fallback__user=user)).distinct()
29

James Lee's avatar
James Lee committed
30
class Project(models.Model):
31
32
33
34
35
36
37
    objects = models.Manager.from_queryset(ProjectManagerQS)()
    author = models.ForeignKey(
        'projector.user',
        blank=True,
        null=True,
        on_delete=models.SET_NULL,
    )
James Lee's avatar
James Lee committed
38
39
40
    icon = ProcessedImageField(
        upload_to='icons/',
        processors=[ResizeToFill(150, 150)],
41
        format='PNG',
James Lee's avatar
James Lee committed
42
43
44
45
        blank=True,
        null=True,
    )
    title = models.CharField(max_length=250)
46
47
    slug = models.SlugField(
        max_length=250, unique=True,
48
49
50
51
52
53
54
55
56
57
58
        help_text="Must be unique. Used for URLs",
        validators=[validate_project_slug]
    )
    api_key = models.CharField(
        max_length=64, default=make_api_key,
        help_text="Used for posting w/ hooks"
    )
    visible = models.BooleanField(
        default=True,
        verbose_name="Show on FAST website",
        help_text="Set this to false to not show up on the homepage."
59
    )
60
61
62
63
    tagline = models.CharField(
        max_length=100,
        help_text="Short, catchy sentence about the project"
    )
64
    description = MarkdownxField(
65
66
        help_text="Markdown-formatted project description"
    )
67
    technologies = models.ManyToManyField('projector.Technology', blank=True)
James Lee's avatar
James Lee committed
68
    project_created = models.DateField(
69
70
71
72
73
        help_text="The approximate date this project started development or was launched. Only year / term will be used by site."
    )
    groups = models.ManyToManyField(
        'projector.Group',
        help_text='Include or add all groups which work with this project from a development perspective'
James Lee's avatar
James Lee committed
74
75
    )
    SCOPES = (
76
77
78
        ('Local', 'Local (Used by Faculty/Dept Internally)'),
        ('Cross-Department', 'Cross-Department (Invite only)'),
        ('Campus-Wide', 'Campus-Wide (Available to All)'),
James Lee's avatar
James Lee committed
79
80
81
82
83
84
    )
    scope = models.CharField(
        max_length=100,
        choices=SCOPES,
        blank=True,
    )
85
86
87
88
    url = models.CharField(
        max_length=256,
        blank=True,
        help_text='Link to project homepage. This will also act to whitelist a domain where the overlay tools can send emails from'
James Lee's avatar
James Lee committed
89
    )
90
91
92
93
94
95
96
97
98
99
    documentation_url = models.CharField(
        max_length=256,
        blank=True,
        help_text="A link to your USER-FACING documentation"
    )
    ticket_url = models.CharField(
        max_length=256,
        blank=True,
        help_text="A link, if one exists to the page for submitting tickets to this project"
    )
100
101
102
103
104
105
106
107
108
109
110
111
112
113
    gitlab_id = models.CharField(
        max_length=100, blank=True,
        help_text="This can be found under general project settings (its an integer). If you have a CHANGELOG.md file in the root of your repo it will be read."
    )
    gitlab_access_token = models.CharField(
        max_length=256,
        blank=True,
        help_text="If the default gitlab API token won't work for you, provide an alternative"
    )
    gitlab_url = models.CharField(
        max_length=256,
        blank=True,
        help_text="If your project isn't in the UW git repo, you can provide an alternative gitlab url here."
    )
James Lee's avatar
James Lee committed
114
115
116
117
118
119
120

    def __str__(self):
        return self.title

    @property
    def html(self):
        return markdown.markdown(self.description)
121

James Lee's avatar
James Lee committed
122
123
    @property
    def changelog(self):
124
125
        if not self.gitlab_id:
            return ''
James Lee's avatar
James Lee committed
126
        changelog = get(
127
128
129
130
            '{}/api/v4/projects/{}/repository/files/CHANGELOG.md/raw?ref=master'.format(
                self.gitlab_url if self.gitlab_url else
                settings.GITLAB_DEFAULT_URL,
                self.gitlab_id
James Lee's avatar
James Lee committed
131
132
            ),
            headers={
133
134
135
                'PRIVATE-TOKEN': self.gitlab_access_token
                if self.gitlab_access_token
                else settings.GITLAB_DEFAULT_ACCESS_TOKEN,
James Lee's avatar
James Lee committed
136
137
138
139
            }
        )
        return changelog.text

140
    @cached_property
James Lee's avatar
James Lee committed
141
    def changelogmd(self):
142
143
144
145
146
        try:
            return markdown.markdown(self.changelog)
        except:
            return ''

147
""" Replace with overlay.Notifications
James Lee's avatar
James Lee committed
148
149
150
151
152
153
154
155
156
157
158
class Notice(models.Model):
    project = models.ForeignKey(
        'projector.Project',
        on_delete=models.CASCADE
    )
    title = models.CharField(max_length=100)
    content = models.TextField()
    expires_on = models.DateTimeField()

    def __str__(self):
        return self.title
159
"""
James Lee's avatar
James Lee committed
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184

class Technology(models.Model):
    CATEGORIES = (
        ('LANGUAGE', 'Language'),
        ('BACKEND', 'Backend Framework'),
        ('FRONTEND', 'Frontend Framework'),
        ('PLUGIN', 'Plugin (numpy, openCV, etc)')
    )
    category = models.CharField(
        max_length=100,
        choices=CATEGORIES,
        blank=True,
    )
    name = models.CharField(max_length=100)
    slug = models.SlugField(max_length=100, blank=True)


    class Meta:
        verbose_name = 'Technology'
        verbose_name_plural = 'Technologies'
    
    def __str__(self):
        return self.name


185
186
class TrackedDocument(models.Model):
    project = models.ForeignKey(
Mirko Vucicevich's avatar
Mirko Vucicevich committed
187
        'projector.Project',
188
189
        on_delete=models.CASCADE,
        help_text="Note: limited to projects for which you are a contact",
Mirko Vucicevich's avatar
Mirko Vucicevich committed
190
    )
191
192
    title = models.CharField(max_length=256)
    slug = models.SlugField(max_length=256)
Mirko Vucicevich's avatar
Mirko Vucicevich committed
193
194
195
196
197
198
199
200
    author = models.ForeignKey(
        'projector.User',
        null=True, blank=True,
        on_delete=models.SET_NULL
    )
    date_updated = models.DateTimeField(auto_now=True)
    body = MarkdownxField()

201
class DocumentVersion(models.Model):
Mirko Vucicevich's avatar
Mirko Vucicevich committed
202
203
    # Identical to above -- we store a version of past SLAs when they change
    # Business logic found in admin.py
204
205
    document = models.ForeignKey(
        'projector.TrackedDocument',
Mirko Vucicevich's avatar
Mirko Vucicevich committed
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
        on_delete=models.CASCADE,
        related_name='past_versions'
    )
    author = models.ForeignKey(
        'projector.User',
        null=True, blank=True,
        on_delete=models.SET_NULL
    )
    date_updated = models.DateTimeField()
    body = models.TextField()

    class Meta:
        ordering = ('-date_updated',)



James Lee's avatar
James Lee committed
222
223
224
225
226
227
228