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}")
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
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)
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
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
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
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')
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
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
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
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
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
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
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
def log_edits(user, post): if user != post.author: auth.db_logger(user=user, text=f'edited post', target=post.author, post=post)