Beispiel #1
0
class DeveloperApplication(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)
    email = models.EmailField(unique=True, validators=[validate_email])
    phone_number = models.CharField(max_length=15)
    country = CountryField()
    city = models.CharField(max_length=50)
    stack = models.TextField()
    experience = models.TextField()
    discovery_story = models.TextField()
    status = models.PositiveSmallIntegerField(
        choices=APPLICATION_STATUS_CHOICES,
        help_text=','.join(['%s - %s' % (item[0], item[1]) for item in APPLICATION_STATUS_CHOICES]),
        default=REQUEST_STATUS_INITIAL
    )
    created_at = models.DateTimeField(auto_now_add=True)
    confirmation_key = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
    confirmation_sent_at = models.DateTimeField(blank=True, null=True, editable=False)
    used = models.BooleanField(default=False)
    used_at = models.DateTimeField(blank=True, null=True, editable=False)

    def __str__(self):
        return self.display_name

    @property
    def display_name(self):
        return '%s %s' % (self.first_name, self.last_name)

    @property
    def country_name(self):
        return self.country.name

    country_name.fget.short_description = 'country'
Beispiel #2
0
class TaskRequest(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL,
                             on_delete=models.DO_NOTHING)
    task = models.ForeignKey(Task, on_delete=models.CASCADE)
    type = models.PositiveSmallIntegerField(
        choices=TASK_REQUEST_CHOICES,
        help_text=','.join(
            ['%s - %s' % (item[0], item[1]) for item in TASK_REQUEST_CHOICES]))
    created_at = models.DateTimeField(auto_now_add=True)

    def __unicode__(self):
        return '%s - %s' % (self.get_type_display(), self.task.summary)

    @staticmethod
    @allow_staff_or_superuser
    def has_read_permission(request):
        return True

    @allow_staff_or_superuser
    def has_object_read_permission(self, request):
        return self.task.has_object_read_permission(request)

    @staticmethod
    @allow_staff_or_superuser
    def has_write_permission(request):
        return request.user.type == USER_TYPE_DEVELOPER

    @allow_staff_or_superuser
    def has_object_write_permission(self, request):
        return request.user == self.user
Beispiel #3
0
class BaseAttachmentModel(BaseModel):
    """
    Base model to store files or images to Items
    """
    name = models.CharField(
        null=True,
        blank=True,
        max_length=255,
        verbose_name=_('BaseItemAttachmentModel.name.verbose_name'),
        help_text=_('BaseItemAttachmentModel.name.help_text'))
    position = models.PositiveSmallIntegerField(
        # Note: Will be set in admin via adminsortable2
        # The JavaScript which performs the sorting is 1-indexed !
        default=0,
        blank=False,
        null=False)

    def __str__(self):
        return self.name

    def full_clean(self, *, parent_instance, **kwargs):
        if self.user_id is None:
            # inherit owner of this link from parent model instance
            self.user_id = parent_instance.user_id

        return super().full_clean(**kwargs)

    class Meta:
        abstract = True
class Questionnaire(models.Model):
    PHASE_CHOICES = (
        (1, 'Phase-1'),
        (2, 'Phase-2'),
        (3, 'Phase-3'),
        (4, 'Phase-4'),
    )

    identifier = models.UUIDField(default=uuid.uuid4,
                                  editable=False,
                                  unique=True)
    name = models.CharField(max_length=500)
    phase = models.PositiveSmallIntegerField(choices=PHASE_CHOICES, default=1)
    root = models.BooleanField(
        default=False,
        help_text='Note that you cannot delete the root questionnaire.')

    def __str__(self):
        return self.name

    # TODO needed? (already enforced in forms.py)
    def save(self,
             force_insert=False,
             force_update=False,
             using=None,
             update_fields=None,
             *args,
             **kwargs):
        root_field = self.root
        if Questionnaire.objects.filter(root=True).exists():
            invalid = False
            if self.pk is None:  # if new questionnaire is being added
                if root_field is True:
                    invalid = True
            else:  # if existing questionnaire is being edited
                if Questionnaire.objects.get(root=True).pk != self.pk:
                    if root_field is True:
                        invalid = True
            if invalid:
                raise ValidationError('You already have a root questionnaire!')
        super(Questionnaire, self).save(force_insert=force_insert,
                                        force_update=force_update,
                                        using=using,
                                        update_fields=update_fields,
                                        *args,
                                        **kwargs)

    # TODO needed? (already enforced in admin.py)
    def delete(self, using=None, keep_parents=False):
        if self.root:
            raise ValidationError(
                'You cannot delete a root question! Consider editing it instead.'
            )
        super(Questionnaire, self).delete(using=using,
                                          keep_parents=keep_parents)
