Exemple #1
0
class InvalidModel(models.Model):
    state = FSMField(default='new')
    action = FSMField(default='no')

    @transition(source='new', target='no')
    def validate(self):
        pass
Exemple #2
0
class BlogPostWithExplicitState(models.Model):
    state = FSMField(default='new')
    approvement = FSMField(default='new')

    @transition(field=state, source='new', target='published')
    def publish(self):
        pass

    @transition(field=approvement, source='new', target='approved')
    def approve(self):
        pass

    @transition(field=approvement, source='new', target='declined')
    def decline(self):
        pass
Exemple #3
0
class BlogPost(models.Model):
    state = FSMField(default='new')

    @transition(source='new', target='published')
    def publish(self):
        pass

    @transition(source='published')
    def notify_all(self):
        pass

    @transition(source='published', target='hidden')
    def hide(self):
        pass

    @transition(source='new', target='removed')
    def remove(self):
        raise Exception('No rights to delete %s' % self)

    @transition(source=['published', 'hidden'], target='stolen')
    def steal(self):
        pass

    @transition(source='*', target='moderated')
    def moderate(self):
        pass
Exemple #4
0
class UserMessage(models.Model):
    message_type = models.CharField(max_length=80,
                                    verbose_name="Send as",
                                    choices=MESSAGE_TYPES,
                                    default=MESSAGE_TYPES[0])
    message_to = models.ForeignKey(settings.AUTH_USER_MODEL,
                                   related_name="to",
                                   verbose_name="To",
                                   blank=False)
    message_from = models.ForeignKey(settings.AUTH_USER_MODEL,
                                     blank=False,
                                     null=False,
                                     related_name="from+")
    subject = models.CharField(blank=True, null=True, max_length=200)
    message = models.TextField(blank=True)
    state = FSMField(default='pending')
    last_modified = models.DateTimeField(auto_now=True)

    class Meta:
        app_label = "signalbox"
        permissions = (("can_send_message",
                        "Can send email/SMS to participant."), )
        ordering = ['-last_modified']

    def __unicode__(self):
        return "%s from %s to %s" % (self.message_type, self.message_from,
                                     self.message_to)

    def _send_sms(self):

        number = TwilioNumber.objects.get(is_default_account=True)
        client = number.client()
        to_number = self.message_to.userprofile.mobile
        from_number = number.phone_number
        sms = client.sms.messages.create(to=to_number,
                                         from_=from_number,
                                         body=self.message)

        return sms

    def _send_email(self):
        mess = send_mail(self.subject,
                         self.message,
                         self.message_from.email, (self.message_to.email, ),
                         fail_silently=False)
        return mess

    @transition(field=state, source='pending', target='sent', save=True)
    def send(self):
        f = getattr(self, "_send_" +
                    self.message_type.lower())  # e.g. gets _send_sms()
        return f()
Exemple #5
0
class BlogPostWithConditions(models.Model):
    state = FSMField(default='new')

    def model_condition(self):
        return True

    def unmet_condition(self):
        return False

    @transition(source='new', target='published', conditions=[condition_func, model_condition])
    def publish(self):
        pass

    @transition(source='published', target='destroyed', conditions=[condition_func, unmet_condition])
    def destroy(self):
        pass
Exemple #6
0
class Profile(models.Model):

    user = models.OneToOneField(User)

    nickname = models.CharField(max_length=100, blank=True, null=True)
    nickname_slug = models.CharField(max_length=100, blank=True, null=True, db_index=True)
    nickname_state = FSMField(default='unset', protected=True)

    task_types = models.ManyToManyField(
        'TaskType',
        verbose_name='Which tasks would you like to help with?')
    wants_reviews = models.BooleanField(
        verbose_name="Help review other's tasks.",
        help_text="Reviewing other's tasks helps finish transcripts faster.",
        default=False)
    task_order = models.CharField(
        choices=TASK_ORDER_CHOICES,
        verbose_name='Which order would you like to receive tasks?',
        default='eager',
        max_length=10)

    def __unicode__(self):
        if self.nickname:
            fmt = '{self.nickname} ({self.user.username})'
        else:
            fmt = '{self.user.username}'
        return fmt.format(**locals())

    def get_absolute_url(self):
        pk = self.user.pk  # Line up URL with User pk, not Profile.
        return reverse('profiles:detail', kwargs=dict(pk=pk))

    @property
    def preferred_task_names(self):
        return set(v['name'] for v in self.task_types.values('name'))

    @transition(nickname_state, 'unset', 'set')
    def set_nickname(self, new_nickname):
        new_nickname = u' '.join(new_nickname.strip().split())
        if new_nickname == u'':
            raise ValueError('Nickname cannot be empty')
        new_slug = slugify(new_nickname)
        if Profile.objects.filter(nickname_slug=new_slug).exclude(pk=self.pk):
            raise ValueError('Nickname already in use by another user')
        else:
            self.nickname = new_nickname
            self.nickname_slug = new_slug
