Example #1
0
class Comment(GenericModel):
    comments = models.TextField()
    parent_comment = models.ForeignKey('self',
                                       on_delete=models.CASCADE,
                                       null=True,
                                       blank=True)
    commented_by = models.ForeignKey(User,
                                     related_name='comment_user',
                                     on_delete=models.PROTECT)
    comment_date = models.DateField()
    modified_by = models.ForeignKey(User,
                                    related_name='modified_user',
                                    on_delete=models.PROTECT,
                                    null=True,
                                    blank=True)
    modified_on = models.DateField(null=True, blank=True)
    comment_heirarchy = EnumField(CommentHeirarchy,
                                  default=CommentHeirarchy.INDIVIDUAL)

    def __str__(self):
        return "{}".format(self.id)

    class Meta:
        db_table = 'notes_comment'
        verbose_name = _('Comment')
        verbose_name_plural = _('Comments')
Example #2
0
class DiscordMember(models.Model):
    access_level = EnumField(AccessLevel, default=AccessLevel.DEFAULT)

    guild = models.ForeignKey(DiscordGuild,
                              models.CASCADE,
                              related_name="members",
                              db_index=True)
    user = models.ForeignKey(DiscordUser,
                             models.CASCADE,
                             related_name="members",
                             db_index=True)

    def __str__(self):
        return f"{self.user} (access={self.access_level})"

    class Meta:
        db_table = 'members'
class Profile(CommonData):
    model_name = 'Profile'

    user = models.OneToOneField(settings.AUTH_USER_MODEL,
                                on_delete=models.CASCADE)

    salary_start: int = models.PositiveIntegerField(
        verbose_name=_('Salary from'),
        null=True,
        blank=True,
        error_messages=ErrorMessages.get_field(model=model_name,
                                               field='salary_start'))

    salary_end: int = models.PositiveIntegerField(
        verbose_name=_('Salary up'),
        null=True,
        blank=True,
        error_messages=ErrorMessages.get_field(model=model_name,
                                               field='salary_end'))

    english_level: EnglishLevel = EnumField(
        verbose_name=_('English level'),
        enum=EnglishLevel,
        default=EnglishLevel.NA,
        error_messages=ErrorMessages.get_field(model=model_name,
                                               field='english_level'))

    category: Category = models.ForeignKey(
        to=Category,
        on_delete=models.PROTECT,
        null=True,
        blank=True,
        error_messages=ErrorMessages.get_field(model=model_name,
                                               field='category_id'))

    country: Country = models.ForeignKey(
        to=Country,
        on_delete=models.PROTECT,
        null=True,
        blank=True,
        error_messages=ErrorMessages.get_field(model=model_name,
                                               field='country_id'))

    def __str__(self):
        return self.user.name
Example #4
0
class DiscordUser(models.Model):
    discord_id = models.BigAutoField(primary_key=True)
    first_seen = models.DateTimeField(auto_now_add=True)

    name = models.TextField()
    discriminator = models.CharField(max_length=4)

    trophys = models.JSONField(default=dict, blank=True)

    ping_friendly = models.BooleanField(default=True)

    language = models.CharField(max_length=6, default="en")
    first_use = models.BooleanField(default=True)

    access_level_override = EnumField(AccessLevel, default=AccessLevel.DEFAULT)

    boss_kills = models.IntegerField(default=0)

    @property
    def trophies_data(self):
        trophies_parsed = []
        for trophy_id, have in self.trophys.items():
            trophy = trophys[trophy_id]
            if have:
                trophies_parsed.append({
                    "id":
                    trophy_id,
                    "image_url":
                    static("botdata/trophies/{}.png".format(trophy_id)),
                    "name":
                    trophy["name"],
                    "description":
                    trophy["description"]
                })

        return trophies_parsed

    def __str__(self):
        return f"{self.name}#{self.discriminator}"

    class Meta:
        db_table = 'users'
