예제 #1
0
def score(uid, threshold=None):
    """
    """

    if not settings.CLASSIFY_SPAM:
        return

    if threshold is None:
        threshold = settings.SPAM_THRESHOLD

    post = Post.objects.filter(uid=uid).first()

    # User's with high enough score automatically given green light.
    # if not post.author.profile.low_rep:
    #    return

    # Search for spam similar to this post.
    post_score = compute_score(post=post)

    # Update the spam score.
    Post.objects.filter(id=post.id).update(spam_score=post_score)

    # If the score exceeds threshold it gets labeled spam.
    if post_score >= threshold:
        Post.objects.filter(id=post.id).update(spam=Post.SPAM)
        auth.db_logger(text=f" post={post.uid}; spam score={post_score}")
예제 #2
0
def delete_post(request, post, **kwargs):
    """
    Post may be marked as deleted or removed entirely
    """
    user = request.user

    # Decide on the removal
    remove = removal_condition(post, user)

    if remove:
        msg = f"removed post"
        messages.info(request, mark_safe(msg))
        auth.db_logger(user=user, post=post, text=msg)
        post.delete()
        # Deleted children should return root url.
        url = "/" if post.is_toplevel else post.root.get_absolute_url()
    else:
        Post.objects.filter(uid=post.uid).update(status=Post.DELETED)
        post.recompute_scores()
        msg = f"deleted post"
        messages.info(request, mark_safe(msg))
        auth.db_logger(user=user, post=post, text=msg)
        url = post.get_absolute_url()

    # Recompute post score.
    if not post.is_toplevel:
        post.root.recompute_scores()

    return url
예제 #3
0
def change_user_state(mod, target, state):
    """
    Changes user state.
    """

    # Only moderators may change user states.
    if not mod.profile.is_moderator:
        logger.error(f"{mod} is not a moderator")
        return

    # Cannot moderate self.
    if mod == target:
        logger.error(f"{mod} cannot moderate themselves")
        return

    # The target may not be a moderator.
    if target.profile.is_moderator:
        logger.info(f"{mod} cannot alter state on a moderator {target}")
        return

    # Set the new state.
    target.profile.state = state
    target.profile.save()

    # Generate the logging message.
    msg = f"changed user state to {target.profile.get_state_display()}"
    auth.db_logger(user=mod, action=Log.MODERATE, target=target, text=msg, post=None)
예제 #4
0
def off_topic(request, post, **kwargs):
    """
    Marks post as off topic. Generate off topic comment.
    """
    user = request.user
    if "offtopic" not in post.tag_val:
        post.tag_val += ",offtopic "
        post.save()

        # Generate off comment.
        template = 'messages/offtopic.md'
        tmpl = loader.get_template(template)
        context = dict(post=post)
        content = tmpl.render(context)

        auth.create_post(ptype=Post.COMMENT,
                         parent=post,
                         content=content,
                         title='',
                         author=request.user)
        msg = "off topic"
        messages.info(request, mark_safe(msg))
        auth.db_logger(user=user, text=f"{msg}", post=post)
    else:
        messages.warning(request, "post has been already tagged as off topic")

    url = post.get_absolute_url()
    return url
