diff --git a/biostar/__init__.py b/biostar/__init__.py index de77620ca..a459ed057 100644 --- a/biostar/__init__.py +++ b/biostar/__init__.py @@ -3,4 +3,4 @@ __all__ = ['celery_app'] -VERSION = '2.3.3' +VERSION = '2.3.4' diff --git a/biostar/accounts/views.py b/biostar/accounts/views.py index 2e0714faf..2ca107eb3 100644 --- a/biostar/accounts/views.py +++ b/biostar/accounts/views.py @@ -36,6 +36,7 @@ RATELIMIT_KEY = settings.RATELIMIT_KEY + def edit_profile(request): if request.user.is_anonymous: messages.error(request, "Must be logged in to edit profile") @@ -57,7 +58,6 @@ def edit_profile(request): username = form.cleaned_data["username"] email = form.cleaned_data['email'] User.objects.filter(pk=user.pk).update(username=username, email=email) - # Update user information in Profile object. Profile.objects.filter(user=user).update(name=form.cleaned_data['name'], watched_tags=form.cleaned_data['watched_tags'], location=form.cleaned_data['location'], @@ -69,6 +69,8 @@ def edit_profile(request): message_prefs=form.cleaned_data["message_prefs"], html=markdown(form.cleaned_data["text"]), digest_prefs=form.cleaned_data['digest_prefs']) + # Recompute watched tags + Profile.objects.filter(user=user).first().add_watched() return redirect(reverse("user_profile", kwargs=dict(uid=user.profile.uid))) diff --git a/biostar/forum/api.py b/biostar/forum/api.py index 8eb07d75f..a7dc0b611 100644 --- a/biostar/forum/api.py +++ b/biostar/forum/api.py @@ -192,6 +192,17 @@ def traffic(request): return data +@json_response +def api_tag(request, tag): + """ + Return list of post uids that have a tag. + """ + posts = Post.objects.filter(tags__name=tag.lower()).values_list('uid', flat=True) + posts = list(posts) + + return posts + + @json_response def user_email(request, email): user = User.objects.filter(email__iexact=email.lower()) diff --git a/biostar/forum/auth.py b/biostar/forum/auth.py index 4d2a9464f..92ad4f045 100644 --- a/biostar/forum/auth.py +++ b/biostar/forum/auth.py @@ -287,9 +287,10 @@ def post_tree(user, root): # Get all posts that belong to post root. query = Post.objects.valid_posts(u=user, root=root).exclude(pk=root.id) + # Filter quarantined and deleted comments or answers. if user.is_anonymous or not user.profile.is_moderator: - query = query.exclude(Q(spam=Post.SUSPECT) | Q(status=Post.DELETED)) + query = query.exclude(Q(spam=Post.SUSPECT) | Q(status=Post.DELETED) | Q(spam=Post.SPAM)) query = query.select_related("lastedit_user__profile", "author__profile", "root__author__profile") diff --git a/biostar/forum/models.py b/biostar/forum/models.py index c3ff87448..085913261 100644 --- a/biostar/forum/models.py +++ b/biostar/forum/models.py @@ -35,8 +35,11 @@ def valid_posts(self, u=None, **kwargs): if u and u.is_authenticated and u.profile.is_moderator: return query + # Users get to see their own quarantined posts. + # Filter for open posts that are not spam. query = query.filter(status=Post.OPEN, root__status=Post.OPEN) + query = query.filter(models.Q(spam=Post.NOT_SPAM) | models.Q(spam=Post.DEFAULT) | models.Q(root__spam=Post.NOT_SPAM) | models.Q(root__spam=Post.DEFAULT)) diff --git a/biostar/forum/search.py b/biostar/forum/search.py index 0e87a4e8c..9b8dd532c 100644 --- a/biostar/forum/search.py +++ b/biostar/forum/search.py @@ -13,6 +13,7 @@ from whoosh.searching import Results import html2markdown +import bleach from whoosh.qparser import MultifieldParser, OrGroup from whoosh.analysis import STOP_WORDS from whoosh.index import create_in, open_dir, exists_in @@ -107,8 +108,9 @@ def index_exists(dirname=settings.INDEX_DIR, indexname=settings.INDEX_NAME): def add_index(post, writer): - # user html2markdown to clean text. - content = html2markdown.convert(post.content) + + # Ensure the content is stripped of any html. + content = bleach.clean(post.content, styles=[], attributes={}, tags=[], strip=True) writer.update_document(title=post.title, url=post.get_absolute_url(), type_display=post.get_type_display(), content_length=len(content), diff --git a/biostar/forum/static/forum.css b/biostar/forum/static/forum.css index 0cb9674d7..cbd509912 100644 --- a/biostar/forum/static/forum.css +++ b/biostar/forum/static/forum.css @@ -52,6 +52,10 @@ body > .widen.container { } +.ui.form .form-wrap .field>.selection.dropdown { + min-width: 0 !important; +} + .voting .ui.button { background: none; padding: 5px; diff --git a/biostar/forum/tasks.py b/biostar/forum/tasks.py index b90b045b2..a3892538c 100644 --- a/biostar/forum/tasks.py +++ b/biostar/forum/tasks.py @@ -42,19 +42,6 @@ def spam_scoring(post): message(exc) -def tpatt(tag): - """ - Return pattern matching a tag found in comma separated string. - (?i) : case-insensitive flag - ^{tag}\\s*, : matches beginning - ,\\s*{tag}\\s*, : matches middle - ,\\s*{tag}$ : matches end - ^{tag}[^\\w+] : matches single entry ( no commas ) - """ - patt = fr"(?i)(^{tag}\s*,|,\s*{tag}\s*,|,\s*{tag}$|^{tag}$)" - return patt - - @task def notify_watched_tags(post, extra_context): """ @@ -63,7 +50,7 @@ def notify_watched_tags(post, extra_context): from biostar.accounts.models import User from django.conf import settings - users = [User.objects.filter(profile__watched_tags__iregex=tpatt(tag.name)).distinct() + users = [User.objects.filter(profile__watched__name=tag.name).distinct() for tag in post.root.tags.all()] # Flatten nested users queryset and get email. diff --git a/biostar/forum/templates/accounts/edit_profile.html b/biostar/forum/templates/accounts/edit_profile.html index b53f8f669..ad7e59389 100644 --- a/biostar/forum/templates/accounts/edit_profile.html +++ b/biostar/forum/templates/accounts/edit_profile.html @@ -1,9 +1,26 @@ {% extends "forum_base.html" %} {% load forum_tags %} {% load accounts_tags %} +{% load static %} {% block headtitle %}Edit Profile{% endblock %} +{% block head %} + + + + + +{% endblock %} + +{% block js %} + + + + + +{% endblock %} + {% block content %}
diff --git a/biostar/forum/templates/accounts/user_profile.html b/biostar/forum/templates/accounts/user_profile.html index 574ab5527..7ba1f87f2 100644 --- a/biostar/forum/templates/accounts/user_profile.html +++ b/biostar/forum/templates/accounts/user_profile.html @@ -21,6 +21,7 @@ {% endblock %} + {% block content %}
diff --git a/biostar/forum/templates/banners/menu-topics.html b/biostar/forum/templates/banners/menu-topics.html index fb51be77a..da0da5009 100644 --- a/biostar/forum/templates/banners/menu-topics.html +++ b/biostar/forum/templates/banners/menu-topics.html @@ -18,7 +18,7 @@ {% if counts.messages.count %} -
+
Messages ({{ counts.messages.count }})
{% else %} @@ -26,11 +26,10 @@ {% endif %} - + {% if counts.votes.count %} -
New Votes ({{ counts.votes.count }})
+
Votes ({{ counts.votes.count }})
{% else %} Votes {% endif %} diff --git a/biostar/forum/templates/base_view.html b/biostar/forum/templates/base_view.html index 156422b80..c1b77db32 100644 --- a/biostar/forum/templates/base_view.html +++ b/biostar/forum/templates/base_view.html @@ -9,3 +9,32 @@ {% endblock %} + +{% block css %} + + + + + +{% endblock %} + + +{% block js %} + + + + + + + + + + + + + + + + + +{% endblock %} \ No newline at end of file diff --git a/biostar/forum/templates/forum_base.html b/biostar/forum/templates/forum_base.html index 312de474d..0317d3975 100644 --- a/biostar/forum/templates/forum_base.html +++ b/biostar/forum/templates/forum_base.html @@ -56,10 +56,10 @@ {#% recaptcha_init %#} {# CSS compression. #} - {% compress css inline %} + {% compress css %} + {# Preload key requests #} - {% block cssfile %} {% endblock %} @@ -67,15 +67,13 @@ {% endcompress %} {# Javascript compression. #} - {% compress js inline %} + {% compress js %} {% block jsfile %} {% endblock %} {% endcompress %} - - {# Optional css header content. #} {% block css %} {% endblock %} @@ -86,6 +84,8 @@ {% endblock %} + +
@@ -120,6 +120,8 @@ {% endblock %}
+ + diff --git a/biostar/forum/templates/new_post.html b/biostar/forum/templates/new_post.html index 4e6f44549..58b875a48 100644 --- a/biostar/forum/templates/new_post.html +++ b/biostar/forum/templates/new_post.html @@ -8,33 +8,6 @@ -{% block css %} - - - - -{% endblock %} - -{% block js %} - - - - - - - - - - - - - - - - - - -{% endblock %} {% block container %}
diff --git a/biostar/forum/templates/post_view.html b/biostar/forum/templates/post_view.html index 14118e552..90b998bdc 100644 --- a/biostar/forum/templates/post_view.html +++ b/biostar/forum/templates/post_view.html @@ -10,33 +10,6 @@ -{% block css %} - - - - -{% endblock %} - - -{% block js %} - - - - - - - - - - - - - - - - - -{% endblock %} {% block body %} diff --git a/biostar/forum/urls.py b/biostar/forum/urls.py index 4f6d39bac..2917d524f 100644 --- a/biostar/forum/urls.py +++ b/biostar/forum/urls.py @@ -63,6 +63,7 @@ # Api calls path(r'api/traffic/', api.traffic, name='api_traffic'), path(r'api/user//', api.user_details, name='api_user'), + path(r'api/tag//', api.api_tag, name='api_tag'), path(r'api/tags/list/', api.tags_list, name='api_tags_list'), path(r'api/post//', api.post_details, name='api_post'), path(r'api/vote//', api.vote_details, name='api_vote'), diff --git a/biostar/forum/views.py b/biostar/forum/views.py index dc309bf3a..c53263176 100644 --- a/biostar/forum/views.py +++ b/biostar/forum/views.py @@ -112,7 +112,10 @@ def get_posts(user, topic="", tag="", order="", limit=None): elif topic == FOLLOWING and user.is_authenticated: query = query.filter(subs__user=user).exclude(subs__type=Subscription.NO_MESSAGES) elif topic == MYPOSTS and user.is_authenticated: - query = query.filter(author=user) + # Show users all of there posts ( deleted, spam, or quarantined ) + query = Post.objects.filter(author=user) + #query = query.filter(author=user) + elif topic == MYVOTES and user.is_authenticated: query = query.filter(votes__post__author=user) elif topic == MYTAGS and user.is_authenticated: diff --git a/biostar/recipes/forms.py b/biostar/recipes/forms.py index 888095b07..7c34cc188 100644 --- a/biostar/recipes/forms.py +++ b/biostar/recipes/forms.py @@ -244,8 +244,9 @@ def clean(self): if not cleaned_data.get("data_name"): raise forms.ValidationError("Name is required with text inputs.") - total_count = Data.objects.filter(owner=self.user).count() - if total_count >= settings.MAX_DATA: + total_count = Data.objects.filter(owner=self.user, deleted=False).count() + + if total_count >= settings.MAX_DATA and not self.user.profile.is_moderator: raise forms.ValidationError(f"Exceeded maximum amount of data:{settings.MAX_DATA}.") return cleaned_data diff --git a/biostar/recipes/static/engine.css b/biostar/recipes/static/engine.css index 31e814f8d..9f0d51dc0 100644 --- a/biostar/recipes/static/engine.css +++ b/biostar/recipes/static/engine.css @@ -37,8 +37,7 @@ body { } .ui-droppable-hover { - border: #10ee0a dotted 5px !important; - height: 50% !important; + border-bottom: #10ee0a solid 5px !important; } .draggable { diff --git a/conf/scripts/server-cleanup.sh b/conf/scripts/server-cleanup.sh new file mode 100644 index 000000000..915f3db1a --- /dev/null +++ b/conf/scripts/server-cleanup.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +# Load the conda commands. +source ~/miniconda3/etc/profile.d/conda.sh + +export POSTGRES_HOST=/var/run/postgresql + +# Activate the conda environemnt. +conda activate engine + +# Set the configuration module. +export DJANGO_SETTINGS_MODULE=conf.run.site_settings + +python manage.py cleanup diff --git a/conf/scripts/server-setup.sh b/conf/scripts/server-setup.sh deleted file mode 100644 index 5df81166d..000000000 --- a/conf/scripts/server-setup.sh +++ /dev/null @@ -1,44 +0,0 @@ -# Must be run as root. - -set -uex - -# The host name of the server. -HOST_NAME=test.biostars.org - -# Set the hostname. -hostnamectl set-hostname $HOST_NAME - -# Set the timezone. -dpkg-reconfigure tzdata - -# Add the default user. -adduser www - -# Add the www user to the sudo group. -adduser www sudo - -# Update the server. -apt-get update && apt-get upgrade -y - -# Upgrade the distributions. -sudo apt-get dist-upgrade - -# Add and enable fail2ban. -apt-get install ufw fail2ban -y - -# Enable the firewalls -ufw allow ssh -ufw allow http -ufw allow https -ufw enable - -# Set up a packages that will be needed. -apt-get install nginx postgresql software-properties-common curl -y -apt-get install supervisor build-essential cmake zlib1g-dev -y - -# Install the certbot packages. -add-apt-repository universe -add-apt-repository ppa:certbot/certbot -apt-get update -apt-get install certbot python-certbot-nginx -y -