Exemple #7
0
class vmware_resource_lock(models.Model):
    state = FSMField(default='request')
    service_request_id = models.IntegerField()
    esx = models.ForeignKey(vmware_server, blank=True, null=True)
    cpu = models.IntegerField(blank=True, null=True)
    mem = models.IntegerField(blank=True, null=True)
    storage_type = models.ForeignKey(vmware_datastore_type,
                                     blank=True,
                                     null=True)
    disk_space = models.IntegerField(blank=True, null=True)
    data_store = models.ForeignKey(vmware_datastore, blank=True, null=True)
    vmware_manage_ip = models.ForeignKey(vmware_manage_ip,
                                         blank=True,
                                         null=True)
    vmware_service_ip = models.ForeignKey(vmware_service_ip,
                                          blank=True,
                                          null=True)
    status = models.ForeignKey(vmware_resource_lock_status)

    def __unicode__(self):
        return 'Service request id: %d, %s' % (self.service_request_id,
                                               self.status.status)

    def save(self, *args, **kwargs):
        return super(vmware_resource_lock, self).save(*args, **kwargs)

    #@transition(field=state,source='*',target='freeze',save=True)
    def FreezeResource(self, ChangeStatus=False):
        logger.info('freeze resource')
        try:
            flag=FreezeVMwareLockedResource(esx=self.esx,cpu=self.cpu,mem=self.mem,\
                               storage_type=self.storage_type,\
                               data_store=self.data_store,disk_space=self.disk_space,\
                               manage_ip=self.vmware_manage_ip,service_ip=self.vmware_service_ip)
            """
            if self.status!=vmware_resource_lock_status.objects.get(status='freeze') \
                                                            and ChangeStatus:
                self.status=vmware_resource_lock_status.objects.get(status='freeze')
                self.save()
            """
            return flag
        except Exception, e:
            logger.error('Error in Freezing resource. %s' % e)
            return False
Exemple #8
0
class RssFetch(models.Model):
    """A fetch of the RSS feed of a podcast.

    @startuml
    [*] --> not_fetched
    not_fetched --> fetching
    fetching --> fetched
    fetching --> failed
    not_fetched --> fetched
    fetched --> [*]
    failed --> [*]
    @enduml
    """

    podcast = models.ForeignKey(Podcast, related_name='fetches')
    created = CreationDateTimeField()
    fetched = models.DateTimeField(blank=True, null=True)
    body = models.BinaryField(blank=True, null=True)
    state = FSMField(default='not_fetched', protected=True)

    @transition(state, 'not_fetched', 'fetched', save=True)
    def load_rss(self, body=None, filename=None):
        if body is not None:
            self.body = body
        elif filename is not None:
            with open(filename, 'rb') as f:
                self.body = f.read()
        else:
            raise TypeError('Specify either `body` or `filename`.')
        self.fetched = datetime_now()

    @transition(state, 'not_fetched', 'fetching', save=True)
    def start(self):
        self.fetched = datetime_now()

    @transition(state, 'fetching', 'fetched', save=True)
    def finish(self, body):
        self.body = body

    @transition(state, 'fetching', 'failed', save=True)
    def fail(self):
        pass