예제 #5
0
def spam_check(uid):
    from biostar.forum.models import Post, Log
    from biostar.accounts.models import User, Profile

    post = Post.objects.filter(uid=uid).first()
    author = post.author

    # Automated spam disabled in for trusted user
    if author.profile.trusted or author.profile.score > 50:
        return

    # Classify spam only if we have not done it yet.
    if post.spam != Post.DEFAULT:
        return

    try:
        from biostar.utils import spamlib

        if not os.path.isfile(settings.SPAM_MODEL):
            spamlib.build_model(fname=settings.SPAM_DATA, model=settings.SPAM_MODEL)

        # Classify the content.
        flag = spamlib.classify_content(post.content, model=settings.SPAM_MODEL)

        # Another process may have already classified it as spam.
        check = Post.objects.filter(uid=post.uid).first()
        if check and check.spam == Post.SPAM:
            return

        ## Links in title usually mean spam.
        spam_words = ["http://", "https://"]
        for word in spam_words:
            flag = flag or (word in post.title)

        if flag:

            Post.objects.filter(uid=post.uid).update(spam=Post.SPAM, status=Post.CLOSED)

            # Get the first admin.
            user = User.objects.filter(is_superuser=True).order_by("pk").first()

            create_messages(template="messages/spam-detected.md",
                            extra_context=dict(post=post),
                            user_ids=[post.author.id])

            spam_count = Post.objects.filter(spam=Post.SPAM, author=author).count()

            db_logger(user=user, action=Log.CLASSIFY, text=f"classified the post as spam", post=post)

            if spam_count > 1 and low_trust(post.author):
                # Suspend the user
                Profile.objects.filter(user=author).update(state=Profile.SUSPENDED)
                db_logger(user=user, action=Log.MODERATE, text=f"suspended the author of", post=post)

    except Exception as exc:
        print(exc)
        logger.error(exc)

    return False
예제 #6
0
def toggle_spam(request, post, **kwargs):
    """
    Toggles spam status on post based on a status
    """

    url = post.get_absolute_url()

    # Moderators may only be suspended by admins (TODO).
    if post.author.profile.is_moderator and post.spam in (Post.DEFAULT,
                                                          Post.NOT_SPAM):
        messages.warning(
            request, "cannot toggle spam on a post created by a moderator")
        return url

    # The user performing the action.
    user = request.user

    # Drop the cache for the post.
    delete_post_cache(post)

    # Current state of the toggle.
    if post.is_spam:
        Post.objects.filter(id=post.id).update(spam=Post.NOT_SPAM,
                                               status=Post.OPEN)
    else:
        Post.objects.filter(id=post.id).update(spam=Post.SPAM,
                                               status=Post.CLOSED)

    # Refetch up to date state of the post.
    post = Post.objects.filter(id=post.id).get()

    # Set the state for the user (only non moderators are affected)
    state = Profile.SUSPENDED if post.is_spam else Profile.NEW

    # Apply the user change.
    change_user_state(mod=user, target=post.author, state=state)

    # Generate logging messages.
    if post.is_spam:
        text = f"marked post as spam"
    else:
        text = f"restored post from spam"

        # Set indexed flag to False, so it's removed from spam index
        Post.objects.filter(id=post.id).update(indexed=False)

    # Set a logging message.
    messages.success(request, text)

    # Submit the log into the database.
    auth.db_logger(user=user,
                   action=Log.MODERATE,
                   target=post.author,
                   text=text,
                   post=post)

    url = post.get_absolute_url()

    return url
예제 #7
0
def suspend_user(user):

    if user.profile.trusted:
        return

    if user.profile.state == Profile.NEW:
        user.profile.state = Profile.SUSPENDED
        user.profile.save()
        admin = User.objects.filter(is_superuser=True).order_by("pk").first()
        auth.db_logger(user=admin, target=user, text=f'insta banned')
예제 #8
0
def bump(request, post, **kwargs):
    now = util.now()
    user = request.user

    Post.objects.filter(uid=post.uid).update(lastedit_date=now, rank=now.timestamp())
    msg = f"bumped post"
    url = post.get_absolute_url()
    messages.info(request, mark_safe(msg))
    auth.db_logger(user=user, text=f"{msg}", post=post)

    return url
예제 #9
0
def open(request, post, **kwargs):
    if post.is_spam and post.author.profile.low_rep:
        post.author.profile.bump_over_threshold()

    user = request.user
    Post.objects.filter(uid=post.uid).update(status=Post.OPEN, spam=Post.NOT_SPAM)
    post.recompute_scores()

    post.root.recompute_scores()
    msg = f"opened post"
    url = post.get_absolute_url()
    messages.info(request, mark_safe(msg))
    auth.db_logger(user=user, text=f"{msg}", post=post)
    return url