class JobOffer(CommonData):
    model_name = 'JobOffer'

    position: str = models.CharField(
        verbose_name=_('Position'),
        max_length=100,
        error_messages=ErrorMessages.get_char_field(
            model=model_name, field='position')
    )

    description: str = models.TextField(
        verbose_name=_('Description'),
        error_messages=ErrorMessages.get_field(
            model=model_name, field='description')
    )

    company: Company = models.ForeignKey(
        to=Company,
        on_delete=models.PROTECT,
        error_messages=ErrorMessages.get_field(
            model=model_name, field='company_id')
    )

    seniority: Seniority = EnumField(
        verbose_name=_('Seniority'),
        enum=Seniority,
        default=Seniority.NA,
        error_messages=ErrorMessages.get_field(
            model=model_name, field='seniority')
    )

    english_level: EnglishLevel = EnumField(
        verbose_name=_('English level'),
        enum=EnglishLevel,
        default=EnglishLevel.NA,
        error_messages=ErrorMessages.get_field(
            model=model_name, field='english_level')
    )

    category: Category = models.ForeignKey(
        to=Category,
        on_delete=models.PROTECT,
        error_messages=ErrorMessages.get_field(
            model=model_name, field='category_id')
    )

    country: Country = models.ForeignKey(
        to=Country,
        on_delete=models.PROTECT,
        null=True,
        blank=True,
        error_messages=ErrorMessages.get_field(
            model=model_name, field='country_id')
    )

    cities: str = models.CharField(
        verbose_name=_('cities'),
        max_length=100,
        null=True,
        blank=True,
        error_messages=ErrorMessages.get_char_field(
            model=model_name, field='cities')
    )

    technologies = models.ManyToManyField(
        Technology,
        verbose_name=_('Technologies'),
    )

    soft_skills: str = models.TextField(
        verbose_name=_('Soft skills'),
        null=True,
        blank=True,
        error_messages=ErrorMessages.get_field(
            model=model_name, field='soft_skills')
    )

    salary_start: int = models.PositiveIntegerField(
        verbose_name=_('Salary from'),
        error_messages=ErrorMessages.get_field(
            model=model_name, field='salary_start')
    )

    salary_end: int = models.PositiveIntegerField(
        verbose_name=_('Salary up'),
        error_messages=ErrorMessages.get_field(
            model=model_name, field='salary_end')
    )

    currency: Currency = models.ForeignKey(
        to=Currency,
        on_delete=models.PROTECT,
        error_messages=ErrorMessages.get_field(
            model=model_name, field='currency_id')
    )

    benefits: str = models.TextField(
        verbose_name=_('Benefits'),
        null=True,
        blank=True,
        error_messages=ErrorMessages.get_field(
            model=model_name, field='benefits')
    )

    is_active: bool = models.BooleanField(
        verbose_name=_('Active'),
        default=True,
        error_messages=ErrorMessages.get_field(
            model=model_name, field='is_active')
    )

    def __str__(self):
        return f'{self.company.name} - {self.position}'