class Post(models.Model):
    POST_TYPE = (
        (1, 'Article'),
        (2, 'Course'),
        (3, 'Job'),
        (4, 'Project'),
    )

    identifier = models.UUIDField(default=uuid.uuid4,
                                  editable=False,
                                  unique=True)
    slug = models.SlugField(unique=True, null=True, blank=True, max_length=512)
    type = models.PositiveSmallIntegerField(choices=POST_TYPE, default=1)
    tags = tagulous.models.TagField(related_name='posts', to=Tag, blank=True)

    author = models.ForeignKey('users.CustomUser',
                               on_delete=models.CASCADE,
                               related_name='blogs')
    title = models.CharField(max_length=255)
    body = BleachField()
    preview = models.CharField(
        max_length=300,
        help_text='A short preview of this post that is shown in list of posts.'
    )

    likes = models.ManyToManyField('users.CustomUser', blank=True)
    allow_comments = models.BooleanField(default=True)

    def save(self, *args, **kwargs):
        self.slug = slugify(f'{self.title} {self.identifier}',
                            allow_unicode=True)
        super(Post, self).save(*args, **kwargs)

    def __str__(self):
        return f'{self.title}, by {self.author.first_name} {self.author.last_name}'

    @property
    def likes_count(self):
        return self.likes.count()

    @property
    def relative_url(self):
        return reverse('blog-post', kwargs={'slug': self.slug})

    def get_absolute_url(self):
        domain = Site.objects.get_current().domain
        protocol = "https" if settings.PRODUCTION_SERVER else "http"
        absolute_url = f'{protocol}://{domain}{self.relative_url}'
        return absolute_url
class Question(models.Model):
    identifier = models.UUIDField(default=uuid.uuid4,
                                  editable=False,
                                  unique=True)
    body = models.CharField(max_length=1000)
    questionnaire = models.ForeignKey('Questionnaire',
                                      related_name='question',
                                      on_delete=models.CASCADE)
    multiselect = models.BooleanField(default=False)
    position = models.PositiveSmallIntegerField("position", null=True)

    class Meta:
        ordering = ['position']

    def __str__(self):
        return self.body
Beispiel #7
0
class ProgressEvent(models.Model):
    task = models.ForeignKey(Task, on_delete=models.CASCADE)
    type = models.PositiveSmallIntegerField(
        choices=PROGRESS_EVENT_TYPE_CHOICES,
        default=PROGRESS_EVENT_TYPE_DEFAULT,
        help_text=','.join([
            '%s - %s' % (item[0], item[1])
            for item in PROGRESS_EVENT_TYPE_CHOICES
        ]))
    due_at = models.DateTimeField()
    title = models.CharField(max_length=200, blank=True, null=True)
    description = models.CharField(max_length=1000, blank=True, null=True)
    created_by = models.ForeignKey(settings.AUTH_USER_MODEL,
                                   related_name='progress_events_created',
                                   blank=True,
                                   null=True)
    created_at = models.DateTimeField(auto_now_add=True)
    last_reminder_at = models.DateTimeField(blank=True, null=True)

    def __unicode__(self):
        return '%s | %s - %s' % (self.get_type_display(), self.task.summary,
                                 self.due_at)

    class Meta:
        unique_together = ('task', 'due_at')
        ordering = ['due_at']

    @staticmethod
    @allow_staff_or_superuser
    def has_read_permission(request):
        return True

    @allow_staff_or_superuser
    def has_object_read_permission(self, request):
        return self.task.has_object_read_permission(request)

    @staticmethod
    @allow_staff_or_superuser
    def has_write_permission(request):
        return request.user.type == USER_TYPE_PROJECT_OWNER

    @allow_staff_or_superuser
    def has_object_write_permission(self, request):
        return request.user == self.task.user