Exemple #9
0
class ServiceRequest(models.Model):
    state = FSMField(default='submitted', blank=True, null=True)
    user = models.ForeignKey(User)
    submitter = models.CharField(max_length=50)
    description = models.TextField()
    request_type = models.ForeignKey(RequestType)
    request_parameter = models.CharField(max_length=1000)
    submit_time = models.DateTimeField(editable=False, blank=True, null=True)
    last_modify_time = models.DateTimeField(blank=True, null=True)
    request_status = models.ForeignKey(RequestStatus, )
    status_message = models.CharField(max_length=500, blank=True, null=True)
    approver = models.CharField(max_length=50, blank=True, null=True)

    def __unicode__(self):
        return "ID: %d,Submitter: %s, description: %s." % (
            self.id, self.submitter, self.description)

    def save(self, *args, **kwargs):
        if not self.id:
            self.submit_time = datetime.datetime.today()
            request_status = RequestStatus.objects.get(request_status_id=0)
            self.submitter = self.user.username
        self.last_modify_time = datetime.datetime.today()
        return super(ServiceRequest, self).save(*args, **kwargs)
Exemple #10
0
class Podcast(models.Model):
    """A podcast we want to have in our directory.

    approval_state
    --------------

    @startuml
    [*] --> not_approved
    not_approved --> owner_verified
    not_approved --> user_approved
    not_approved --> staff_approved
    user_approved --> staff_approved
    user_approved --> owner_verified
    staff_approved --> owner_verified
    @enduml
    """

    rss_url = models.URLField(max_length=512, unique=True)
    approval_state = FSMField(default='not_approved', protected=True)
    title = models.CharField(max_length=512, blank=True, null=True)
    link_url = models.URLField(max_length=512, blank=True, null=True)
    image_url = models.URLField(max_length=512, blank=True, null=True)
    provides_own_transcripts = models.BooleanField(
        default=False,
        help_text=
        "If True, episodes have external transcript set to the episode's link URL"
    )

    class Meta:
        ordering = ('title', 'rss_url')

    def __unicode__(self):
        return self.title or self.rss_url

    def get_absolute_url(self):
        if self.title:
            return reverse('podcasts:detail_slug',
                           kwargs=dict(pk=self.pk, slug=slugify(self.title)))
        else:
            return reverse('podcasts:detail', kwargs=dict(pk=self.pk))

    # approval_state
    # --------------

    @transition(approval_state,
                source='not_approved',
                target='user_approved',
                save=True)
    def user_approve(self, user, notes=None):
        TranscriptionApproval.objects.create(
            podcast=self,
            user=user,
            approval_type='user',
            notes=notes,
        )

    @transition(approval_state,
                source=['not_approved', 'user_approved'],
                target='staff_approved',
                save=True)
    def staff_approve(self, user, notes=None):
        TranscriptionApproval.objects.create(
            podcast=self,
            user=user,
            approval_type='staff',
            notes=notes,
        )

    @transition(approval_state,
                source=['not_approved', 'user_approved', 'staff_approved'],
                target='owner_verified',
                save=True)
    def owner_verify(self, user, notes=None):
        TranscriptionApproval.objects.create(
            podcast=self,
            user=user,
            approval_type='owner',
            notes=notes,
        )
