Skip to content

Commit

Permalink
Merge branch 'master' of github.com:ialbert/biostar-central
Browse files Browse the repository at this point in the history
  • Loading branch information
ialbert committed Mar 31, 2021
2 parents e9fe9c7 + 50fa2b0 commit be55cc3
Show file tree
Hide file tree
Showing 11 changed files with 64 additions and 186 deletions.
157 changes: 43 additions & 114 deletions biostar/forum/ajax.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from whoosh.searching import Results

from biostar.accounts.models import Profile, User
from . import auth, util, forms, tasks, search, views, const
from . import auth, util, forms, tasks, search, views, const, moderate
from .models import Post, Vote, Subscription, delete_post_cache

def ajax_msg(msg, status, **kwargs):
Expand Down Expand Up @@ -210,80 +210,6 @@ def ajax_digest(request):
return ajax_success(msg="Changed digest options.")


def validate_recaptcha(token):
"""
Send recaptcha token to API to check if user response is valid
"""
url = 'https://www.google.com/recaptcha/api/siteverify'
values = {
'secret': settings.RECAPTCHA_PRIVATE_KEY,
'response': token
}
data = urlencode(values).encode("utf-8")
response = builtin_request.urlopen(url, data)
result = json.load(response)

if result['success']:
return True, ""

return False, "Invalid reCAPTCHA. Please try again."


def validate_toplevel_fields(fields={}):
"""Validate fields found in top level posts"""

title = fields.get('title', '')
tag_list = fields.get('tag_list', [])
tag_val = fields.get('tag_val', '')
post_type = fields.get('post_type', '')
title_length = len(title.replace(' ', ''))
allowed_types = [opt[0] for opt in Post.TYPE_CHOICES]
tag_length = len(tag_list)

if title_length <= forms.MIN_CHARS:
msg = f"Title too short, please add more than {forms.MIN_CHARS} characters."
return False, msg

if title_length > forms.MAX_TITLE:
msg = f"Title too long, please add less than {forms.MAX_TITLE} characters."
return False, msg

if post_type not in allowed_types:
msg = "Not a valid post type."
return False, msg
if tag_length > forms.MAX_TAGS:
msg = f"Too many tags, maximum of {forms.MAX_TAGS} tags allowed."
return False, msg

if len(tag_val) > 100:
msg = f"Tags have too many characters, maximum of 100 characters total allowed in tags."
return False, msg

return True, ""


def validate_post_fields(fields={}, is_toplevel=False):
"""
Validate fields found in dictionary.
"""
content = fields.get('content', '')
content_length = len(content.replace(' ', ''))

# Validate fields common to all posts.
if content_length <= forms.MIN_CHARS:
msg = f"Content too short, please add more than {forms.MIN_CHARS} characters."
return False, msg
if content_length > forms.MAX_CONTENT:
msg = f"Content too long, please add less than {forms.MAX_CONTENT} characters."
return False, msg

# Validate fields found in top level posts
if is_toplevel:
return validate_toplevel_fields(fields=fields)

return True, ""


def get_fields(request, post=None):
"""
Used to retrieve all fields in a request used for editing and creating posts.
Expand All @@ -306,6 +232,22 @@ def get_fields(request, post=None):
return fields


def set_post(post, user, fields):

# Set the fields for this post.
if post.is_toplevel:
post.title = fields.get('title', post.title)
post.type = fields.get('post_type', post.type)
post.tag_val = fields.get('tag_val', post.tag_val)

post.lastedit_user = user
post.lastedit_date = util.now()
post.content = fields.get('content', post.content)
post.save()

return post


@ajax_limited(key=RATELIMIT_KEY, rate=EDIT_RATE)
@ajax_error_wrapper(method="POST", login_required=True)
def ajax_edit(request, uid):
Expand All @@ -314,43 +256,30 @@ def ajax_edit(request, uid):
"""

post = Post.objects.filter(uid=uid).first()
user = request.user
can_edit = (user.profile.is_moderator or user == post.author)

if not post:
return ajax_error(msg="Post does not exist")

# Get the fields found in the request
fields = get_fields(request=request, post=post)

if not (request.user.profile.is_moderator or request.user == post.author):
if not can_edit:
return ajax_error(msg="Only moderators or the author can edit posts.")

# Validate fields in request.POST
valid, msg = validate_post_fields(fields=fields, is_toplevel=post.is_toplevel)
if not valid:
return ajax_error(msg=msg)
# Get the fields found in the request
fields = get_fields(request=request, post=post)