Beispiel #8
0
class ProgressReport(models.Model):
    event = models.OneToOneField(ProgressEvent, on_delete=models.CASCADE)
    user = models.ForeignKey(settings.AUTH_USER_MODEL,
                             on_delete=models.DO_NOTHING)
    status = models.PositiveSmallIntegerField(
        choices=PROGRESS_REPORT_STATUS_CHOICES,
        help_text=','.join([
            '%s - %s' % (item[0], item[1])
            for item in PROGRESS_REPORT_STATUS_CHOICES
        ]))
    percentage = models.PositiveIntegerField(
        validators=[MinValueValidator(0),
                    MaxValueValidator(100)])
    accomplished = models.TextField()
    next_steps = models.TextField(blank=True, null=True)
    remarks = models.TextField(blank=True, null=True)
    created_at = models.DateTimeField(auto_now_add=True)
    uploads = GenericRelation(Upload, related_query_name='progress_reports')

    def __unicode__(self):
        return '{0} - {1}%'.format(self.event, self.percentage)

    @staticmethod
    @allow_staff_or_superuser
    def has_read_permission(request):
        return True

    @allow_staff_or_superuser
    def has_object_read_permission(self, request):
        return self.event.task.has_object_read_permission(request)

    @staticmethod
    @allow_staff_or_superuser
    def has_write_permission(request):
        return request.user.type == USER_TYPE_DEVELOPER

    @allow_staff_or_superuser
    def has_object_write_permission(self, request):
        return request.user == self.user
