예제 #1
0
class IssueComment(UIDMixin):
    issue = models.ForeignKey(Issue, related_name="comments")
    active = models.BooleanField(default=True)
    ordinal = models.PositiveIntegerField(null=True, blank=True)
    created_at = models.DateTimeField(auto_now_add=True,
                                      verbose_name=_("Created at"))
    created_by = models.ForeignKey(settings.AUTH_USER_MODEL,
                                   verbose_name=_("Created by"),
                                   related_name="issue_comments_created")

    version = models.PositiveIntegerField(default=1)
    last_edited_at = models.DateTimeField(auto_now_add=True,
                                          verbose_name=_("Last Edited at"))
    last_edited_by = models.ForeignKey(
        settings.AUTH_USER_MODEL,
        verbose_name=_("Created by"),
        related_name="issue_comments_last_edited",
        null=True,
        blank=True)
    content = HTMLField(verbose_name=_("Comment"))

    class Meta:
        ordering = ('created_at', )

    def update_content(self, expected_version, author, content):
        """ creates a new revision and updates current comment """
        if self.version != expected_version:
            return False

        content = enhance_html(content.strip())

        if self.content == content:
            return True

        with transaction.commit_on_success():
            IssueCommentRevision.objects.create(comment=self,
                                                version=expected_version,
                                                created_at=self.created_at,
                                                created_by=self.created_by,
                                                content=self.content)
            self.version += 1
            self.last_edited_at = timezone.now()
            self.last_edited_by = author
            self.content = content
            self.save()

        return True

    @models.permalink
    def get_delete_url(self):
        return "delete_issue_comment", (self.issue.community.id, self.id)

    @models.permalink
    def get_edit_url(self):
        return "edit_issue_comment", (self.issue.community.id, self.id)
예제 #2
0
class AgendaItem(ConfidentialByRelationMixin):

    confidential_from = 'issue'

    objects = ConfidentialManager()

    meeting = models.ForeignKey('Meeting', verbose_name=_("Meeting"),
                                related_name="agenda")
    issue = models.ForeignKey(Issue, verbose_name=_("Issue"),
                              related_name="agenda_items")
    background = HTMLField(_("Background"), null=True, blank=True)
    order = models.PositiveIntegerField(default=100, verbose_name=_("Order"))
    closed = models.BooleanField(_('Closed'), default=True)

    class Meta:
        unique_together = (("meeting", "issue"),)
        verbose_name = _("Agenda Item")
        verbose_name_plural = _("Agenda Items")
        ordering = ('meeting__created_at', 'order')

    def __unicode__(self):
        return self.issue.title

#     def natural_key(self):
#         return (self.meeting.natural_key(), self.issue.natural_key())
#     natural_key.dependencies = ['meetings.meeting', 'issues.issue']

    def attachments(self):
        return self.issue.attachments.filter(agenda_item=self)

    def comments(self):
        return self.issue.comments.filter(active=True, meeting=self.meeting)

    def proposals(self, user=None, community=None):
        rv = self.issue.proposals.object_access_control(
            user=user, community=community).filter(
            active=True, decided_at_meeting=self.meeting)
        return rv

    def accepted_proposals(self, user=None, community=None):
        rv = self.proposals(user=user, community=community).filter(
            status=ProposalStatus.ACCEPTED)
        return rv

    def rejected_proposals(self, user=None, community=None):
        rv = self.proposals(user=user, community=community).filter(
            status=ProposalStatus.REJECTED)
        return rv
예제 #3
0
class Issue(UIDMixin, ConfidentialMixin):
    objects = IssueManager()

    active = models.BooleanField(_("Active"), default=True)
    community = models.ForeignKey('communities.Community',
                                  on_delete=models.PROTECT,
                                  related_name="issues")
    created_at = models.DateTimeField(_("Created at"), auto_now_add=True)
    created_by = models.ForeignKey(settings.AUTH_USER_MODEL,
                                   verbose_name=_("Created by"),
                                   on_delete=models.PROTECT,
                                   related_name="issues_created")

    title = models.CharField(_("Title"), max_length=300)
    abstract = HTMLField(_("Background"), null=True, blank=True)
    content = HTMLField(_("Content"), null=True,
                        blank=True)  # TODO: remove me safely

    calculated_score = models.IntegerField(_("Calculated Score"),
                                           default=0)  # TODO: remove me

    status = models.IntegerField(choices=IssueStatus.choices,
                                 default=IssueStatus.OPEN)
    statuses = IssueStatus

    order_in_upcoming_meeting = models.IntegerField(
        _("Order in upcoming meeting"), default=0, null=True, blank=True)
    order_by_votes = models.FloatField(
        _("Order in upcoming meeting by votes"), default=0, null=True,
        blank=True)

    length_in_minutes = models.IntegerField(_("Length (in minutes)"),
                                            null=True, blank=True)

    completed = models.BooleanField(_("Discussion completed"),
                                    default=False)  # TODO: remove me safely
    is_published = models.BooleanField(_("Is published to members"),
                                       default=False)

    voteable = models.BooleanField(_("Open for voting"), default=False)

    class Meta:
        verbose_name = _("Issue")
        verbose_name_plural = _("Issues")
        ordering = ['order_in_upcoming_meeting', 'title']

    def __str__(self):
        return self.title

    @models.permalink
    def get_edit_url(self):
        return ("issue_edit", (str(self.community.pk), str(self.pk),))

    @models.permalink
    def get_delete_url(self):
        return ("issue_delete", (str(self.community.pk), str(self.pk),))

    @models.permalink
    def get_absolute_url(self):
        return ("issue", (str(self.community.pk), str(self.pk),))

    @models.permalink
    def get_next_upcoming_issue_url(self):
        try:
            next = Issue.objects.filter(community=self.community, status=2, active=True).filter(
                order_in_upcoming_meeting__gt=self.order_in_upcoming_meeting).order_by(
                'order_in_upcoming_meeting').first()
            return "issue", (str(self.community.pk), str(next.pk),)
        except:
            return "community", (str(self.community.pk),)

    def active_proposals(self):
        return self.proposals.filter(active=True)

    def open_proposals(self):
        return self.active_proposals().filter(
            status=Proposal.statuses.IN_DISCUSSION)

    def active_comments(self):
        return self.comments.filter(active=True)

    def new_comments(self):
        return self.comments.filter(meeting_id=None)

    def historical_comments(self):
        return self.comments.filter(active=True).exclude(meeting_id=None)

    def active_references(self):
        return self.references.filter(active=True)

    def new_references(self):
        return self.references.filter(meeting_id=None)

    def historical_references(self):
        return self.references.filter(active=True).exclude(meeting_id=None)

    def has_closed_parts(self):
        """ Should be able to be viewed """

    @property
    def is_upcoming(self):
        return self.status in IssueStatus.IS_UPCOMING

    @property
    def is_current(self):
        return self.status in IssueStatus.IS_UPCOMING and self.community.upcoming_meeting_started

    def changed_in_current(self):
        decided_at_current = self.proposals.filter(active=True,
                                                   decided_at_meeting=None,
                                                   status__in=[
                                                       ProposalStatus.ACCEPTED,
                                                       ProposalStatus.REJECTED
                                                   ])
        return decided_at_current or self.new_comments().filter(active=True)

    @property
    def is_archived(self):
        return self.status == IssueStatus.ARCHIVED

    @property
    def in_closed_meeting(self):
        return meetings.models.AgendaItem.objects.filter(issue=self).exists()

    @property
    def can_straw_vote(self):

        # test date/time limit
        # if self.community.voting_ends_at:
        #     time_till_close = self.community.voting_ends_at - timezone.now()
        #     if time_till_close.total_seconds() <= 0:
        #         return False

        return self.community.straw_voting_enabled and \
               self.is_upcoming and \
               self.voteable and \
               self.community.upcoming_meeting_is_published and \
               self.proposals.open().count() > 0

    def current_attachments(self):
        """Returns attachments not yet attached to an agenda item"""
        return self.attachments.filter(agenda_item__isnull=True)