# Set the fields for this post.
# Pick the form
if post.is_toplevel:
post.title = fields.get('title', post.title)
post.type = fields.get('post_type', post.type)
post.tag_val = fields.get('tag_val', post.tag_val)

post.lastedit_user = request.user
post.lastedit_date = util.now()
post.content = fields.get('content', post.content)
post.save()

# Get the newly set tags to render
tags = post.tag_val.split(",")
context = dict(post=post, tags=tags, show_views=True)
tmpl = loader.get_template('widgets/post_tags.html')
tag_html = tmpl.render(context)

# Get the newly updated user line
context = dict(post=post, avatar=post.is_comment)
tmpl = loader.get_template('widgets/post_user_line.html')
user_line = tmpl.render(context)
form = forms.PostLongForm(post=post, user=user, data=fields)
else:
form = forms.PostShortForm(post=post, user=user, data=fields)

return ajax_success(msg='success', html=post.html, title=post.title, user_line=user_line, tag_html=tag_html)
if form.is_valid():
post = set_post(post, user, form.cleaned_data)
return ajax_success(msg='Edited post', redirect=post.get_absolute_url())
else:
msg = [field.errors for field in form if field.errors]
return ajax_error(msg=msg)


@ajax_error_wrapper(method="POST", login_required=True)
Expand All @@ -365,7 +294,7 @@ def ajax_delete(request):
if not (user.profile.is_moderator or post.author == user):
return ajax_error(msg="Only moderators and post authors can delete.")

url = auth.delete_post(post=post, request=request)
url = moderate.delete_post(post=post, request=request)

return ajax_success(msg="post deleted", url=url)

Expand All @@ -383,17 +312,17 @@ def ajax_comment_create(request):
if not parent:
return ajax_error(msg='Parent post does not exist.')

fields = dict(content=content, user=user, parent=parent)
fields = dict(content=content, parent_uid=parent_uid)

# Validate the fields
valid, msg = validate_post_fields(fields=fields, is_toplevel=False)
if not valid:
return ajax_error(msg=msg)
form = forms.PostShortForm(post=parent, user=user, data=fields)

# Create the comment.
post = Post.objects.create(type=Post.COMMENT, content=content, author=user, parent=parent)

return ajax_success(msg='Created post', redirect=post.get_absolute_url())
if form.is_valid():
# Create the comment.
post = Post.objects.create(type=Post.COMMENT, content=content, author=user, parent=parent)
return ajax_success(msg='Created post', redirect=post.get_absolute_url())
else:
msg = [field.errors for field in form if field.errors]
return ajax_error(msg=msg)


@ajax_limited(key=RATELIMIT_KEY, rate=EDIT_RATE)
Expand Down
16 changes: 11 additions & 5 deletions biostar/forum/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,17 @@ def clean_tag_val(self):
"""
Take out duplicates
"""
tag_val = self.cleaned_data["tag_val"] or 'tag1,tag2'
tags = set([x for x in tag_val.split(",") if x])
required_tags(tags)
tag_val = self.cleaned_data["tag_val"]
tag_val = tag_val.replace(',', ' ').split()

return ",".join(tags)
for tag in tag_val:
if not tag.isalnum():
raise forms.ValidationError(f'only alphanumeric tags allowed: {tag}')

tags = set(tag_val)
tags = ",".join(tags)

return tags

def clean_content(self):
content = self.cleaned_data["content"]
Expand All @@ -175,7 +181,7 @@ def clean_content(self):

class PostShortForm(forms.Form):
MIN_LEN, MAX_LEN = 10, 10000
parent_uid = forms.CharField(widget=forms.HiddenInput(), min_length=2, max_length=32)
parent_uid = forms.CharField(widget=forms.HiddenInput(), min_length=2, max_length=32, required=False)
content = forms.CharField(widget=forms.Textarea,
min_length=MIN_LEN, max_length=MAX_LEN, strip=False)

Expand Down
2 changes: 1 addition & 1 deletion biostar/forum/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import logging

from datetime import timedelta
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.sites.models import Site
Expand Down
6 changes: 0 additions & 6 deletions biostar/forum/static/forum.js
Original file line number Diff line number Diff line change
Expand Up @@ -218,12 +218,6 @@ function highligh_preview(form, text) {
form.find('code').addClass('language-bash');

Prism.highlightAll();
// Enable mathjax in preview.
const content = document.createElement('p');
content.textContent = text;
MathJax.typesetPromise().then(() => {
MathJax.typesetPromise();
}).catch((err) => console.log(err.message));


}
Expand Down
32 changes: 2 additions & 30 deletions biostar/forum/static/inplace.js
Original file line number Diff line number Diff line change
Expand Up @@ -188,35 +188,6 @@ function inplace_form(elem, add_comment) {
}