Exemple #11
0
class Submission(models.Model):
    submitter = models.ForeignKey(DjangoUser)
    #accepted_by = models.ForeignKey(DjangoUser, ...
    editor = models.ForeignKey(
        DjangoUser,
        related_name='editor_for_set',
        blank=True,
        null=True,
        help_text="The editor assigned to this article submission.")
    article = models.OneToOneField(Article)
    date = models.DateTimeField(auto_now_add=True)
    state = FSMField(default=NEW)
    discussion = models.ForeignKey(Discussion)

    #discussion = models.TextField(blank=True, null=True) # replace this with a Discussion instance

    def state_label(self):
        return STATES[self.state]

    def state_description(self):
        # return magic.get(self.state, "does not compute")
        return "description of the current state here"

    def __getattr__(self, attr):
        # if article.submission.is_inreview(): print 'woo'
        if attr[:3] == 'is_':
            state = attr[3:]
            if state in STATE_SLUGS:
                return self.state == state
        raise AttributeError(
            'Attribute "%s" not found. This could be because __getattr__ has been overriden in models.Submission ...'
            % attr)

    def has_editor(self):
        return self.editor != None

    def has_reviewer(self):
        return self.reviewassignment_set.count() > 0

    @transition(source=[NEW, PRIVATE],
                target=SUBMITTED,
                conditions=[cond_article_has_submission_prerequisites])
    def submit(self):
        """Article has never been submitted, or has been submitted and
        then withdrawn before being assigned to an editor, and needs
        to be brought before the editor"""

    @transition(source=SUBMITTED, target=ACCEPTED)
    def accept_for_review(self):
        "Article has been accepted by an editor and is now awaiting reviewers to be assigned."

    @transition(source=ACCEPTED, target=ASSIGNED, conditions=[has_editor])
    def assign_to_editor(self):
        "Article has been accepted by the assigned editor. Editor must now choose reviewers."

    @transition(source=ASSIGNED, target=INREVIEW, conditions=[has_reviewer])
    def in_review(self):
        "Editor has chosen reviewers and made the article available to them"

    '''

    @transition(source=REVIEWED, target=INPRODUCTION)
    def in_production(self):
        "Editor accepts article for publication based on the reviewers and their own decision and has entered the production process."
    '''

    @transition(source=INREVIEW, target=INCOPYEDIT)
    def begin_copyedit(self):
        "Editor accepts article for publication based on the reviewers (and their own decision) and has entered the first part of the production process, copyedit."

    @transition(source=INCOPYEDIT, target=INREVISION)
    def article_changes_required(self):
        "Submission has been returned to the user for specific, minor, changes prior to proofing."

    @transition(source=INREVISION, target=INCOPYEDIT)
    def article_changes_submitted(self):
        "Author has completed the changes asked of them and has returned "

    @transition(source=INCOPYEDIT, target=INPROOFING)
    def begin_proofing(self):
        "Copyedit is complete and the submission is ready to enter proofing."

    @transition(source=INPROOFING, target=RELEASED)
    def release(self):
        "Submission has completed proofing and can now be released."

    #

    @transition(source=SUBMITTED, target=PRIVATE)
    def withdraw(self):
        "User has submitted an article but wishes to alter it before an accept/decline decision is made by the editor."

    '''
    @transition(source=ASSIGNED, target=PRIVATE)
    def pre_review_changes_required(self):
        "Article was accepted and assigned to an editor, however editor believes a major edit is required before being sent to peer review and has returned it to the user."
    '''
    '''
    @transition(source=PRIVATE, target=ASSIGNED, conditions=[has_editor])
    def resubmit(self):
        "Article was returned to the user for edits prior to peer review and must now be resubmitted"
    '''

    @transition(source=[SUBMITTED, INREVIEW], target=REFUSED)
    def refuse(self):
        """Article has been refused. It has been either: refused
        outright by editor or failed peer review or past peer
        review but refused by editor regardless."""

    def __unicode__(self):
        return U'submission %s' % self.id

    def __repr__(self):
        return U'<Submission "%s">' % self.article.title
Exemple #12
0
class ProtectedAccessModel(models.Model):
    status = FSMField(default='new', protected=True)

    @transition(source='new', target='published')
    def publish(self):
        pass
Exemple #13
0
class Document(models.Model):
    status = FSMField(default='new')

    @transition(source='new', target='published')
    def publish(self):
        pass
