diff --git a/.vscode/settings.json b/.vscode/settings.json index 0d19073..a283dec 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,7 +2,7 @@ "python.linting.pylintEnabled": false, "python.linting.flake8Enabled": true, "python.linting.enabled": true, - "python.formatting.provider": "black", + "python.formatting.provider": "none", "python.terminal.activateEnvironment": true, "files.watcherExclude": { "**/.git/objects/**": true, @@ -25,8 +25,19 @@ "wsgi", "wtforms" ], - "python.testing.unittestArgs": ["-v", "-s", "./tests", "-p", "*test.py"], + "python.testing.unittestArgs": [ + "-v", + "-s", + "./tests", + "-p", + "*test.py" + ], "python.testing.pytestEnabled": true, "python.testing.unittestEnabled": false, - "python.testing.pytestArgs": ["tests"] -} + "python.testing.pytestArgs": [ + "tests" + ], + "[python]": { + "editor.defaultFormatter": "ms-python.black-formatter" + } +} \ No newline at end of file diff --git a/app/__init__.py b/app/__init__.py index ce5d073..47636e5 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -15,7 +15,6 @@ migration = Migrate() def create_app(environment="development"): - from config import config from app.views import ( main_blueprint, @@ -24,6 +23,7 @@ def create_app(environment="development"): book_blueprint, home_blueprint, section_blueprint, + vote_blueprint, ) from app.models import ( User, @@ -52,6 +52,7 @@ def create_app(environment="development"): app.register_blueprint(book_blueprint) app.register_blueprint(home_blueprint) app.register_blueprint(section_blueprint) + app.register_blueprint(vote_blueprint) # Set up flask login. @login_manager.user_loader diff --git a/app/forms/__init__.py b/app/forms/__init__.py index 8e8d990..61318cb 100644 --- a/app/forms/__init__.py +++ b/app/forms/__init__.py @@ -11,3 +11,4 @@ from .collection import CreateCollectionForm, EditCollectionForm from .section import CreateSectionForm, EditSectionForm from .interpretation import CreateInterpretationForm, EditInterpretationForm from .comment import CreateCommentForm +from .vote import VoteForm diff --git a/app/forms/vote.py b/app/forms/vote.py new file mode 100644 index 0000000..d71d1ec --- /dev/null +++ b/app/forms/vote.py @@ -0,0 +1,6 @@ +from flask_wtf import FlaskForm +from wtforms import BooleanField + + +class VoteForm(FlaskForm): + positive = BooleanField("Positive") diff --git a/app/models/interpretation.py b/app/models/interpretation.py index d46d714..e27b7a3 100644 --- a/app/models/interpretation.py +++ b/app/models/interpretation.py @@ -27,5 +27,17 @@ class Interpretation(BaseModel): back_populates="interpretations", ) + @property + def vote_count(self): + count = 0 + + for vote in self.votes: + if vote.positive: + count += 1 + else: + count -= 1 + + return count + def __repr__(self): return f"<{self.id}: {self.label}>" diff --git a/app/views/__init__.py b/app/views/__init__.py index 9c6c52e..f77e35c 100644 --- a/app/views/__init__.py +++ b/app/views/__init__.py @@ -5,3 +5,4 @@ from .user import bp as user_blueprint from .book import bp as book_blueprint from .home import bp as home_blueprint from .section import bp as section_blueprint +from .vote import bp as vote_blueprint diff --git a/app/views/vote.py b/app/views/vote.py new file mode 100644 index 0000000..e4f1cca --- /dev/null +++ b/app/views/vote.py @@ -0,0 +1,67 @@ +from flask import ( + Blueprint, + jsonify, +) +from flask_login import login_required, current_user + +from app import models as m, db, forms as f +from app.logger import log + +bp = Blueprint("vote", __name__, url_prefix="/vote") + + +@bp.route( + "/interpretation/", + methods=["POST"], +) +@login_required +def vote_interpretation(interpretation_id: int): + interpretation: m.Interpretation = db.session.get( + m.Interpretation, interpretation_id + ) + if not interpretation: + log(log.WARNING, "Interpretation with id [%s] not found", interpretation_id) + return jsonify({"message": "Interpretation not found"}), 404 + + form = f.VoteForm() + if form.validate_on_submit(): + vote: m.InterpretationVote = m.InterpretationVote.query.filter_by( + user_id=current_user.id, interpretation_id=interpretation_id + ).first() + if vote: + db.session.delete(vote) + + positive = form.positive.data and "True" in form.positive.raw_data + if not vote or vote.positive != positive: + vote: m.InterpretationVote = m.InterpretationVote( + user_id=current_user.id, + interpretation_id=interpretation_id, + positive=positive, + ) + log( + log.INFO, + "User [%s]. [%s] vote interpretation: [%s]", + current_user, + "Positive" if positive else "Negative", + interpretation, + ) + vote.save(False) + else: + log( + log.INFO, + "User [%s]. Remove [%s] vote for interpretation: [%s]", + current_user, + "positive" if positive else "negative", + interpretation, + ) + db.session.commit() + + return jsonify({"vote_count": interpretation.vote_count}) + + log( + log.CRITICAL, + "Unexpected error: User [%s]. Vote for interpretation: [%s]", + current_user, + interpretation, + ) + return jsonify({"message": "Unexpected error"}), 400 diff --git a/tests/test_upvote.py b/tests/test_upvote.py new file mode 100644 index 0000000..9a8b436 --- /dev/null +++ b/tests/test_upvote.py @@ -0,0 +1,93 @@ +from flask import current_app as Response +from flask.testing import FlaskClient + +from app import models as m +from tests.utils import login + + +def test_upvote(client: FlaskClient): + _, user = login(client) + + response: Response = client.post( + "/vote/interpretation/999", + data=dict( + positive=True, + ), + follow_redirects=True, + ) + + assert response + assert response.status_code == 404 + assert response.json["message"] == "Interpretation not found" + + interpretation = m.Interpretation( + label="Test Interpretation 1 Label", + text="Test Interpretation 1 Text", + user_id=user.id, + ).save() + + assert interpretation.vote_count == 0 + + response: Response = client.post( + f"/vote/interpretation/{interpretation.id}", + data=dict( + positive=True, + ), + follow_redirects=True, + ) + + assert response + assert response.status_code == 200 + json = response.json + assert json + assert "vote_count" in json + assert json["vote_count"] == 1 + assert interpretation.vote_count == 1 + + response: Response = client.post( + f"/vote/interpretation/{interpretation.id}", + data=dict( + positive=True, + ), + follow_redirects=True, + ) + + assert response + assert response.status_code == 200 + json = response.json + assert json + assert "vote_count" in json + assert json["vote_count"] == 0 + assert interpretation.vote_count == 0 + + response: Response = client.post( + f"/vote/interpretation/{interpretation.id}", + data=dict( + positive=False, + ), + follow_redirects=True, + ) + + assert response + assert response.status_code == 200 + json = response.json + assert json + assert "vote_count" in json + assert json["vote_count"] == -1 + assert interpretation.vote_count == -1 + + response: Response = client.post( + f"/vote/interpretation/{interpretation.id}", + data=dict( + # positive=False, + ), + follow_redirects=True, + ) + + assert response + assert response.status_code == 200 + json = response.json + assert json + assert "vote_count" in json + assert json["vote_count"] == 0 + assert interpretation.vote_count == 0