function update_post(uid, data) {

// Current post content and title to replace
// with returned values.
var post = $("[data-value='{0}']".format(uid));
var post_content = $(".post[data-value='{0}'] .editable".format(uid)).first();
var post_title = $("[data-value='{0}'] .title".format(uid)).first();
var post_tags =$("[data-value='{0}'] .inplace-tags".format(uid)).first();
var post_users =$("[data-value='{0}'] .user-info".format(uid)).first();

// Replace current post info with edited data
post_content.html(data.html).show().focus();

post_title.html(data.title).show();
post_tags.html(data.tag_html).show();
post_users.html(data.user_line).show();

cancel_inplace(post);

// Enable Mathjax on the new content.
const content = document.createElement('p');
content.textContent = post_content.text();
MathJax.typesetPromise().then(() => {
MathJax.typesetPromise();
}).catch((err) => console.log(err.message));

}


function edit_post(post) {

var uid = post.data('value');
Expand Down Expand Up @@ -260,7 +231,8 @@ function edit_post(post) {
popup_message(form.find(".save,.create"), data.msg, data.status, 3000);
} else {
// Update post with latest
update_post(uid, data);
window.location = data.redirect;
window.location.reload();
}
},
error: function (xhr, status, text) {
Expand Down
6 changes: 3 additions & 3 deletions biostar/forum/templates/forms/field_tags.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
{% endif %}

<select multiple="multiple" data-value="{{ form_field.id_for_label }}" class="ui search selection dropdown tags">
<option value="">Search tags</option>
{% for value, is_selected in dropdown_options %}
<option value="">Insert tags</option>
{% for value in dropdown_options %}

<option value="{{ value }}" {% if is_selected %}selected="selected"{% endif %}>
<option value="{{ value }}" selected="selected">
{{ value }}
</option>

Expand Down
15 changes: 0 additions & 15 deletions biostar/forum/templates/forum_base.html
Original file line number Diff line number Diff line change
Expand Up @@ -40,21 +40,6 @@

{% endif %}

<script async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js" id="MathJax-script"></script>

<script>
MathJax = {
tex: {
// \$ fixes php conflicting with Mathjax delimiter
inlineMath: [['\$', '\$'], ['\\(', '\\)']]
},
svg: {
fontCache: 'global'
}
};

</script>

{# CSS compression. #}
{% compress css %}

Expand Down
3 changes: 1 addition & 2 deletions biostar/forum/templates/post_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,16 @@


{% if tag %}

<div class="ui filter message">

Filtering for tag: <span class="ptag">{{ tag }}</span> &bull;
<a href="{% url 'post_list' %}">
reset <i class="undo small icon"></i>
</a>
</div>

{% endif %}


<div class="ui divided items">
{% for post in posts %}
{% post_details post=post user=request.user avatar=avatar %}
Expand Down
8 changes: 2 additions & 6 deletions biostar/forum/templatetags/forum_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,10 +347,7 @@ def get_dropdown_options(selected_list):
query = Tag.objects.exclude(name__in=selected_list)[:limit].values_list("name", flat=True)
opts = {(name.strip(), False) for name in query}

# Chain the selected and rest of the options
opts = itertools.chain(selected, opts)

return opts
return selected


@register.inclusion_tag('forms/field_tags.html', takes_context=True)
Expand All @@ -359,9 +356,8 @@ def tags_field(context, form_field, initial=''):

# Get currently selected tags from the post or request
selected = initial.split(",") if initial else []
options = get_dropdown_options(selected_list=selected)

context = dict(initial=initial, form_field=form_field, dropdown_options=options)
context = dict(initial=initial, form_field=form_field, dropdown_options=selected)

return context

Expand Down
3 changes: 0 additions & 3 deletions biostar/forum/tests/test_ajax.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,6 @@ def test_inplace_edit(self):
json_response = ajax.ajax_edit(request, uid=self.post.uid)
response_data = json.loads(json_response.content)

for tag in data['tag_val']:
self.assertTrue(tag in response_data['tag_html'], 'Tag not working')

self.process_response(json_response)

def test_inplace_create(self):
Expand Down

0 comments on commit be55cc3

Please sign in to comment.