Exemple #14
0
class ClientCall(models.Model):
    'call from Twilio'
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)

    sid = models.CharField(max_length=34, db_index=True)
    bed_count = models.PositiveIntegerField(default=0)
    shelter = models.ForeignKey(Shelter, null=True)

    location_name = models.CharField(max_length=300, null=True, blank=True)
    client_name = models.CharField(max_length=300, null=True, blank=True)

    call_state = FSMField(default='welcome')

    # states from which more data can be collected
    COLLECT_DATA_STATES = [
        'welcome',
        'processed_location',
        'processed_name',
        'processed_bed_count',
    ]

    @transition(source='*', target='operator', save=True)
    def transfer_to_operator(self):
        '''transfer/connect to an operator, this class does not concern itself with further steps'''

    @transition(source='*', target='enqueued', save=True)
    def enqueue(self):
        '''"enqueue" this call (put on hold)'''

    @transition(source='enqueued', target='dequeued', save=True)
    def dequeue(self, url, method='GET'):
        '''"dequeue" this call (take off hold)'''
        site = Site.objects.get_current()
        client.calls.route(
            sid=self.sid,
            method=method,
            url=urljoin('http://' + site.domain, url)
        )

    @transition(source='dequeued', target='confirmed', save=True)
    def confirm(self):
        '''confirm shelter placement'''

    @transition(source='*', target='declined', save=True)
    def decline(self):
        '''decline shelter placement (no match found/rejection)'''

    @transition(source=COLLECT_DATA_STATES, target='requested_location', save=True)
    def request_location(self):
        '''request client location, via voice'''

    @transition(source='requested_location', target='processed_location', save=True)
    def process_location(self, request):
        '''process location from a given request'''
        # TODO: fill stub with appropriate GIS conversion
        self.location_name = request.POST.get('RecordingUrl', '')

    @transition(source=COLLECT_DATA_STATES, target='requested_name', save=True)
    def request_name(self):
        '''request client name, via voice'''

    @transition(source='requested_name', target='processed_name', save=True)
    def process_name(self, request):
        '''process name from a given request'''
        self.client_name = request.POST.get('RecordingUrl', '')

    @transition(source=COLLECT_DATA_STATES, target='requested_bed_count', save=True)
    def request_bed_count(self):
        '''request number of beds required'''

    @transition(source='requested_bed_count', target='processed_bed_count', save=True)
    def process_bed_count(self, request):
        '''process beds from a given request'''
        self.bed_count = int(request.POST['Digits'])