Beispiel #9
0
class Task(models.Model):
    project = models.ForeignKey(Project,
                                related_name='tasks',
                                on_delete=models.DO_NOTHING,
                                blank=True,
                                null=True)
    user = models.ForeignKey(settings.AUTH_USER_MODEL,
                             related_name='tasks_created',
                             on_delete=models.DO_NOTHING)
    title = models.CharField(max_length=200)
    description = models.TextField(blank=True, null=True)
    remarks = models.TextField(blank=True, null=True)
    url = models.URLField(blank=True, null=True)
    fee = models.BigIntegerField()
    currency = models.CharField(max_length=5,
                                choices=CURRENCY_CHOICES,
                                default=CURRENCY_CHOICES[0][0])
    deadline = models.DateTimeField(blank=True, null=True)
    skills = tagulous.models.TagField(Skill, blank=True)
    visibility = models.PositiveSmallIntegerField(
        choices=VISIBILITY_CHOICES, default=VISIBILITY_CHOICES[0][0])
    update_interval = models.PositiveIntegerField(blank=True, null=True)
    update_interval_units = models.PositiveSmallIntegerField(
        choices=UPDATE_SCHEDULE_CHOICES, blank=True, null=True)
    apply = models.BooleanField(default=True)
    closed = models.BooleanField(default=False)
    paid = models.BooleanField(default=False)
    applicants = models.ManyToManyField(settings.AUTH_USER_MODEL,
                                        through='Application',
                                        through_fields=('task', 'user'),
                                        related_name='task_applications',
                                        blank=True)
    participants = models.ManyToManyField(settings.AUTH_USER_MODEL,
                                          through='Participation',
                                          through_fields=('task', 'user'),
                                          related_name='task_participants',
                                          blank=True)
    satisfaction = models.SmallIntegerField(blank=True, null=True)
    created_at = models.DateTimeField(auto_now_add=True)
    apply_closed_at = models.DateTimeField(blank=True, null=True)
    closed_at = models.DateTimeField(blank=True, null=True)
    paid_at = models.DateTimeField(blank=True, null=True)
    comments = GenericRelation(Comment, related_query_name='tasks')
    uploads = GenericRelation(Upload, related_query_name='tasks')
    ratings = GenericRelation(Rating, related_query_name='tasks')

    def __unicode__(self):
        return self.summary

    class Meta:
        ordering = ['-created_at']
        unique_together = ('user', 'title', 'fee')

    @staticmethod
    @allow_staff_or_superuser
    def has_read_permission(request):
        return True

    @allow_staff_or_superuser
    def has_object_read_permission(self, request):
        if self.visibility == VISIBILITY_DEVELOPER:
            return request.user.type == USER_TYPE_DEVELOPER
        elif self.visibility == VISIBILITY_MY_TEAM:
            return bool(
                Connection.objects.exclude(accepted=False).filter(
                    Q(from_user=self.user, to_user=request.user)
                    | Q(from_user=request.user, to_user=self.user)).count())
        elif self.visibility == VISIBILITY_CUSTOM:
            return self.participation_set.filter(
                (Q(accepted=True) | Q(responded=False)),
                user=request.user).count()
        return False

    @staticmethod
    @allow_staff_or_superuser
    def has_write_permission(request):
        return request.user.type == USER_TYPE_PROJECT_OWNER

    @staticmethod
    @allow_staff_or_superuser
    def has_update_permission(request):
        return True

    @allow_staff_or_superuser
    def has_object_write_permission(self, request):
        return request.user == self.user

    @allow_staff_or_superuser
    def has_object_update_permission(self, request):
        if self.has_object_write_permission(request):
            return True
        # Participants can edit participation info directly on task object
        if request.method in ['PUT', 'PATCH']:
            allowed_keys = [
                'assignee', 'participants', 'confirmed_participants',
                'rejected_participants'
            ]
            if not [x for x in request.data.keys() if not x in allowed_keys]:
                return self.participation_set.filter(
                    (Q(accepted=True) | Q(responded=False)),
                    user=request.user).count()
        return False

    def display_fee(self, amount=None):
        if amount is None:
            amount = self.fee
        if self.currency in CURRENCY_SYMBOLS:
            return '%s%s' % (CURRENCY_SYMBOLS[self.currency],
                             floatformat(amount, arg=-2))
        return amount

    @property
    def summary(self):
        return '%s - Fee: %s' % (self.title, self.display_fee())

    @property
    def excerpt(self):
        try:
            return strip_tags(self.description).strip()
        except:
            return None

    @property
    def skills_list(self):
        return str(self.skills)

    @property
    def milestones(self):
        return self.progressevent_set.filter(type__in=[
            PROGRESS_EVENT_TYPE_MILESTONE, PROGRESS_EVENT_TYPE_SUBMIT
        ])

    @property
    def progress_events(self):
        return self.progressevent_set.all()

    @property
    def participation(self):
        return self.participation_set.filter(
            Q(accepted=True) | Q(responded=False))

    @property
    def assignee(self):
        try:
            return self.participation_set.get(
                (Q(accepted=True) | Q(responded=False)), assignee=True)
        except:
            return None

    @property
    def update_schedule_display(self):
        if self.update_interval and self.update_interval_units:
            if self.update_interval == 1 and self.update_interval_units == UPDATE_SCHEDULE_DAILY:
                return 'Daily'
            interval_units = str(
                self.get_update_interval_units_display()).lower()
            if self.update_interval == 1:
                return 'Every %s' % interval_units
            return 'Every %s %ss' % (self.update_interval, interval_units)
        return None

    @property
    def applications(self):
        return self.application_set.filter(responded=False)

    @property
    def all_uploads(self):
        return Upload.objects.filter(
            Q(tasks=self) | Q(comments__tasks=self)
            | Q(progress_reports__event__task=self))

    @property
    def meta_payment(self):
        return {
            'task_url': '/task/%s/' % self.id,
            'amount': self.fee,
            'currency': self.currency
        }

    def get_default_participation(self):
        tags = ['tunga.io', 'tunga']
        if self.skills:
            tags.extend(str(self.skills).split(','))
        tunga_share = '{share}%'.format(**{'share': TUNGA_SHARE_PERCENTAGE})
        return {
            'type':
            'payment',
            'language':
            'EN',
            'title':
            self.summary,
            'description':
            self.excerpt or self.summary,
            'keywords':
            tags,
            'participants': [{
                'id': 'mailto:%s' % TUNGA_SHARE_EMAIL,
                'role': 'owner',
                'share': tunga_share
            }]
        }

    def mobbr_participation(self, check_only=False):
        participation_meta = self.get_default_participation()
        if not self.url:
            return participation_meta, False

        mobbr_info_url = '%s?url=%s' % (
            'https://api.mobbr.com/api_v1/uris/info',
            urllib.quote_plus(self.url))
        r = requests.get(mobbr_info_url,
                         **{'headers': {
                             'Accept': 'application/json'
                         }})
        has_script = False
        if r.status_code == 200:
            response = r.json()
            task_script = response['result']['script']
            for meta_key in participation_meta:
                if meta_key == 'keywords':
                    if isinstance(task_script[meta_key], list):
                        participation_meta[meta_key].extend(
                            task_script[meta_key])
                elif meta_key == 'participants':
                    if isinstance(task_script[meta_key], list):
                        absolute_shares = []
                        relative_shares = []
                        absolute_participants = []
                        relative_participants = []

                        for key, participant in enumerate(
                                task_script[meta_key]):
                            if re.match(r'\d+%$', participant['share']):
                                share = int(participant['share'].replace(
                                    "%", ""))
                                if share > 0:
                                    absolute_shares.append(share)
                                    new_participant = participant
                                    new_participant['share'] = share
                                    absolute_participants.append(
                                        new_participant)
                            else:
                                share = int(participant['share'])
                                if share > 0:
                                    relative_shares.append(share)
                                    new_participant = participant
                                    new_participant['share'] = share
                                    relative_participants.append(
                                        new_participant)

                        additional_participants = []
                        total_absolutes = sum(absolute_shares)
                        total_relatives = sum(relative_shares)
                        if total_absolutes >= 100 or total_relatives == 0:
                            additional_participants = absolute_participants
                        elif total_absolutes == 0:
                            additional_participants = relative_participants
                        else:
                            additional_participants = absolute_participants
                            for participant in relative_participants:
                                share = int(
                                    round(((participant['share'] *
                                            (100 - total_absolutes)) /
                                           total_relatives), 0))
                                if share > 0:
                                    new_participant = participant
                                    new_participant['share'] = share
                                    additional_participants.append(
                                        new_participant)
                        if len(additional_participants):
                            participation_meta[meta_key].extend(
                                additional_participants)
                            has_script = True
                elif meta_key in task_script:
                    participation_meta[meta_key] = task_script[meta_key]
        return participation_meta, has_script

    @property
    def meta_participation(self):
        participation_meta, has_script = self.mobbr_participation()
        # TODO: Update local participation script to use defined shares
        if not has_script:
            participants = self.participation_set.filter(
                accepted=True).order_by('share')
            total_shares = 100 - TUNGA_SHARE_PERCENTAGE
            num_participants = participants.count()
            for participant in participants:
                participation_meta['participants'].append({
                    'id':
                    'mailto:%s' % participant.user.email,
                    'role':
                    participant.role,
                    'share':
                    int(total_shares / num_participants)
                })
        return participation_meta