예제 #4
0
class Proposal(UIDMixin, ConfidentialMixin):
    objects = ProposalManager()

    issue = models.ForeignKey(Issue, related_name="proposals", on_delete=models.PROTECT)
    active = models.BooleanField(_("Active"), default=True)
    created_at = models.DateTimeField(_("Create at"), auto_now_add=True)
    created_by = models.ForeignKey(settings.AUTH_USER_MODEL,
                                   related_name="proposals_created",
                                   on_delete=models.PROTECT,
                                   verbose_name=_("Created by"))
    type = models.PositiveIntegerField(_("Type"), choices=ProposalType.CHOICES, default=ProposalType.GENERAL)
    types = ProposalType

    title = models.CharField(_("Title"), max_length=800)
    content = HTMLField(_("Details"), null=True, blank=True)

    status = models.IntegerField(choices=ProposalStatus.choices,
                                 default=ProposalStatus.IN_DISCUSSION)
    statuses = ProposalStatus

    decided_at_meeting = models.ForeignKey('meetings.Meeting', null=True,
                                           blank=True, on_delete=models.PROTECT)
    assigned_to = models.CharField(_("Assigned to"), max_length=200,
                                   null=True, blank=True)
    assigned_to_user = models.ForeignKey(settings.AUTH_USER_MODEL,
                                         verbose_name=_("Assigned to user"),
                                         null=True, blank=True,
                                         on_delete=models.PROTECT,
                                         related_name="proposals_assigned")
    due_by = models.DateField(_("Due by"), null=True, blank=True)
    task_completed = models.BooleanField(_("Completed"), default=False)
    votes_pro = models.PositiveIntegerField(_("Votes pro"), null=True,
                                            blank=True)
    votes_con = models.PositiveIntegerField(_("Votes con"), null=True,
                                            blank=True)
    community_members = models.PositiveIntegerField(_("Community members"),
                                                    null=True, blank=True)
    tags = TaggableManager(_("Tags"), blank=True)
    register_board_votes = models.BooleanField(default=False)

    class Meta:
        verbose_name = _("Proposal")
        verbose_name_plural = _("Proposals")

    def __str__(self):
        return self.title

    @property
    def is_open(self):
        return self.decided_at_meeting is None

    @property
    def decided(self):
        return self.status != ProposalStatus.IN_DISCUSSION

    @property
    def can_vote(self):
        """ Returns True if the proposal, issue and meeting are open """
        return self.is_open and self.issue.is_current

    @property
    def has_votes(self):
        """ Returns True if the proposal has any vote """
        return self.votes_con or self.votes_pro

    @property
    def has_arguments(self):
        return ProposalVoteArgument.objects.filter(proposal_vote__proposal=self).exists()

    @property
    def can_straw_vote(self):
        return self.status == ProposalStatus.IN_DISCUSSION and self.issue.can_straw_vote and self.issue.voteable

    @property
    def can_show_straw_votes(self):
        return self.has_votes and \
               (not self.issue.is_upcoming or \
                not self.issue.community.upcoming_meeting_is_published or \
                self.issue.community.straw_vote_ended)

    def get_comments(self):
        return self.proposal_comments.all().order_by('-created_at')

    def get_straw_results(self, meeting_id=None):
        """ get straw voting results registered for the given meeting """
        if meeting_id:
            try:
                res = VoteResult.objects.get(proposal=self,
                                             meeting_id=meeting_id)
            except VoteResult.DoesNotExist:
                return None
            return res
        else:
            if self.issue.is_upcoming and self.issue.community.straw_vote_ended:
                return self
            else:
                try:
                    res = VoteResult.objects.filter(proposal=self) \
                        .latest('meeting__held_at')
                    return res
                except VoteResult.DoesNotExist:
                    return None

    def board_vote_by_member(self, user_id):
        try:
            vote = ProposalVoteBoard.objects.get(user_id=user_id,
                                                 proposal=self)
            return vote.value
        except ProposalVoteBoard.DoesNotExist:
            return None

    @property
    def board_vote_result(self):
        total_votes = 0
        votes_dict = {'sums': {}, 'total': total_votes, 'per_user': {}}
        pro_count = 0
        con_count = 0
        neut_count = 0

        users = self.issue.community.upcoming_meeting_participants.all()
        for u in users:
            vote = ProposalVoteBoard.objects.filter(proposal=self, user=u)
            if vote.exists():
                votes_dict['per_user'][u] = vote[0].value
                if vote[0].value == 1:
                    pro_count += 1
                    total_votes += 1
                elif vote[0].value == -1:
                    con_count += 1
                    total_votes += 1
                elif vote[0].value == 0:
                    neut_count += 1

            else:
                votes_dict['per_user'][u] = 0
                neut_count += 1

        votes_dict['sums']['pro_count'] = pro_count
        votes_dict['sums']['con_count'] = con_count
        votes_dict['sums']['neut_count'] = neut_count
        votes_dict['total'] = total_votes
        return votes_dict

    def do_votes_summation(self, members_count):

        pro_votes = ProposalVote.objects.filter(proposal=self,
                                                value=ProposalVoteValue.PRO).count()
        con_votes = ProposalVote.objects.filter(proposal=self,
                                                value=ProposalVoteValue.CON).count()
        self.votes_pro = pro_votes
        self.votes_con = con_votes
        self.community_members = members_count
        self.save()

    def is_task(self):
        return self.type == ProposalType.TASK

    @models.permalink
    def get_absolute_url(self):
        return ("proposal", (str(self.issue.community.pk), str(self.issue.pk),
                             str(self.pk)))

    @models.permalink
    def get_email_vote_url(self):
        return ("vote_on_proposal", (str(self.issue.community.pk), str(self.pk)))

    @models.permalink
    def get_edit_url(self):
        return (
            "proposal_edit",
            (str(self.issue.community.pk), str(self.issue.pk),
             str(self.pk)))

    @models.permalink
    def get_edit_task_url(self):
        return ("proposal_edit_task",
                (str(self.issue.community.pk), str(self.issue.pk),
                 str(self.pk)))

    @models.permalink
    def get_delete_url(self):
        return (
            "proposal_delete",
            (str(self.issue.community.pk), str(self.issue.pk),
             str(self.pk)))

    def get_status_class(self):
        if self.status == self.statuses.ACCEPTED:
            return "accepted"
        if self.status == self.statuses.REJECTED:
            return "rejected"
        return ""

    def enforce_confidential_rules(self):
        # override `enforce_confidential_rules` on ConfidentialMixin
        # for the special logic required for Proposal objects
        if self.confidential_reason is None:
            if self.issue.is_confidential is True:
                self.is_confidential = True
            else:
                self.is_confidential = False
        else:
            self.is_confidential = True

    @property
    def arguments_for(self):
        return sorted(
            ProposalVoteArgument.objects.filter(proposal_vote__in=self.votes.filter(value=ProposalVoteValue.PRO)),
            key=lambda a: a.argument_score, reverse=True)

    @property
    def arguments_against(self):
        return sorted(
            ProposalVoteArgument.objects.filter(proposal_vote__in=self.votes.filter(value=ProposalVoteValue.CON)),
            key=lambda a: a.argument_score, reverse=True)

    @property
    def elegantly_interleaved_for_and_against_arguments(self):
        if not self.arguments_for:
            return list(self.arguments_against)
        if not self.arguments_against:
            return list(self.arguments_for)
        a = list(self.arguments_against)
        b = list(self.arguments_for)
        b, a = sorted((a, b), key=len)
        len_ab = len(a) + len(b)
        groups = groupby(((a[len(a) * i // len_ab], b[len(b) * i // len_ab]) for i in range(len_ab)),
                         key=lambda x: x[0])
        return [j[i] for k, g in groups for i, j in enumerate(g)]
예제 #5
0
class Reference(UIDMixin):
    issue = models.ForeignKey(Issue, related_name="references", on_delete=models.CASCADE)
    active = models.BooleanField(default=True)
    created_at = models.DateTimeField(_("Created at"), auto_now_add=True)
    created_by = models.ForeignKey(settings.AUTH_USER_MODEL,
                                   verbose_name=_("Created by"),
                                   on_delete=models.CASCADE,
                                   related_name="reference_created")
    meeting = models.ForeignKey('meetings.Meeting', null=True, blank=True, on_delete=models.CASCADE)
    version = models.PositiveIntegerField(default=1)
    last_edited_at = models.DateTimeField(_("Last Edited at"), auto_now_add=True)
    last_edited_by = models.ForeignKey(
        settings.AUTH_USER_MODEL, verbose_name=_("Created by"),
        on_delete=models.CASCADE,
        related_name="references_last_edited", null=True, blank=True)
    reference = HTMLField(_("Reference"))

    @property
    def is_confidential(self):
        return self.issue.is_confidential

    class Meta:
        ordering = ('created_at',)
        verbose_name = _("Reference")
        verbose_name_plural = _("References")

    @property
    def is_open(self):
        return self.meeting_id is None

    def update_reference(self, expected_version, author, reference):
        """ creates a new revision and updates current comment """
        if self.version != expected_version:
            return False

        reference = enhance_html(reference.strip())

        if self.reference == reference:
            return True

        with transaction.atomic():
            ReferenceRevision.objects.create(reference=self,
                                             version=expected_version,
                                             created_at=self.created_at,
                                             created_by=self.created_by,
                                             content=self.reference)
            self.version += 1
            self.last_edited_at = timezone.now()
            self.last_edited_by = author
            self.reference = reference
            self.save()

        return True

    @models.permalink
    def get_delete_url(self):
        return "delete_reference", (self.issue.community.id, self.id)

    @models.permalink
    def get_edit_url(self):
        return "edit_reference", (self.issue.community.id, self.id)

    @models.permalink
    def get_absolute_url(self):
        return "issue", (str(self.issue.community.pk), str(self.issue.pk),)
예제 #6
0
class Community(UIDMixin):
    name = models.CharField(max_length=200, verbose_name=_("Name"))
    is_public = models.BooleanField(_("Public community"),
                                    default=False,
                                    db_index=True)
    logo = models.ImageField(_("Community logo"),
                             upload_to='community_logo',
                             blank=True,
                             null=True)
    official_identifier = models.CharField(_("Community identifier"),
                                           max_length=300,
                                           blank=True,
                                           null=True)

    upcoming_meeting_started = models.BooleanField(_("Meeting started"),
                                                   default=False)
    upcoming_meeting_title = models.CharField(_("Upcoming meeting title"),
                                              max_length=300,
                                              null=True,
                                              blank=True)
    upcoming_meeting_scheduled_at = models.DateTimeField(
        _("Upcoming meeting scheduled at"), blank=True, null=True)
    upcoming_meeting_location = models.CharField(
        _("Upcoming meeting location"), max_length=300, null=True, blank=True)
    upcoming_meeting_comments = HTMLField(_("Upcoming meeting background"),
                                          null=True,
                                          blank=True)

    upcoming_meeting_participants = models.ManyToManyField(
        settings.AUTH_USER_MODEL,
        blank=True,
        related_name="+",
        verbose_name=_("Participants in upcoming meeting"))

    upcoming_meeting_guests = models.TextField(
        _("Guests in upcoming meeting"),
        null=True,
        blank=True,
        help_text=_("Enter each guest in a separate line"))

    upcoming_meeting_version = models.IntegerField(
        _("Upcoming meeting version"), default=0)

    upcoming_meeting_is_published = models.BooleanField(
        _("Upcoming meeting is published"), default=False)

    upcoming_meeting_published_at = models.DateTimeField(
        _("Upcoming meeting published at"), blank=True, null=True)

    upcoming_meeting_summary = HTMLField(_("Upcoming meeting summary"),
                                         null=True,
                                         blank=True)

    board_name = models.CharField(_("Board name"),
                                  default=_("Board"),
                                  max_length=200)

    straw_voting_enabled = models.BooleanField(_("Straw voting enabled"),
                                               default=False)

    issue_ranking_enabled = models.BooleanField(
        _("Issue ranking votes enabled"), default=False)

    voting_ends_at = models.DateTimeField(_("Straw Vote ends at"),
                                          null=True,
                                          blank=True)

    referendum_started = models.BooleanField(_("Referendum started"),
                                             default=False)

    referendum_started_at = models.DateTimeField(_("Referendum started at"),
                                                 null=True,
                                                 blank=True)

    referendum_ends_at = models.DateTimeField(_("Referendum ends at"),
                                              null=True,
                                              blank=True)

    default_quorum = models.PositiveSmallIntegerField(_("Default quorum"),
                                                      null=True,
                                                      blank=True)

    allow_links_in_emails = models.BooleanField(_("Allow links inside emails"),
                                                default=True)

    email_invitees = models.BooleanField(_("Send mails to invitees"),
                                         default=False)

    register_missing_board_members = models.BooleanField(
        _("Register missing board members"), default=False)

    inform_system_manager = models.BooleanField(_('Inform System Manager'),
                                                default=False)

    no_meetings_community = models.BooleanField(
        _('Community without meetings?'), default=False)

    class Meta:
        verbose_name = _("Community")
        verbose_name_plural = _("Communities")

    def __unicode__(self):
        return self.name

    @models.permalink
    def get_absolute_url(self):
        return "community", (str(self.pk), )

    @models.permalink
    def get_upcoming_absolute_url(self):
        return "community", (str(self.pk), )

    def upcoming_issues(self, user=None, community=None, upcoming=True):
        l = issues_models.IssueStatus.IS_UPCOMING if upcoming else \
            issues_models.IssueStatus.NOT_IS_UPCOMING

        if self.issues.all():
            rv = self.issues.object_access_control(
                user=user, community=community).filter(
                    active=True,
                    status__in=(l)).order_by('order_in_upcoming_meeting')
        else:
            rv = None
        return rv

    def available_issues(self, user=None, community=None):
        if self.issues.all():
            rv = self.issues.object_access_control(
                user=user, community=community).filter(
                    active=True,
                    status=issues_models.IssueStatus.OPEN).order_by(
                        '-created_at')
        else:
            rv = None
        return rv

    def available_issues_by_rank(self):
        return self.issues.filter(
            active=True,
            status=issues_models.IssueStatus.OPEN).order_by('order_by_votes')

    def issues_ready_to_close(self, user=None, community=None):
        if self.upcoming_issues(user=user, community=community):
            rv = self.upcoming_issues(user=user, community=community).filter(
                proposals__active=True,
                proposals__decided_at_meeting=None,
                proposals__status__in=[
                    ProposalStatus.ACCEPTED, ProposalStatus.REJECTED
                ])
        else:
            rv = None
        return rv

    def get_board_name(self):
        return self.board_name or _('Board')

    def get_members(self):
        return OCUser.objects.filter(memberships__community=self)

    def meeting_participants(self):

        meeting_participants = {
            'chairmen': [],
            'board': [],
            'members': [],
        }

        board_ids = [m.user.id for m in self.memberships.board()]

        for u in self.upcoming_meeting_participants.all():
            if u.id in board_ids:
                if u.get_default_group(self) == DefaultGroups.CHAIRMAN:
                    meeting_participants['chairmen'].append(u)
                else:
                    meeting_participants['board'].append(u)
            else:
                meeting_participants['members'].append(u)

        # doing it simply like this, as I'd need to refactor models
        # just to order in the way that is now required.
        for index, item in enumerate(meeting_participants['board']):
            if item.get_default_group(self) == DefaultGroups.MEMBER:
                meeting_participants['board'].insert(
                    0, meeting_participants['board'].pop(index))

        return meeting_participants

    def previous_members_participations(self):
        participations = MeetingParticipant.objects.filter( \
            default_group_name=DefaultGroups.MEMBER,
            meeting__community=self) \
            .order_by('-meeting__held_at')

        return list(set([p.user for p in participations]) - \
                    set(self.upcoming_meeting_participants.all()))

    def previous_guests_participations(self):
        guests_list = Meeting.objects.filter(community=self) \
            .values_list('guests', flat=True)

        prev_guests = set()
        upcoming_guests = self.upcoming_meeting_guests or ' '
        for guest in guests_list:
            if guest:
                prev_guests.update(guest.splitlines())
        prev_guests.difference_update(upcoming_guests.splitlines())
        return prev_guests

    def get_board_members(self):
        board_memberships = Membership.objects.filter(community=self) \
            .exclude(default_group_name=DefaultGroups.MEMBER)

        # doing it simply like this, as I'd need to refactor models
        # just to order in the way that is now required.
        board = [m.user for m in board_memberships]
        for index, item in enumerate(board):
            if item.get_default_group(self) == DefaultGroups.MEMBER:
                board.insert(0, board.pop(index))

        return board

    def get_board_count(self):
        return len(self.get_board_members())

    def get_none_board_members(self):
        return Membership.objects.filter(
            community=self, default_group_name=DefaultGroups.MEMBER)

    def get_guest_list(self):
        if not self.upcoming_meeting_guests:
            return []
        return filter(
            None,
            [s.strip() for s in self.upcoming_meeting_guests.splitlines()])

    def full_participants(self):
        guests_count = len(self.upcoming_meeting_guests.splitlines()) \
            if self.upcoming_meeting_guests else 0
        return guests_count + self.upcoming_meeting_participants.count()

    @property
    def straw_vote_ended(self):
        if not self.upcoming_meeting_is_published:
            return True
        if not self.voting_ends_at:
            return False
        time_till_close = self.voting_ends_at - timezone.now()
        return time_till_close.total_seconds() < 0

    def has_straw_votes(self, user=None, community=None):
        if not self.straw_voting_enabled or self.straw_vote_ended:
            return False
        return self.upcoming_proposals_any({'is_open': True},
                                           user=user,
                                           community=community)

    def sum_vote_results(self, only_when_over=True):
        if not self.voting_ends_at:
            return
        time_till_close = self.voting_ends_at - timezone.now()
        if only_when_over and time_till_close.total_seconds() > 0:
            return

        proposals_to_sum = issues_models.Proposal.objects.filter(
            # votes_pro=None,
            status=ProposalStatus.IN_DISCUSSION,
            issue__status=IssueStatus.IN_UPCOMING_MEETING,
            issue__community_id=self.id)
        member_count = self.get_members().count()
        for prop in proposals_to_sum:
            prop.do_votes_summation(member_count)

    def _get_upcoming_proposals(self, user=None, community=None):
        proposals = []
        upcoming = self.upcoming_issues(user=user, community=community)
        if upcoming:
            for issue in upcoming:
                if issue.proposals.all():
                    proposals.extend(
                        [p for p in issue.proposals.all() if p.active])
        return proposals

    def upcoming_proposals_any(self, prop_dict, user=None, community=None):
        """ test multiple properties against proposals belonging to the upcoming meeting
            return True if any of the proposals passes the tests
        """
        proposals = self._get_upcoming_proposals(user=user,
                                                 community=community)
        test_attrs = lambda p: [
            getattr(p, k) == val for k, val in prop_dict.items()
        ]
        for p in proposals:
            if all(test_attrs(p)):
                return True
        return False

    def _register_absents(self, meeting, meeting_participants):
        board_members = [mm.user for mm in Membership.objects.board() \
            .filter(community=self, user__is_active=True)]
        absents = set(board_members) - set(meeting_participants)
        ordinal_base = len(meeting_participants)
        for i, a in enumerate(absents):
            try:
                mm = a.memberships.get(community=self)
            except Membership.DoesNotExist:
                mm = None
            MeetingParticipant.objects.create(
                meeting=meeting,
                user=a,
                display_name=a.display_name,
                ordinal=ordinal_base + i,
                is_absent=True,
                default_group_name=mm.default_group_name if mm else None)

    def close_meeting(self, m, user, community):
        """
        Creates a :model:`meetings.Meeting` instance, with corresponding
        :model:`meetings.AgenddItem`s.

        Optionally changes statuses for :model:`issues.Issue`s and
        :model:`issues.Proposal`s.
        """

        with transaction.commit_on_success():
            m.community = self
            m.created_by = user
            m.title = self.upcoming_meeting_title
            m.scheduled_at = (self.upcoming_meeting_scheduled_at
                              or timezone.now())
            m.location = self.upcoming_meeting_location
            m.comments = self.upcoming_meeting_comments
            m.guests = self.upcoming_meeting_guests
            m.summary = self.upcoming_meeting_summary

            m.save()

            self.upcoming_meeting_started = False
            self.upcoming_meeting_title = None
            self.upcoming_meeting_scheduled_at = None
            self.upcoming_meeting_location = None
            self.upcoming_meeting_comments = None
            self.upcoming_meeting_summary = None
            self.upcoming_meeting_version = 0
            self.upcoming_meeting_is_published = False
            self.upcoming_meeting_published_at = None
            self.upcoming_meeting_guests = None
            self.voting_ends_at = None
            self.save()

            for i, issue in enumerate(
                    self.upcoming_issues(user=user, community=community)):

                proposals = issue.proposals.filter(
                    active=True, decided_at_meeting=None).exclude(
                        status=ProposalStatus.IN_DISCUSSION)
                for p in proposals:
                    p.decided_at_meeting = m
                    p.save()

                for p in issue.proposals.all():
                    if p.votes_pro is not None:
                        try:
                            VoteResult.objects.create(
                                proposal=p,
                                meeting=m,
                                votes_pro=p.votes_pro,
                                votes_con=p.votes_con,
                                community_members=p.community_members)
                        except:
                            pass

                for c in issue.comments.filter(meeting=None):
                    c.meeting = m
                    c.save()

                ai = meetings_models.AgendaItem.objects.create(
                    meeting=m,
                    issue=issue,
                    order=i,
                    background=issue.abstract,
                    closed=issue.completed)

                issue.attachments.filter(active=True,
                                         agenda_item__isnull=True) \
                    .update(agenda_item=ai)
                issue.is_published = True
                issue.abstract = None

                if issue.completed:
                    issue.order_in_upcoming_meeting = None

                issue.save()
            meeting_participants = self.upcoming_meeting_participants.all()
            for i, p in enumerate(meeting_participants):
                try:
                    mm = p.memberships.get(community=self)
                except Membership.DoesNotExist:
                    mm = None

                MeetingParticipant.objects.create(
                    meeting=m,
                    ordinal=i,
                    user=p,
                    display_name=p.display_name,
                    default_group_name=mm.default_group_name if mm else None)

            self._register_absents(m, meeting_participants)
            self.upcoming_meeting_participants = []

        return m

    def draft_meeting(self):
        if self.upcoming_meeting_scheduled_at:
            held_at = self.upcoming_meeting_scheduled_at.date()
        else:
            held_at = None

        return {
            'id': '',
            'held_at': held_at,
        }

    def draft_agenda(self, payload):
        """ prepares a fake agenda item list for 'protocol_draft' template. """

        # payload should be a list of dicts. Each dict has these keys:
        #   * issue
        #   * proposals
        #
        # The values are querysets

        def as_agenda_item(obj):
            return {
                'issue':
                obj['issue'],
                'proposals':
                obj['proposals'].filter(
                    decided_at_meeting=None,
                    active=True).exclude(status=ProposalStatus.IN_DISCUSSION),
                'accepted_proposals':
                obj['proposals'].filter(decided_at_meeting=None,
                                        active=True,
                                        status=ProposalStatus.ACCEPTED),
                'rejected_proposals':
                obj['proposals'].filter(decided_at_meeting=None,
                                        active=True,
                                        status=ProposalStatus.REJECTED),
                'comments':
                obj['issue'].comments.filter(meeting=None, active=True),
                'attachments':
                obj['issue'].current_attachments()
            }

        return [as_agenda_item(x) for x in payload]
예제 #7
0
class Community(UIDMixin):

    name = models.CharField(max_length=200, verbose_name=_("Name"))
    is_public = models.BooleanField(_("Public Community"),
                                    default=False,
                                    db_index=True)
    logo = models.ImageField(upload_to='community_logo',
                             verbose_name=_("Community Logo"),
                             blank=True,
                             null=True)
    official_identifier = models.CharField(
        max_length=300,
        verbose_name=_("Community Identifier"),
        blank=True,
        null=True)

    upcoming_meeting_started = models.BooleanField(_("Meeting started"),
                                                   default=False)
    upcoming_meeting_title = models.CharField(_("Upcoming meeting title"),
                                              max_length=300,
                                              null=True,
                                              blank=True)
    upcoming_meeting_scheduled_at = models.DateTimeField(
        _("Upcoming meeting scheduled at"), blank=True, null=True)
    upcoming_meeting_location = models.CharField(
        _("Upcoming meeting location"), max_length=300, null=True, blank=True)
    upcoming_meeting_comments = HTMLField(_("Upcoming meeting background"),
                                          null=True,
                                          blank=True)

    upcoming_meeting_participants = models.ManyToManyField(
        settings.AUTH_USER_MODEL,
        blank=True,
        related_name="+",
        verbose_name=_("Participants in upcoming meeting"))

    upcoming_meeting_guests = models.TextField(
        _("Guests in upcoming meeting"),
        null=True,
        blank=True,
        help_text=_("Enter each guest in a separate line"))

    upcoming_meeting_version = models.IntegerField(
        _("Upcoming meeting version"), default=0)

    upcoming_meeting_is_published = models.BooleanField(
        _("Upcoming meeting is published"), default=False)
    upcoming_meeting_published_at = models.DateTimeField(
        _("Upcoming meeting published at"), blank=True, null=True)

    upcoming_meeting_summary = HTMLField(_("Upcoming meeting summary"),
                                         null=True,
                                         blank=True)
    board_name = models.CharField(_("Board Name"),
                                  max_length=200,
                                  null=True,
                                  blank=True)

    class Meta:
        verbose_name = _("Community")
        verbose_name_plural = _("Communities")

    def __unicode__(self):
        return self.name

    @models.permalink
    def get_absolute_url(self):
        return "community", (str(self.pk), )

    @models.permalink
    def get_upcoming_absolute_url(self):
        return "community", (str(self.pk), )

    def upcoming_issues(self, upcoming=True):
        l = issues_models.IssueStatus.IS_UPCOMING if upcoming else \
            issues_models.IssueStatus.NOT_IS_UPCOMING
        return self.issues.filter(
            active=True, status__in=(l)).order_by('order_in_upcoming_meeting')

    def available_issues(self):
        return self.issues.filter(
            active=True,
            status=issues_models.IssueStatus.OPEN).order_by('created_at')

    def issues_ready_to_close(self):
        return self.upcoming_issues().filter(
            proposals__active=True,
            proposals__decided_at_meeting=None,
            proposals__status__in=[
                ProposalStatus.ACCEPTED, ProposalStatus.REJECTED
            ])

    def get_board_name(self):
        return self.board_name or _('Board')

    def get_members(self):
        return OCUser.objects.filter(memberships__community=self)

    def get_guest_list(self):
        if not self.upcoming_meeting_guests:
            return []
        return filter(
            None,
            [s.strip() for s in self.upcoming_meeting_guests.splitlines()])

    def send_mail(self, template, sender, send_to, data=None, base_url=None):

        if not base_url:
            base_url = settings.HOST_URL

        d = data.copy() if data else {}

        d.update({
            'base_url': base_url,
            'community': self,
            'LANGUAGE_CODE': settings.LANGUAGE_CODE,
            'MEDIA_URL': settings.MEDIA_URL,
            'STATIC_URL': settings.STATIC_URL,
        })

        subject = render_to_string("emails/%s_title.txt" % template, d).strip()

        message = render_to_string("emails/%s.txt" % template, d)
        html_message = render_to_string("emails/%s.html" % template, d)
        from_email = "%s <%s>" % (self.name, settings.FROM_EMAIL)

        recipient_list = set([sender.email])

        if send_to == SendToOption.ALL_MEMBERS:
            recipient_list.update(
                list(self.memberships.values_list('user__email', flat=True)))
        elif send_to == SendToOption.BOARD_ONLY:
            recipient_list.update(
                list(self.memberships.board().values_list('user__email',
                                                          flat=True)))
        elif send_to == SendToOption.ONLY_ATTENDEES:
            recipient_list.update(
                list(
                    self.upcoming_meeting_participants.values_list('email',
                                                                   flat=True)))

        logger.info("Sending agenda to %d users" % len(recipient_list))

        send_mails(from_email, recipient_list, subject, message, html_message)

        return len(recipient_list)

    def close_meeting(self, m, user):

        with transaction.commit_on_success():
            m.community = self
            m.created_by = user
            m.title = self.upcoming_meeting_title
            m.scheduled_at = (self.upcoming_meeting_scheduled_at
                              or timezone.now())
            m.location = self.upcoming_meeting_location
            m.comments = self.upcoming_meeting_comments
            m.guests = self.upcoming_meeting_guests
            m.summary = self.upcoming_meeting_summary

            m.save()

            self.upcoming_meeting_started = False
            self.upcoming_meeting_title = None
            self.upcoming_meeting_scheduled_at = None
            self.upcoming_meeting_location = None
            self.upcoming_meeting_comments = None
            self.upcoming_meeting_summary = None
            self.upcoming_meeting_version = 0
            self.upcoming_meeting_is_published = False
            self.upcoming_meeting_published_at = None
            self.upcoming_meeting_guests = None
            self.save()

            for i, issue in enumerate(self.upcoming_issues()):

                proposals = issue.proposals.filter(
                    active=True, decided_at_meeting=None).exclude(
                        status=ProposalStatus.IN_DISCUSSION)
                for p in proposals:
                    p.decided_at_meeting = m
                    p.save()

                for c in issue.comments.filter(active=True, meeting=None):
                    c.meeting = m
                    c.save()

                meetings_models.AgendaItem.objects.create(
                    meeting=m, issue=issue, order=i, closed=issue.completed)

                issue.is_published = True

                if issue.completed:
                    issue.status = issue.statuses.ARCHIVED
                    issue.order_in_upcoming_meeting = None

                issue.save()

            for i, p in enumerate(self.upcoming_meeting_participants.all()):

                try:
                    mm = p.memberships.get(community=self)
                except Membership.DoesNotExist:
                    mm = None

                MeetingParticipant.objects.create(
                    meeting=m,
                    ordinal=i,
                    user=p,
                    display_name=p.display_name,
                    default_group_name=mm.default_group_name if mm else None)

            self.upcoming_meeting_participants = []

        return m

    def draft_agenda(self):
        """ prepares a fake agenda item list for 'protocol_draft' template. """
        def as_agenda_item(issue):
            return {
                'issue':
                issue,
                'proposals':
                issue.proposals.filter(
                    decided_at_meeting=None,
                    active=True).exclude(status=ProposalStatus.IN_DISCUSSION),
                'accepted_proposals':
                issue.proposals.filter(decided_at_meeting=None,
                                       active=True,
                                       status=ProposalStatus.ACCEPTED),
                'rejected_proposals':
                issue.proposals.filter(decided_at_meeting=None,
                                       active=True,
                                       status=ProposalStatus.REJECTED),
                'comments':
                issue.comments.filter(meeting=None, active=True),
            }

        return [
            as_agenda_item(x)
            for x in self.issues.filter(status__in=IssueStatus.IS_UPCOMING)
        ]
예제 #8
0
class Community(UIDMixin):

    name = models.CharField(max_length=200, verbose_name=_("Name"))
    is_public = models.BooleanField(_("Public Community"), default=False,
                                    db_index=True)
    logo = models.ImageField(upload_to='community_logo', verbose_name=_("Community Logo"), blank=True, null=True)
    official_identifier = models.CharField(max_length=300, verbose_name=_("Community Identifier"), blank=True, null=True)

    upcoming_meeting_started = models.BooleanField(
                                        _("Meeting started"),
                                        default=False)
    upcoming_meeting_title = models.CharField(
                                         _("Upcoming meeting title"),
                                         max_length=300, null=True,
                                         blank=True)
    upcoming_meeting_scheduled_at = models.DateTimeField(
                                        _("Upcoming meeting scheduled at"),
                                        blank=True, null=True)
    upcoming_meeting_location = models.CharField(
                                         _("Upcoming meeting location"),
                                         max_length=300, null=True,
                                         blank=True)
    upcoming_meeting_comments = HTMLField(_("Upcoming meeting background"),
                                          null=True, blank=True)

    upcoming_meeting_participants = models.ManyToManyField(
                                      settings.AUTH_USER_MODEL,
                                      blank=True,
                                      related_name="+",
                                      verbose_name=_(
                                         "Participants in upcoming meeting"))

    upcoming_meeting_guests = models.TextField(_("Guests in upcoming meeting"),
                           null=True, blank=True,
                           help_text=_("Enter each guest in a separate line"))

    upcoming_meeting_version = models.IntegerField(
                                   _("Upcoming meeting version"), default=0)

    upcoming_meeting_is_published = models.BooleanField(
                                        _("Upcoming meeting is published"),
                                        default=False)
    upcoming_meeting_published_at = models.DateTimeField(
                                        _("Upcoming meeting published at"),
                                        blank=True, null=True)

    upcoming_meeting_summary = HTMLField(_("Upcoming meeting summary"),
                                         null=True, blank=True)
    board_name = models.CharField(_("Board Name"), max_length=200,
                                  null=True, blank=True)

    class Meta:
        verbose_name = _("Community")
        verbose_name_plural = _("Communities")

    def __unicode__(self):
        return self.name

    @models.permalink
    def get_absolute_url(self):
        return "community", (str(self.pk),)

    @models.permalink
    def get_upcoming_absolute_url(self):
        return "community", (str(self.pk),)

    def upcoming_issues(self, upcoming=True):
        return self.issues.filter(active=True, is_closed=False,
            in_upcoming_meeting=upcoming).order_by('order_in_upcoming_meeting')

    def available_issues(self):
        return self.upcoming_issues(False)

    def issues_ready_to_close(self):
        return self.upcoming_issues().filter(
                                         proposals__is_accepted=True
                                     ).annotate(
                                        num_proposals=models.Count('proposals')
                                     )

    def get_board_name(self):
        return self.board_name or _('Board')

    def get_members(self):
        return OCUser.objects.filter(memberships__community=self)

    def get_guest_list(self):
        if not self.upcoming_meeting_guests:
            return []
        return filter(None, [s.strip() for s in self.upcoming_meeting_guests.splitlines()])

    def send_mail(self, template, sender, send_to, data=None, base_url=None):

        if not base_url:
            base_url = settings.HOST_URL

        d = data.copy() if data else {}

        d.update({
              'base_url': base_url,
              'community': self,
              'LANGUAGE_CODE': settings.LANGUAGE_CODE,
             })

        subject = render_to_string("emails/%s_title.txt" % template, d)

        message = render_to_string("emails/%s.txt" % template, d)
        html_message = render_to_string("emails/%s.html" % template, d)
        from_email = "%s <%s>" % (self.name, settings.FROM_EMAIL)

        recipient_list = set([sender.email])

        if send_to == SendToOption.ALL_MEMBERS:
            recipient_list.update(list(
                      self.memberships.values_list('user__email', flat=True)))
        elif send_to == SendToOption.BOARD_ONLY:
            recipient_list.update(list(
                        self.memberships.board().values_list('user__email', flat=True)))
        elif send_to == SendToOption.ONLY_ATTENDEES:
            recipient_list.update(list(
                       self.upcoming_meeting_participants.values_list(
                                                          'email', flat=True)))

        logger.info("Sending agenda to %d users" % len(recipient_list))

        send_mails(from_email, recipient_list, subject, message, html_message)

        return len(recipient_list)
예제 #9
0
class Issue(UIDMixin):
    active = models.BooleanField(default=True, verbose_name=_("Active"))
    community = models.ForeignKey(Community,
                                  verbose_name=_("Community"),
                                  related_name="issues")
    created_at = models.DateTimeField(auto_now_add=True,
                                      verbose_name=_("Created at"))
    created_by = models.ForeignKey(settings.AUTH_USER_MODEL,
                                   verbose_name=_("Create by"),
                                   related_name="issues_created")

    title = models.CharField(max_length=300, verbose_name=_("Title"))
    abstract = HTMLField(null=True, blank=True, verbose_name=_("Background"))
    content = HTMLField(null=True, blank=True, verbose_name=_("Content"))

    calculated_score = models.IntegerField(default=0,
                                           verbose_name=_("Calculated Score"))

    in_upcoming_meeting = models.BooleanField(_("In upcoming meeting"),
                                              default=False)
    order_in_upcoming_meeting = models.IntegerField(
        _("Order in upcoming meeting"), default=9999, null=True, blank=True)
    length_in_minutes = models.IntegerField(_("Length (in minutes)"),
                                            null=True,
                                            blank=True)

    completed = models.BooleanField(default=False,
                                    verbose_name=_("Discussion completed"))
    is_closed = models.BooleanField(default=False, verbose_name=_("Is close"))
    closed_at_meeting = models.ForeignKey('meetings.Meeting',
                                          null=True,
                                          blank=True,
                                          verbose_name=_("Closed at meeting"))

    class Meta:
        verbose_name = _("Issue")
        verbose_name_plural = _("Issues")

    def __unicode__(self):
        return self.title

    @models.permalink
    def get_edit_url(self):
        return ("issue_edit", (
            str(self.community.pk),
            str(self.pk),
        ))

    @models.permalink
    def get_delete_url(self):
        return ("issue_delete", (
            str(self.community.pk),
            str(self.pk),
        ))

    @models.permalink
    def get_absolute_url(self):
        return ("issue", (
            str(self.community.pk),
            str(self.pk),
        ))

    def accepted_proposals(self):
        return self.proposals.filter(is_accepted=True, active=True)

    def active_proposals(self):
        return self.proposals.filter(active=True)

    def active_comments(self):
        return self.comments.filter(active=True)
예제 #10
0
class Proposal(UIDMixin):
    issue = models.ForeignKey(Issue,
                              related_name="proposals",
                              verbose_name=_("Issue"))
    active = models.BooleanField(default=True, verbose_name=_("Active"))
    created_at = models.DateTimeField(auto_now_add=True,
                                      verbose_name=_("Create at"))
    created_by = models.ForeignKey(settings.AUTH_USER_MODEL,
                                   related_name="proposals_created",
                                   verbose_name=_("Created by"))
    type = models.PositiveIntegerField(choices=ProposalType.CHOICES,
                                       verbose_name=_("Type"))

    title = models.CharField(max_length=300, verbose_name=_("Title"))
    content = HTMLField(null=True, blank=True, verbose_name=_("Content"))

    is_accepted = models.BooleanField(_("Is accepted"), default=False)
    accepted_at = models.DateTimeField(_("Accepted at"), null=True, blank=True)
    assigned_to = models.CharField(_("Assigned to"),
                                   max_length=200,
                                   null=True,
                                   blank=True)
    assigned_to_user = models.ForeignKey(settings.AUTH_USER_MODEL,
                                         verbose_name=_("Assigned to user"),
                                         null=True,
                                         blank=True,
                                         related_name="proposals_assigned")
    due_by = models.DateField(null=True, blank=True, verbose_name=_("Due by"))

    votes = models.ManyToManyField(settings.AUTH_USER_MODEL,
                                   verbose_name=_("Votes"),
                                   blank=True,
                                   related_name="proposals",
                                   through="ProposalVote")

    objects = ProposalManager()

    class Meta:
        verbose_name = _("Proposal")
        verbose_name_plural = _("Proposals")

    def __unicode__(self):
        return self.title

    def is_task(self):
        return self.type == ProposalType.TASK

    @models.permalink
    def get_absolute_url(self):
        return ("proposal", (str(self.issue.community.pk), str(self.issue.pk),
                             str(self.pk)))

    @models.permalink
    def get_edit_url(self):
        return ("proposal_edit", (str(self.issue.community.pk),
                                  str(self.issue.pk), str(self.pk)))

    @models.permalink
    def get_edit_task_url(self):
        return ("proposal_edit_task", (str(self.issue.community.pk),
                                       str(self.issue.pk), str(self.pk)))

    @models.permalink
    def get_delete_url(self):
        return ("proposal_delete", (str(self.issue.community.pk),
                                    str(self.issue.pk), str(self.pk)))
예제 #11
0
class Proposal(UIDMixin):
    issue = models.ForeignKey(Issue,
                              related_name="proposals",
                              verbose_name=_("Issue"))
    active = models.BooleanField(_("Active"), default=True)
    created_at = models.DateTimeField(_("Create at"), auto_now_add=True)
    created_by = models.ForeignKey(settings.AUTH_USER_MODEL,
                                   related_name="proposals_created",
                                   verbose_name=_("Created by"))
    type = models.PositiveIntegerField(_("Type"), choices=ProposalType.CHOICES)
    types = ProposalType

    title = models.CharField(_("Title"), max_length=300)
    content = HTMLField(_("Content"), null=True, blank=True)

    status = models.IntegerField(choices=ProposalStatus.choices,
                                 default=ProposalStatus.IN_DISCUSSION)
    statuses = ProposalStatus

    decided_at_meeting = models.ForeignKey('meetings.Meeting',
                                           null=True,
                                           blank=True)
    assigned_to = models.CharField(_("Assigned to"),
                                   max_length=200,
                                   null=True,
                                   blank=True)
    assigned_to_user = models.ForeignKey(settings.AUTH_USER_MODEL,
                                         verbose_name=_("Assigned to user"),
                                         null=True,
                                         blank=True,
                                         related_name="proposals_assigned")
    due_by = models.DateField(_("Due by"), null=True, blank=True)

    votes = models.ManyToManyField(settings.AUTH_USER_MODEL,
                                   verbose_name=_("Votes"),
                                   blank=True,
                                   related_name="proposals",
                                   through="ProposalVote")

    objects = ProposalManager()

    class Meta:
        verbose_name = _("Proposal")
        verbose_name_plural = _("Proposals")

    def __unicode__(self):
        return self.title

    @property
    def is_open(self):
        return self.decided_at_meeting is None

    @property
    def can_vote(self):
        """ Returns True if the proposal, issue and meeting are open """
        print "xyz", self.id, self.is_open, self.issue.is_upcoming, \
                   self.issue.community.upcoming_meeting_started
        return self.is_open and self.issue.is_upcoming and \
                   self.issue.community.upcoming_meeting_started

    def is_task(self):
        return self.type == ProposalType.TASK

    @models.permalink
    def get_absolute_url(self):
        return ("proposal", (str(self.issue.community.pk), str(self.issue.pk),
                             str(self.pk)))

    @models.permalink
    def get_edit_url(self):
        return ("proposal_edit", (str(self.issue.community.pk),
                                  str(self.issue.pk), str(self.pk)))

    @models.permalink
    def get_edit_task_url(self):
        return ("proposal_edit_task", (str(self.issue.community.pk),
                                       str(self.issue.pk), str(self.pk)))

    @models.permalink
    def get_delete_url(self):
        return ("proposal_delete", (str(self.issue.community.pk),
                                    str(self.issue.pk), str(self.pk)))

    def get_status_class(self):
        if self.status == self.statuses.ACCEPTED:
            return "accepted"
        if self.status == self.statuses.REJECTED:
            return "rejected"
        return ""
        return ""
예제 #12
0
class Issue(UIDMixin):
    active = models.BooleanField(default=True, verbose_name=_("Active"))
    community = models.ForeignKey('communities.Community',
                                  verbose_name=_("Community"),
                                  related_name="issues")
    created_at = models.DateTimeField(auto_now_add=True,
                                      verbose_name=_("Created at"))
    created_by = models.ForeignKey(settings.AUTH_USER_MODEL,
                                   verbose_name=_("Create by"),
                                   related_name="issues_created")

    title = models.CharField(max_length=300, verbose_name=_("Title"))
    abstract = HTMLField(null=True, blank=True, verbose_name=_("Background"))
    content = HTMLField(null=True, blank=True, verbose_name=_("Content"))

    calculated_score = models.IntegerField(default=0,
                                           verbose_name=_("Calculated Score"))

    status = models.IntegerField(choices=IssueStatus.choices,
                                 default=IssueStatus.OPEN)
    statuses = IssueStatus

    order_in_upcoming_meeting = models.IntegerField(
        _("Order in upcoming meeting"), default=9999, null=True, blank=True)
    length_in_minutes = models.IntegerField(_("Length (in minutes)"),
                                            null=True,
                                            blank=True)

    completed = models.BooleanField(default=False,
                                    verbose_name=_("Discussion completed"))
    is_published = models.BooleanField(_("Is published to members"),
                                       default=False)

    class Meta:
        verbose_name = _("Issue")
        verbose_name_plural = _("Issues")
        ordering = ['order_in_upcoming_meeting', 'title']

    def __unicode__(self):
        return self.title

    @models.permalink
    def get_edit_url(self):
        return ("issue_edit", (
            str(self.community.pk),
            str(self.pk),
        ))

    @models.permalink
    def get_delete_url(self):
        return ("issue_delete", (
            str(self.community.pk),
            str(self.pk),
        ))

    @models.permalink
    def get_absolute_url(self):
        return ("issue", (
            str(self.community.pk),
            str(self.pk),
        ))

    def active_proposals(self):
        return self.proposals.filter(active=True)

    def new_comments(self):
        return self.comments.filter(active=True, meeting_id=None)

    def has_closed_parts(self):
        """ Should be able to be viewed """

    @property
    def is_upcoming(self):
        return self.status in IssueStatus.IS_UPCOMING

    @property
    def is_current(self):
        return self.status in IssueStatus.IS_UPCOMING and self.community.upcoming_meeting_started

    def changed_in_current(self):
        decided_at_current = self.proposals.filter(
            active=True,
            decided_at_meeting=None,
            status__in=[ProposalStatus.ACCEPTED, ProposalStatus.REJECTED])
        return decided_at_current or self.new_comments()

    @property
    def is_archived(self):
        return self.status == IssueStatus.ARCHIVED