Example #6
0
class Project(ProjectContactDetailMixin, ProjectLocationMixin,
              base.TimeStampedModel, ModuleClusterPropertiesMixin,
              TimelinePropertiesMixin):
    slug = AutoSlugField(populate_from='name', unique=True)
    name = models.CharField(
        max_length=120,
        verbose_name=_('Title of your project'),
        help_text=_('This title will appear on the '
                    'teaser card and on top of the project '
                    'detail page. It should be max. 120 characters long'))
    organisation = models.ForeignKey(settings.A4_ORGANISATIONS_MODEL,
                                     on_delete=models.CASCADE)

    group = models.ForeignKey(Group,
                              on_delete=models.SET_NULL,
                              blank=True,
                              null=True)

    description = models.CharField(
        max_length=250,
        verbose_name=_('Short description of your project'),
        help_text=_('This short description will appear on '
                    'the header of the project and in the teaser. '
                    'It should briefly state the goal of the project '
                    'in max. 250 chars.'))
    information = RichTextCollapsibleUploadingField(
        blank=True,
        config_name='collapsible-image-editor',
        verbose_name=_('Description of your project'),
        help_text=_('This description should tell participants '
                    'what the goal of the project is, how the project’s '
                    'participation will look like. It will be always visible '
                    'in the „Info“ tab on your project’s page.'))
    result = RichTextCollapsibleUploadingField(
        blank=True,
        config_name='collapsible-image-editor',
        verbose_name=_('Results of your project'),
        help_text=_('Here you should explain what the expected outcome of the '
                    'project will be and how you are planning to use the '
                    'results. If the project is finished you should add a '
                    'summary of the results.'))
    access = EnumField(Access,
                       default=Access.PUBLIC,
                       verbose_name=_('Access to the project'))
    is_draft = models.BooleanField(default=True)
    image = fields.ConfiguredImageField(
        'heroimage',
        verbose_name=_('Header image'),
        help_prefix=_(
            'The image will be shown as a decorative background image.'),
        upload_to='projects/backgrounds',
        blank=True)
    image_copyright = fields.ImageCopyrightField(image_name=_('Header image'))
    tile_image = fields.ConfiguredImageField(
        'tileimage',
        verbose_name=_('Tile image'),
        help_prefix=_('The image will be shown in the project tile.'),
        upload_to='projects/tiles',
        blank=True)
    tile_image_copyright = fields.ImageCopyrightField(
        image_name=_('Tile image'))
    participants = models.ManyToManyField(
        settings.AUTH_USER_MODEL,
        related_name='project_participant',
        blank=True,
    )
    moderators = models.ManyToManyField(
        settings.AUTH_USER_MODEL,
        related_name='project_moderator',
        blank=True,
    )
    is_archived = models.BooleanField(
        default=False,
        verbose_name=_('Project is archived'),
        help_text=_('Archived projects are not shown in the project overview. '
                    'For project initiators they are still visible in the '
                    'dashboard.'),
    )
    topics = TopicField(verbose_name=_('Project topics'),
                        help_text=_('Add topics to your project.'))
    project_type = models.CharField(blank=True,
                                    max_length=256,
                                    default='a4projects.Project')

    objects = ProjectManager()

    class Meta:
        ordering = ['-created']

    def __str__(self):
        return self.name

    def save(self, *args, **kwargs):
        self.information = html_transforms.clean_html_field(
            self.information, 'collapsible-image-editor')
        self.result = html_transforms.clean_html_field(
            self.result, 'collapsible-image-editor')
        if self.pk is None:
            project_type = '{}.{}'.format(self._meta.app_label,
                                          self.__class__.__name__)
            self.project_type = project_type
        super(Project, self).save(*args, **kwargs)

    def get_absolute_url(self):
        return reverse('project-detail', kwargs=dict(slug=self.slug))

    # functions needed to determine permissions
    def has_member(self, user):
        """
        Everybody is member of all public projects and private projects can
        be joined as moderator or participant.
        """
        return ((user.is_authenticated and self.is_public)
                or (user in self.participants.all())
                or (user in self.moderators.all()))

    def is_group_member(self, user):
        if self.group:
            return user.groups.filter(id=self.group.id).exists()
        return False

    def has_moderator(self, user):
        return user in self.moderators.all()

    # properties
    @cached_property
    def topic_names(self):
        if hasattr(settings, 'A4_PROJECT_TOPICS'):
            choices = dict(settings.A4_PROJECT_TOPICS)
            return [choices[topic] for topic in self.topics]
        return []

    @cached_property
    def other_projects(self):
        other_projects = self.organisation.project_set\
            .filter(is_draft=False, is_archived=False).exclude(slug=self.slug)
        return other_projects

    @cached_property
    def is_private(self):
        return self.access == Access.PRIVATE

    @cached_property
    def is_public(self):
        return self.access == Access.PUBLIC

    @cached_property
    def is_semipublic(self):
        return self.access == Access.SEMIPUBLIC

    @cached_property
    def is_archivable(self):
        return not self.is_archived and self.has_finished

    # module properties
    @cached_property
    def modules(self):
        return self.module_set.all()

    @cached_property
    def published_modules(self):
        return self.module_set.filter(is_draft=False)

    @cached_property
    def unpublished_modules(self):
        return self.module_set.filter(is_draft=True)

    @cached_property
    def running_module_ends_next(self):
        """
        Return the currently active module that ends next.
        """
        return self.running_modules.order_by('module_end').first()

    @cached_property
    def past_modules(self):
        """Return past modules ordered by start."""
        return self.published_modules.past_modules()

    @cached_property
    def future_modules(self):
        """
        Return future modules ordered by start date.

        Note: Modules without a start date are assumed to start in the future.
        """
        return self.published_modules.future_modules()

    @cached_property
    def module_running_days_left(self):
        """
        Return the number of days left in the currently running module that
        ends next.

        Attention: It's a bit coarse and should only be used for estimations
        like 'ending soon', but NOT to display the number of days a project
        is still running. For that use module_running_time_left.
        """
        running_module = self.running_module_ends_next
        if running_module:
            today = timezone.now().replace(hour=0, minute=0, second=0)
            time_delta = running_module.module_end - today
            return time_delta.days
        return None

    @cached_property
    def module_running_time_left(self):
        """
        Return the time left in the currently running module that ends next.
        """

        running_module = self.running_module_ends_next
        if running_module:
            return running_module.module_running_time_left
        return None

    @cached_property
    def module_running_progress(self):
        """
        Return the progress of the currently running module that ends next
        in percent.
        """
        running_module = self.running_module_ends_next
        if running_module:
            return running_module.module_running_progress
        return None

    # properties used in timeline/module cluster logic
    @cached_property
    def end_date(self):
        # FIXME: project properties should rely on modules, not phases.
        end_date = None
        last_phase = self.published_phases.exclude(end_date=None)\
            .order_by('end_date').last()
        if last_phase and last_phase.end_date:
            end_date = last_phase.end_date
        if self.events:
            last_event = self.events.order_by('date').last()
            if end_date:
                if last_event.date > end_date:
                    end_date = last_event.date
            else:
                end_date = last_event.date
        return end_date

    @cached_property
    def events(self):
        if hasattr(self, 'offlineevent_set'):
            return self.offlineevent_set.all()

    @cached_property
    def has_future_events(self):
        if self.events:
            now = timezone.now()
            return self.events.filter(date__gt=now).exists()
        return False

    # properties relying on phases
    @cached_property
    def last_active_phase(self):
        """
        Return the last active phase.

        The last active phase is defined as the phase that out of all past
        and currently active phases started last.
        This property is used to determine which phase view is shown.
        """
        # FIXME: project properties should rely on modules, not phases.
        return self.phases\
            .filter(module__is_draft=False)\
            .past_and_active_phases()\
            .last()

    @cached_property
    def last_active_module(self):
        """
        Return the module of the last active phase.

        Attention: Might be _deprecated_ and replaced by logic coming from
        the modules.
        """
        # FIXME: project properties should rely on modules, not phases.
        last_active_phase = self.last_active_phase
        if last_active_phase:
            return last_active_phase.module
        return None

    @cached_property
    def active_phase_ends_next(self):
        """
        Return the currently active phase that ends next.
        """
        # FIXME: project properties should rely on modules, not phases.
        return self.phases.active_phases()\
            .filter(module__is_draft=False)\
            .order_by('end_date').first()

    @cached_property
    def phases(self):
        # FIXME: project properties should rely on modules, not phases.
        from adhocracy4.phases import models as phase_models
        return phase_models.Phase.objects\
            .filter(module__project=self)

    @cached_property
    def published_phases(self):
        # FIXME: project properties should rely on modules, not phases.
        from adhocracy4.phases import models as phase_models
        return phase_models.Phase.objects\
            .filter(module__project=self, module__is_draft=False)

    @cached_property
    def future_phases(self):
        # FIXME: project properties should rely on modules, not phases.
        return self.published_phases.future_phases()

    @cached_property
    def past_phases(self):
        # FIXME: project properties should rely on modules, not phases.
        return self.published_phases.past_phases()

    @cached_property
    def has_started(self):
        # FIXME: project properties should rely on modules, not phases.
        return self.published_phases.past_and_active_phases().exists()

    @cached_property
    def has_finished(self):
        # FIXME: project properties should rely on modules, not phases.
        return self.modules.exists()\
            and self.published_modules.exists()\
            and not self.published_phases.active_phases().exists()\
            and not self.published_phases.future_phases().exists()\
            and not self.has_future_events

    # deprecated properties
    @cached_property
    def active_phase(self):
        """
        Return the currently active phase.

        The currently active phase is defined as the phase that out of all
        currently active phases started last. This is analogous to the last
        active phase.

        Attention: This method is _deprecated_ as multiple phases (in
        different modules) may be active at the same time.
        """
        warnings.warn(
            "active_phase is deprecated; "
            "use active_phase_ends_next or active_module_ends_next",
            DeprecationWarning)
        last_active_phase = self.last_active_phase
        if last_active_phase and not last_active_phase.is_over:
            return last_active_phase
        return None

    @cached_property
    def days_left(self):
        """
        Return the number of days left in the currently active phase.

        Attention: This method is _deprecated_ as multiple phases may be
        active at the same time.
        """
        warnings.warn(
            "days_left is deprecated as it relies on active_phase; "
            "use module_running_days_left", DeprecationWarning)
        active_phase = self.active_phase
        if active_phase:
            today = timezone.now().replace(hour=0, minute=0, second=0)
            time_delta = active_phase.end_date - today
            return time_delta.days
        return None

    @cached_property
    def time_left(self):
        """
        Return the time left in the currently active phase that ends next.

        Attention: _deprecated_ as in the projects logic from the modules
        should be used.
        """
        warnings.warn(
            "time_left is deprecated as in the projects "
            "logic from the modules should be used; "
            "use module_running_time_left", DeprecationWarning)

        def seconds_in_units(seconds):
            unit_totals = []

            unit_limits = [([_('day'), _('days')], 24 * 3600),
                           ([_('hour'), _('hours')], 3600),
                           ([_('minute'), _('minutes')], 60),
                           ([_('second'), _('seconds')], 1)]

            for unit_name, limit in unit_limits:
                if seconds >= limit:
                    amount = int(float(seconds) / limit)
                    if amount > 1:
                        unit_totals.append((unit_name[1], amount))
                    else:
                        unit_totals.append((unit_name[0], amount))
                    seconds = seconds - (amount * limit)
            unit_totals.append((_('seconds'), 0))

            return unit_totals

        active_phase = self.active_phase_ends_next
        if active_phase:
            today = timezone.now()
            time_delta = active_phase.end_date - today
            seconds = time_delta.total_seconds()
            time_delta_list = seconds_in_units(seconds)
            best_unit = time_delta_list[0]
            time_delta_str = '{} {}'.format(str(best_unit[1]),
                                            str(best_unit[0]))
            return time_delta_str

    @cached_property
    def active_phase_progress(self):
        """
        Return the progress of the currently active phase that ends next
        in percent.

        Attention: _deprecated_ as in the projects logic from the modules
        should be used.
        """
        warnings.warn(
            "active_phase_progress is deprecated as in the projects "
            "logic from the modules should be used; "
            "use module_running_progress", DeprecationWarning)
        active_phase = self.active_phase_ends_next
        if active_phase:
            time_gone = timezone.now() - active_phase.start_date
            total_time = active_phase.end_date - active_phase.start_date
            return round(time_gone / total_time * 100)
        return None