Exemple #15
0
class PostStatus(models.Model):
    """
    Keeps track of the status of posts for moderation purposes.
    """
    UNREVIEWED = 'unreviewed'
    USER_DELETED = 'user_deleted'
    FILTERED_SPAM = 'filtered_spam'
    FILTERED_HAM = 'filtered_ham'
    MARKED_SPAM = 'marked_spam'
    MARKED_HAM = 'marked_ham'

    AKISMET_MAX_SIZE = 1024 * 250

    post = models.OneToOneField(Post, db_index=True)
    state = FSMField(default=UNREVIEWED, db_index=True)
    topic = models.ForeignKey(Topic)  # Original topic
    forum = models.ForeignKey(Forum)  # Original forum
    user_agent = models.CharField(max_length=200, blank=True, null=True)
    referrer = models.CharField(max_length=200, blank=True, null=True)
    permalink = models.CharField(max_length=200, blank=True, null=True)

    objects = PostStatusManager()

    spam_category = None
    spam_forum = None
    spam_topic = None

    def _get_spam_dustbin(self):
        if self.spam_category is None:
            self.spam_category, _ = Category.objects.get_or_create(
                name=forum_settings.SPAM_CATEGORY_NAME)

        if self.spam_forum is None:
            self.spam_forum, _ = Forum.objects.get_or_create(
                category=self.spam_category,
                name=forum_settings.SPAM_FORUM_NAME)

        if self.spam_topic is None:
            filterbot = User.objects.get_by_natural_key("filterbot")
            self.spam_topic, _ = Topic.objects.get_or_create(
                forum=self.spam_forum,
                name=forum_settings.SPAM_TOPIC_NAME,
                user=filterbot)

        return (self.spam_topic, self.spam_forum)

    def _undelete_post(self):
        """
        If the post is in the spam dustbin, move it back to its original location.
        """
        spam_topic, spam_forum = self._get_spam_dustbin()
        post = self.post
        topic = self.topic
        head = post.topic.head

        if post == head:
            # Move the original topic back to the original forum (either from
            # the dustbin, or from the spam dustbin)
            topic.move_to(self.forum)

        if topic != post.topic:
            # If the post was moved from its original topic, put it back now that
            # the topic is in place.
            post.move_to(topic, delete_topic=False)

    def _delete_post(self):
        """
        Move the post to the spam dustbin.
        """
        spam_topic, spam_forum = self._get_spam_dustbin()
        post = self.post
        topic = self.topic
        head = topic.head

        if post == head:
            topic.move_to(spam_forum)
        else:
            post.move_to(spam_topic)

    def to_akismet_data(self):
        post = self.post
        topic = self.topic
        user = post.user
        user_ip = post.user_ip
        comment_author = user.username
        user_agent = self.user_agent
        referrer = self.referrer
        permalink = self.permalink
        comment_date_gmt = post.created.isoformat(' ')
        comment_post_modified_gmt = topic.created.isoformat(' ')

        return {
            'user_ip': user_ip,
            'user_agent': user_agent,
            'comment_author': comment_author,
            'referrer': referrer,
            'permalink': permalink,
            'comment_type': 'comment',
            'comment_date_gmt': comment_date_gmt,
            'comment_post_modified_gmt': comment_post_modified_gmt
        }

    def to_akismet_content(self):
        """
        Truncate the post body to the largest allowed string size. Use size, not
        length, since the Akismet server checks size, not length.
        """
        return self.post.body.encode('utf-8')[:self.AKISMET_MAX_SIZE].decode(
            'utf-8', 'ignore')

    def _comment_check(self):
        """
        Pass the associated post through Akismet if it's available. If it's not
        available return None. Otherwise return True or False.
        """
        if akismet_api is None:
            logger.warning("Skipping akismet check. No api.")
            return None

        data = self.to_akismet_data()
        content = self.to_akismet_content()
        is_spam = None

        try:
            is_spam = akismet_api.comment_check(content, data)
        except AkismetError as e:
            try:
                # try again, in case of timeout
                is_spam = akismet_api.comment_check(content, data)
            except Exception as e:
                logger.error("Error while checking Akismet",
                             exc_info=True,
                             extra={
                                 "post": self.post,
                                 "post_id": self.post.id,
                                 "content_length": len(content)
                             })
                is_spam = None
        except Exception as e:
            logger.error("Error while checking Akismet",
                         exc_info=True,
                         extra={
                             "post": self.post,
                             "post_id": self.post.id,
                             "content_length": len(content)
                         })
            is_spam = None

        return is_spam

    def _submit_comment(self, report_type):
        """
        Report this post to Akismet as spam or ham. Raises an exception if it
        fails. report_type is 'spam' or 'ham'. Used by report_spam/report_ham.
        """
        if akismet_api is None:
            logger.error("Can't submit to Akismet. No API.")
            return None

        data = self.to_akismet_data()
        content = self.to_akismet_content()

        if report_type == "spam":
            akismet_api.submit_spam(content, data)

        elif report_type == "ham":
            akismet_api.submit_ham(content, data)
        else:
            raise NotImplementedError(
                "You're trying to report an unsupported comment type.")

    def _submit_spam(self):
        """
        Report this post to Akismet as spam.
        """
        self._submit_comment("spam")

    def _submit_ham(self):
        """
        Report this post to Akismet as ham.
        """
        self._submit_comment("ham")

    def is_spam(self):
        """
        Condition used by the FSM. Return True if the Akismet API is available
        and returns a positive. Otherwise return False or None.
        """
        is_spam = self._comment_check()
        if is_spam is None:
            return False
        else:
            return is_spam

    def is_ham(self):
        """
        Inverse of is_spam.
        """
        is_spam = self._comment_check()
        if is_spam is None:
            return False
        else:
            return not is_spam

    @transition(field=state,
                source=UNREVIEWED,
                target=FILTERED_SPAM,
                save=True,
                conditions=[is_spam])
    def filter_spam(self):
        """
        Akismet detected this post is spam, move it to the dustbin and report it.
        """
        self._delete_post()

    @transition(field=state,
                source=UNREVIEWED,
                target=FILTERED_HAM,
                save=True,
                conditions=[is_ham])
    def filter_ham(self):
        """
        Akismet detected this post as ham. Don't do anything (except change state).
        """
        pass

    @transition(field=state,
                source=[UNREVIEWED, FILTERED_HAM, MARKED_HAM],
                target=USER_DELETED,
                save=True)
    def filter_user_deleted(self):
        """
        Post is not marked spam by akismet, but user has been globally deleted,
        putting this into the spam dusbin.
        """
        self._delete_post()

    @transition(field=state,
                source=[FILTERED_SPAM, MARKED_SPAM],
                target=MARKED_HAM,
                save=True)
    def mark_ham(self):
        """
        Either Akismet returned a false positive, or a moderator accidentally
        marked this as spam. Tell Akismet that this is ham, undelete it.
        """
        self._submit_ham()
        self._undelete_post()

    @transition(field=state,
                source=[FILTERED_HAM, MARKED_HAM],
                target=MARKED_SPAM,
                save=True)
    def mark_spam(self):
        """
        Akismet missed this, or a moderator accidentally marked it as ham. Tell
        Akismet that this is spam.
        """
        self._submit_spam()
        self._delete_post()

    @transition(field=state, source=USER_DELETED, target=UNREVIEWED, save=True)
    def filter_user_undeleted(self):
        """
        Post is not marked spam by akismet, but user has been globally deleted,
        putting this into the spam dusbin.
        """
        self._undelete_post()

    def review(self, certainly_spam=False):
        """
        Process this post, used by the manager and the spam-hammer. The
        ``certainly_spam`` argument is used to force mark as spam/delete the
        post, no matter what status Akismet returns.
        """
        if can_proceed(self.filter_spam):
            self.filter_spam()
        elif can_proceed(self.filter_ham):
            self.filter_ham()
            if certainly_spam:
                self.mark_spam()
        else:
            if certainly_spam:
                self._delete_post()
            logger.warn("Couldn't filter post.",
                        exc_info=True,
                        extra={
                            'post_id': self.post.id,
                            'content_length': len(self.post.body)
                        })
