from django.utils import timezone from django.utils.functional import cached_property from django.db import models from imagekit.models import ProcessedImageField from imagekit.processors import ResizeToFill from django.conf import settings from django.core.exceptions import ValidationError from markdownx.models import MarkdownxField from requests import get import secrets import markdown 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) class ProjectManagerQS(models.QuerySet): def by_user(self, user): return self.filter(models.Q(author=user) | models.Q(groups__users=user) | models.Q(fallback__user=user)).distinct() class Project(models.Model): objects = models.Manager.from_queryset(ProjectManagerQS)() author = models.ForeignKey( 'projector.user', blank=True, null=True, on_delete=models.SET_NULL, ) icon = ProcessedImageField( upload_to='icons/', processors=[ResizeToFill(150, 150)], format='PNG', blank=True, null=True, ) title = models.CharField(max_length=250) slug = models.SlugField( max_length=250, unique=True, 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." ) tagline = models.CharField( max_length=100, help_text="Short, catchy sentence about the project" ) description = MarkdownxField( help_text="Markdown-formatted project description" ) technologies = models.ManyToManyField('projector.Technology', blank=True) project_created = models.DateField( 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' ) SCOPES = ( ('Local', 'Local (Used by Faculty/Dept Internally)'), ('Cross-Department', 'Cross-Department (Invite only)'), ('Campus-Wide', 'Campus-Wide (Available to All)'), ) scope = models.CharField( max_length=100, choices=SCOPES, blank=True, ) 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' ) 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" ) 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." ) def __str__(self): return self.title @property def html(self): return markdown.markdown(self.description) @property def changelog(self): if not self.gitlab_id: return '' changelog = get( '{}/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 ), headers={ 'PRIVATE-TOKEN': self.gitlab_access_token if self.gitlab_access_token else settings.GITLAB_DEFAULT_ACCESS_TOKEN, } ) return changelog.text @cached_property def changelogmd(self): try: return markdown.markdown(self.changelog) except: return '' """ Replace with overlay.Notifications 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 """ 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 class TrackedDocument(models.Model): project = models.ForeignKey( 'projector.Project', on_delete=models.CASCADE, help_text="Note: limited to projects for which you are a contact", ) title = models.CharField(max_length=256) slug = models.SlugField(max_length=256) author = models.ForeignKey( 'projector.User', null=True, blank=True, on_delete=models.SET_NULL ) date_updated = models.DateTimeField(auto_now=True) body = MarkdownxField() class DocumentVersion(models.Model): # Identical to above -- we store a version of past SLAs when they change # Business logic found in admin.py document = models.ForeignKey( 'projector.TrackedDocument', 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',)