예제 #10
0
def close(request, post, **kwargs):

    """
    Close this post and provide a rationale for closing as well.
    """
    user = request.user
    Post.objects.filter(uid=post.uid).update(status=Post.CLOSED)
    # Generate a rationale post on why this post is closed.
    rationale = mod_rationale(post=post, user=user,
                              template="messages/closed.md")
    msg = "closed"
    url = rationale.get_absolute_url()
    messages.info(request, mark_safe(msg))
    auth.db_logger(user=user, text=f"{msg}", post=post)
    return url
예제 #11
0
def send_herald_message(sender, instance, created, **kwargs):
    """
    Send message to users when they receive an award.
    """
    if created:
        template = "messages/shared_link.md"
        context = dict(shared=instance)

        # Let the user know we have received.
        tasks.create_messages(template=template,
                              extra_context=context,
                              user_ids=[instance.author.pk])
        logmsg = f"{instance.get_status_display().lower()} herald story {instance.url[:100]}"
        auth.db_logger(user=instance.author, text=logmsg)

    return
예제 #12
0
def relocate(request, post, **kwds):
    """
    Moves an answer to a comment and viceversa.
    """
    url = post.get_absolute_url()

    if post.is_toplevel:
        messages.warning(request, "cannot relocate a top level post")
        return url

    if post.type == Post.COMMENT:
        msg = f"relocated comment to answer"
        post.type = Post.ANSWER
    else:
        msg = f"relocated answer to comment"
        post.type = Post.COMMENT

    post.parent = post.root
    post.save()

    auth.db_logger(user=request.user, post=post, text=f"{msg}")
    messages.info(request, msg)
    return url
예제 #13
0
def herald_publisher(request, limit=20, nmin=1):
    """
    Create one publication from Herald accepted submissions ( up to 'limit' ).
    """

    # Reset status on published links.
    # SharedLink.objects.filter(status=SharedLink.PUBLISHED).update(status=SharedLink.ACCEPTED)

    heralds = SharedLink.objects.filter(
        status=SharedLink.ACCEPTED).order_by('-pk')[:limit]
    count = heralds.count()

    if count < nmin:
        logger.warning(
            f"Not enough stories to publish, minimum of {nmin} required.")
        return

    # Create herald content
    date = util.now()

    date_fmt = date.strftime("%A, %B %d, %Y")

    title = f"The Biostar Herald for {date_fmt}"

    port = f':{settings.HTTP_PORT}' if settings.HTTP_PORT else ''

    base_url = f"{settings.PROTOCOL}://{settings.SITE_DOMAIN}{port}"
    authors = set(h.author for h in heralds)
    editors = set(h.editor for h in heralds)

    subscribe_url = reverse('herald_subscribe')
    context = dict(heralds=heralds,
                   title=title,
                   site_domain=settings.SITE_DOMAIN,
                   protocol=settings.PROTOCOL,
                   base_url=base_url,
                   authors=authors,
                   editors=editors,
                   subscribe_url=subscribe_url)

    content = render_template(template="herald/herald_content.md",
                              context=context)

    # Create herald post
    user = User.objects.filter(is_superuser=True).first()
    post = auth.create_post(title=title,
                            content=content,
                            author=user,
                            tag_val='herald',
                            ptype=Post.HERALD,
                            nodups=False,
                            request=request)

    # Tie these submissions to herald post
    hpks = heralds.values_list('pk', flat=True)
    SharedLink.objects.filter(pk__in=hpks).update(status=SharedLink.PUBLISHED,
                                                  post=post,
                                                  lastedit_date=date)

    # Log the action
    auth.db_logger(user=user, text=f"published {count} submissions in {title}")

    # Create a herald blog post
    herald_blog(post=post)

    # Send out herald emails
    herald_emails.spool(uid=post.uid)

    # Clean up declined links
    remove_declined()

    return post
예제 #14
0
 def callback():
     source = request.user
     target = User.objects.filter(id=uid).first()
     text = f"changed user state to {target.profile.get_state_display()}"
     auth.db_logger(user=source, text=text, target=target)
     return
예제 #15
0
def log_edits(user, post):
    if user != post.author:
        auth.db_logger(user=user, text=f'edited post', target=post.author, post=post)