Beispiel #10
0
class Integration(models.Model):
    task = models.ForeignKey(Task, on_delete=models.CASCADE)
    provider = models.CharField(max_length=30,
                                choices=providers.registry.as_choices())
    type = models.PositiveSmallIntegerField(
        choices=INTEGRATION_TYPE_CHOICES,
        help_text=','.join([
            '%s - %s' % (item[0], item[1]) for item in INTEGRATION_TYPE_CHOICES
        ]))
    events = models.ManyToManyField(IntegrationEvent,
                                    related_name='integrations')
    secret = models.CharField(max_length=30, default=get_random_string)
    created_by = models.ForeignKey(settings.AUTH_USER_MODEL,
                                   related_name='integrations_created',
                                   blank=True,
                                   null=True)
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    def __unicode__(self):
        return '%s - %s' % (self.get_provider_display(), self.task.summary)

    class Meta:
        unique_together = ('task', 'provider')
        ordering = ['created_at']

    @staticmethod
    @allow_staff_or_superuser
    def has_read_permission(request):
        return True

    @allow_staff_or_superuser
    def has_object_read_permission(self, request):
        return request.user == self.task.user

    @staticmethod
    @allow_staff_or_superuser
    def has_write_permission(request):
        return request.user.type == USER_TYPE_PROJECT_OWNER

    @allow_staff_or_superuser
    def has_object_write_permission(self, request):
        return request.user == self.task.user

    @property
    def hook_id(self):
        try:
            return self.integrationmeta_set.get(meta_key='hook_id').meta_value
        except:
            return None

    @property
    def repo_id(self):
        try:
            return self.integrationmeta_set.get(meta_key='repo_id').meta_value
        except:
            return None

    @property
    def repo_full_name(self):
        try:
            return self.integrationmeta_set.get(
                meta_key='repo_full_name').meta_value
        except:
            return None

    @property
    def issue_id(self):
        try:
            return self.integrationmeta_set.get(meta_key='issue_id').meta_value
        except:
            return None

    @property
    def issue_number(self):
        try:
            return self.integrationmeta_set.get(
                meta_key='issue_number').meta_value
        except:
            return None