Exemple #16
0
class Course(models.Model):
    """Models a single course, e.g. the Math 101 course of 2002."""

    __metaclass__ = LocalizeModelBase

    state = FSMField(default='new', protected=True)

    semester = models.ForeignKey(Semester, verbose_name=_(u"semester"))

    name_de = models.CharField(max_length=1024,
                               verbose_name=_(u"name (german)"))
    name_en = models.CharField(max_length=1024,
                               verbose_name=_(u"name (english)"))
    name = Translate

    # type of course: lecture, seminar, project
    kind = models.CharField(max_length=1024, verbose_name=_(u"type"))

    # bachelor, master, d-school course
    degree = models.CharField(max_length=1024, verbose_name=_(u"degree"))

    # students that are allowed to vote
    participants = models.ManyToManyField(settings.AUTH_USER_MODEL,
                                          verbose_name=_(u"participants"),
                                          blank=True)
    participant_count = models.IntegerField(
        verbose_name=_(u"participant count"),
        blank=True,
        null=True,
        default=None)

    # students that already voted
    voters = models.ManyToManyField(settings.AUTH_USER_MODEL,
                                    verbose_name=_(u"voters"),
                                    blank=True,
                                    related_name='+')
    voter_count = models.IntegerField(verbose_name=_(u"voter count"),
                                      blank=True,
                                      null=True,
                                      default=None)

    # when the evaluation takes place
    vote_start_date = models.DateField(null=True,
                                       verbose_name=_(u"first date to vote"))
    vote_end_date = models.DateField(null=True,
                                     verbose_name=_(u"last date to vote"))

    # who last modified this course, shell be noted
    last_modified_time = models.DateTimeField(auto_now=True)
    last_modified_user = models.ForeignKey(settings.AUTH_USER_MODEL,
                                           related_name="+",
                                           null=True,
                                           blank=True)

    class Meta:
        ordering = ('semester', 'degree', 'name_de')
        unique_together = (
            ('semester', 'degree', 'name_de'),
            ('semester', 'degree', 'name_en'),
        )
        verbose_name = _(u"course")
        verbose_name_plural = _(u"courses")

    def __unicode__(self):
        return self.name

    def clean(self):
        if self.vote_start_date and self.vote_end_date:
            if not (self.vote_start_date < self.vote_end_date):
                raise ValidationError(
                    _(u"The vote start date must be before the vote end date.")
                )

    def save(self, *args, **kw):
        super(Course, self).save(*args, **kw)

        # make sure there is a general contribution
        if not self.general_contribution:
            self.contributions.create(contributor=None)

    def is_fully_checked(self):
        """Shortcut for finding out whether all text answers to this course have been checked"""
        return not self.textanswer_set.filter(checked=False).exists()

    def can_user_vote(self, user):
        """Returns whether the user is allowed to vote on this course."""
        if (not self.state == "inEvaluation") or (self.vote_end_date <
                                                  datetime.date.today()):
            return False

        return user in self.participants.all() and user not in self.voters.all(
        )

    def can_fsr_edit(self):
        return self.state in [
            'new', 'prepared', 'lecturerApproved', 'approved', 'inEvaluation'
        ]

    def can_fsr_delete(self):
        return not (self.textanswer_set.exists() or
                    self.gradeanswer_set.exists() or not self.can_fsr_edit())

    def can_fsr_review(self):
        return (not self.is_fully_checked()) and self.state in [
            'inEvaluation', 'evaluated'
        ]

    def can_fsr_approve(self):
        return self.state in ['new', 'prepared', 'lecturerApproved']

    @transition(field=state,
                source=['new', 'lecturerApproved'],
                target='prepared')
    def ready_for_contributors(self, send_mail=True):
        if send_mail:
            EmailTemplate.get_review_template().send_courses(
                [self], send_to_editors=True)

    @transition(field=state, source='prepared', target='lecturerApproved')
    def contributor_approve(self):
        pass

    @transition(field=state,
                source=['new', 'prepared', 'lecturerApproved'],
                target='approved')
    def fsr_approve(self):
        pass

    @transition(field=state, source='prepared', target='new')
    def revert_to_new(self):
        pass

    @transition(field=state, source='approved', target='inEvaluation')
    def evaluation_begin(self):
        pass

    @transition(field=state, source='inEvaluation', target='evaluated')
    def evaluation_end(self):
        pass

    @transition(field=state,
                source='evaluated',
                target='reviewed',
                conditions=[is_fully_checked])
    def review_finished(self):
        pass

    @transition(field=state, source='reviewed', target='published')
    def publish(self):
        pass

    @transition(field=state, source='published', target='reviewed')
    def revoke(self):
        pass

    @property
    def general_contribution(self):
        try:
            return self.contributions.get(contributor=None)
        except Contribution.DoesNotExist:
            return None

    @property
    def num_participants(self):
        if self.participants.exists():
            return self.participants.count()
        else:
            return self.participant_count or 0

    @property
    def num_voters(self):
        if self.voters.exists():
            return self.voters.count()
        else:
            return self.voter_count or 0

    @property
    def due_participants(self):
        for user in self.participants.all():
            if not user in self.voters.all():
                yield user

    @property
    def responsible_contributor(self):
        for contribution in self.contributions.all():
            if contribution.responsible:
                return contribution.contributor

    @property
    def responsible_contributors_name(self):
        return self.responsible_contributor.userprofile.full_name

    @property
    def responsible_contributors_username(self):
        return self.responsible_contributor.username

    def has_enough_questionnaires(self):
        return all(contribution.questionnaires.exists() for contribution in
                   self.contributions.all()) and self.general_contribution

    def is_user_editor_or_delegate(self, user):
        if self.contributions.filter(can_edit=True, contributor=user).exists():
            return True
        else:
            represented_userprofiles = user.represented_users.all()
            represented_users = [
                profile.user for profile in represented_userprofiles
            ]
            if self.contributions.filter(
                    can_edit=True, contributor__in=represented_users).exists():
                return True

        return False

    def is_user_responsible_or_delegate(self, user):
        if self.contributions.filter(responsible=True,
                                     contributor=user).exists():
            return True
        else:
            represented_userprofiles = user.represented_users.all()
            represented_users = [
                profile.user for profile in represented_userprofiles
            ]
            if self.contributions.filter(
                    responsible=True,
                    contributor__in=represented_users).exists():
                return True

        return False

    def is_user_contributor(self, user):
        if self.contributions.filter(contributor=user).exists():
            return True

        return False

    def warnings(self):
        result = []
        if not self.has_enough_questionnaires():
            result.append(_(u"Not enough questionnaires assigned"))
        return result

    @property
    def textanswer_set(self):
        """Pseudo relationship to all text answers for this course"""
        return TextAnswer.objects.filter(
            contribution__in=self.contributions.all())

    @property
    def open_textanswer_set(self):
        """Pseudo relationship to all text answers for this course"""
        return TextAnswer.objects.filter(
            contribution__in=self.contributions.all()).filter(checked=False)

    @property
    def checked_textanswer_set(self):
        """Pseudo relationship to all text answers for this course"""
        return TextAnswer.objects.filter(
            contribution__in=self.contributions.all()).filter(checked=True)

    @property
    def gradeanswer_set(self):
        """Pseudo relationship to all grade answers for this course"""
        return GradeAnswer.objects.filter(
            contribution__in=self.contributions.all())