From 67c59264ee20e3384d701f6dca02b5c1a0b9e739 Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Mon, 9 Dec 2019 13:58:11 +1300 Subject: [PATCH 01/48] Add initial style checker application structure --- codewof/config/settings/base.py | 1 + codewof/config/urls.py | 1 + codewof/style/__init__.py | 1 + codewof/style/apps.py | 9 +++++++++ codewof/style/urls.py | 10 ++++++++++ codewof/style/views.py | 12 ++++++++++++ codewof/templates/style/home.html | 13 +++++++++++++ requirements/base.txt | 3 +++ 8 files changed, 50 insertions(+) create mode 100644 codewof/style/__init__.py create mode 100644 codewof/style/apps.py create mode 100644 codewof/style/urls.py create mode 100644 codewof/style/views.py create mode 100644 codewof/templates/style/home.html diff --git a/codewof/config/settings/base.py b/codewof/config/settings/base.py index b95776d27..4113cac39 100644 --- a/codewof/config/settings/base.py +++ b/codewof/config/settings/base.py @@ -124,6 +124,7 @@ 'users.apps.UsersAppConfig', 'programming.apps.ProgrammingConfig', 'research.apps.ResearchConfig', + 'style.apps.StyleAppConfig', ] # https://docs.djangoproject.com/en/dev/ref/settings/#installed-apps INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS diff --git a/codewof/config/urls.py b/codewof/config/urls.py index 85eb2a8d4..75e69dd4f 100644 --- a/codewof/config/urls.py +++ b/codewof/config/urls.py @@ -13,6 +13,7 @@ path('', include('general.urls', namespace='general')), path(settings.ADMIN_URL, admin.site.urls), path('research/', include('research.urls', namespace='research')), + path('style/', include('style.urls', namespace='style')), path('users/', include('users.urls', namespace='users'),), path('accounts/', include('allauth.urls')), path('', include('programming.urls', namespace='programming'),), diff --git a/codewof/style/__init__.py b/codewof/style/__init__.py new file mode 100644 index 000000000..da0d7c0d0 --- /dev/null +++ b/codewof/style/__init__.py @@ -0,0 +1 @@ +"""Module for style checking application.""" diff --git a/codewof/style/apps.py b/codewof/style/apps.py new file mode 100644 index 000000000..5c8920ff7 --- /dev/null +++ b/codewof/style/apps.py @@ -0,0 +1,9 @@ +"""Application configuration for style application.""" + +from django.apps import AppConfig + + +class StyleAppConfig(AppConfig): + """Application configuration for style application.""" + + name = 'style' diff --git a/codewof/style/urls.py b/codewof/style/urls.py new file mode 100644 index 000000000..e094a9091 --- /dev/null +++ b/codewof/style/urls.py @@ -0,0 +1,10 @@ +"""URL routing for style application.""" + +from django.urls import path +from . import views + +app_name = 'style' +urlpatterns = [ + path('', views.HomeView.as_view(), name='home'), + # path('/', views.LanguageView.as_view(), name='language'), +] diff --git a/codewof/style/views.py b/codewof/style/views.py new file mode 100644 index 000000000..f04d1944d --- /dev/null +++ b/codewof/style/views.py @@ -0,0 +1,12 @@ +"""Views for style application.""" + +from django.urls import reverse_lazy +from django.views.generic import ( + TemplateView, +) + + +class HomeView(TemplateView): + """View for style homepage.""" + + template_name = 'style/home.html' diff --git a/codewof/templates/style/home.html b/codewof/templates/style/home.html new file mode 100644 index 000000000..529432d66 --- /dev/null +++ b/codewof/templates/style/home.html @@ -0,0 +1,13 @@ +{% extends "base.html" %} + +{% load static %} + +{% block title %}Style Checker{% endblock %} + +{% block page_heading %} +

Style Checker

+{% endblock page_heading %} + +{% block content %} +

Currently no style checkers are available.

+{% endblock content %} diff --git a/requirements/base.txt b/requirements/base.txt index 06c014480..f7528d437 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -29,3 +29,6 @@ google-api-python-client==1.7.11 verto==0.10.0 python-markdown-math==0.6 PyYAML==5.1.2 + +# Style checker +pycodestyle==2.5.0 From 84bbc6a1bc0813701e27d4f71ba0236b22813f25 Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Tue, 10 Dec 2019 09:48:55 +1300 Subject: [PATCH 02/48] Add editor for style checker --- codewof/static/js/style_checkers/python.js | 38 ++++++++++++++++++ codewof/style/urls.py | 2 +- codewof/style/views.py | 19 +++++++++ codewof/templates/base.html | 8 ++-- codewof/templates/style/python.html | 46 ++++++++++++++++++++++ 5 files changed, 108 insertions(+), 5 deletions(-) create mode 100644 codewof/static/js/style_checkers/python.js create mode 100644 codewof/templates/style/python.html diff --git a/codewof/static/js/style_checkers/python.js b/codewof/static/js/style_checkers/python.js new file mode 100644 index 000000000..b0ac06023 --- /dev/null +++ b/codewof/static/js/style_checkers/python.js @@ -0,0 +1,38 @@ +var CodeMirror = require('codemirror'); +require('codemirror/mode/python/python.js'); + +var EXAMPLE_CODE = `def fizzbuzz(): + for i in range(1 ,100): + if i % 3 == 0 and i % 5 == 0 : + print("FizzBuzz") + elif i%3 == 0: + print( "Fizz") + elif i % 5==0: + print("Buzz") + + else: + print(i)`; + +$(document).ready(function () { + var editor = CodeMirror.fromTextArea(document.getElementById("code"), { + mode: { + name: "python", + version: 3, + singleLineStringErrors: false + }, + lineNumbers: true, + textWrapping: false, + styleActiveLine: true, + autofocus: true, + indentUnit: 4, + viewportMargin: Infinity + }); + + $('#load_example_btn').click(function () { + editor.setValue(EXAMPLE_CODE); + }); + + $('#clear_btn').click(function () { + editor.setValue(""); + }); +}); diff --git a/codewof/style/urls.py b/codewof/style/urls.py index e094a9091..abfa10bb3 100644 --- a/codewof/style/urls.py +++ b/codewof/style/urls.py @@ -6,5 +6,5 @@ app_name = 'style' urlpatterns = [ path('', views.HomeView.as_view(), name='home'), - # path('/', views.LanguageView.as_view(), name='language'), + path('/', views.LanguageStyleCheckerView.as_view(), name='language'), ] diff --git a/codewof/style/views.py b/codewof/style/views.py index f04d1944d..1d3a86fb3 100644 --- a/codewof/style/views.py +++ b/codewof/style/views.py @@ -1,12 +1,31 @@ """Views for style application.""" from django.urls import reverse_lazy +from django.http import Http404 +from django.template.loader import get_template +from django.template import TemplateDoesNotExist from django.views.generic import ( TemplateView, ) +LANGUAGE_PATH_TEMPLATE = 'style/{}.html' + class HomeView(TemplateView): """View for style homepage.""" template_name = 'style/home.html' + + +class LanguageStyleCheckerView(TemplateView): + """View for a language style checker.""" + + def get_template_names(self): + language_slug = self.kwargs.get('language', '') + template_path = LANGUAGE_PATH_TEMPLATE.format(language_slug) + try: + get_template(template_path) + except TemplateDoesNotExist: + raise Http404 + else: + return [template_path] diff --git a/codewof/templates/base.html b/codewof/templates/base.html index 19db2b69b..05841e3ba 100644 --- a/codewof/templates/base.html +++ b/codewof/templates/base.html @@ -98,10 +98,10 @@
{% block content_container %} -
- {% block content %} - {% endblock content %} -
+
+ {% block content %} + {% endblock content %} +
{% endblock content_container %}
diff --git a/codewof/templates/style/python.html b/codewof/templates/style/python.html new file mode 100644 index 000000000..f42af1ed4 --- /dev/null +++ b/codewof/templates/style/python.html @@ -0,0 +1,46 @@ +{% extends "base.html" %} + +{% load static %} + +{% block title %}Python Style Checker{% endblock %} + +{% block page_heading %} +

Python Style Checker

+{% endblock page_heading %} + +{% block content_container %} +
+

+ This style checker will check your code against the PEP 8 Python Style Guide. +

+
+
+

Your code

+
+ +
+
+ + + +
+
+
+

Style errors found

+

+ + Run the style checker and any errors will appear here. + +

+
+{% endblock content_container %} + +{% block scripts %} + +{% endblock scripts %} From a922d70e58670b63d537931b71b1660583468c56 Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Tue, 10 Dec 2019 09:58:52 +1300 Subject: [PATCH 03/48] Add Python logo --- codewof/static/scss/website.scss | 15 +++++++++++++++ codewof/static/svg/devicon-python.svg | 2 ++ codewof/templates/style/python.html | 11 +++++++++-- 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 codewof/static/svg/devicon-python.svg diff --git a/codewof/static/scss/website.scss b/codewof/static/scss/website.scss index 343b53f16..f888a6b0b 100644 --- a/codewof/static/scss/website.scss +++ b/codewof/static/scss/website.scss @@ -197,6 +197,21 @@ strong { .img-y5 { max-height: 5rem; } +.img-x1 { + max-width: 1rem; +} +.img-x2 { + max-width: 2rem; +} +.img-x3 { + max-width: 3rem; +} +.img-x4 { + max-width: 4rem; +} +.img-x5 { + max-width: 5rem; +} //////////////////////////////// //Variables// //////////////////////////////// diff --git a/codewof/static/svg/devicon-python.svg b/codewof/static/svg/devicon-python.svg new file mode 100644 index 000000000..9d6cfe612 --- /dev/null +++ b/codewof/static/svg/devicon-python.svg @@ -0,0 +1,2 @@ + + diff --git a/codewof/templates/style/python.html b/codewof/templates/style/python.html index f42af1ed4..55918fe30 100644 --- a/codewof/templates/style/python.html +++ b/codewof/templates/style/python.html @@ -1,11 +1,18 @@ {% extends "base.html" %} -{% load static %} +{% load static svg %} {% block title %}Python Style Checker{% endblock %} {% block page_heading %} -

Python Style Checker

+
+
+ {% svg 'devicon-python' %} +
+

+ Python Style Checker +

+
{% endblock page_heading %} {% block content_container %} From 2f2d656f0e117f7f592be36f4f39ae73e1a3a506 Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Wed, 12 Feb 2020 14:09:07 +1300 Subject: [PATCH 04/48] Check for python style errors with flake8 --- .gitignore | 3 + codewof/config/settings/base.py | 2 + codewof/static/js/style_checkers/python.js | 38 +- codewof/style/style_checkers/flake8.ini | 5 + codewof/style/style_checkers/python.py | 110 ++++ codewof/style/style_checkers/python_data.py | 553 ++++++++++++++++++ codewof/style/urls.py | 1 + codewof/style/views.py | 29 +- .../style/component/style_error.html | 18 + codewof/templates/style/python.html | 16 +- requirements/base.txt | 3 +- requirements/local.txt | 2 +- 12 files changed, 769 insertions(+), 11 deletions(-) create mode 100644 codewof/style/style_checkers/flake8.ini create mode 100644 codewof/style/style_checkers/python.py create mode 100644 codewof/style/style_checkers/python_data.py create mode 100644 codewof/templates/style/component/style_error.html diff --git a/.gitignore b/.gitignore index d55d7fd62..5b7614c2e 100644 --- a/.gitignore +++ b/.gitignore @@ -273,3 +273,6 @@ pip-selfcheck.json ### Project template codewof/media/ + +### Tem files +codewof/temp/ diff --git a/codewof/config/settings/base.py b/codewof/config/settings/base.py index 4113cac39..6fa49b05b 100644 --- a/codewof/config/settings/base.py +++ b/codewof/config/settings/base.py @@ -388,6 +388,8 @@ SVG_DIRS = [ os.path.join(str(STATIC_ROOT), 'svg') ] +STYLE_CHECKER_TEMP_FILES_ROOT = os.path.join(str(ROOT_DIR), 'temp', 'style') +STYLE_CHECKER_PYTHON3_SETTINGS = os.path.join(str(ROOT_DIR), 'style', 'style_checkers', 'flake8.ini') # reCAPTCHA # ------------------------------------------------------------------------------ diff --git a/codewof/static/js/style_checkers/python.js b/codewof/static/js/style_checkers/python.js index b0ac06023..c05310b55 100644 --- a/codewof/static/js/style_checkers/python.js +++ b/codewof/static/js/style_checkers/python.js @@ -1,3 +1,4 @@ +var editor; var CodeMirror = require('codemirror'); require('codemirror/mode/python/python.js'); @@ -14,7 +15,7 @@ var EXAMPLE_CODE = `def fizzbuzz(): print(i)`; $(document).ready(function () { - var editor = CodeMirror.fromTextArea(document.getElementById("code"), { + editor = CodeMirror.fromTextArea(document.getElementById("code"), { mode: { name: "python", version: 3, @@ -27,12 +28,45 @@ $(document).ready(function () { indentUnit: 4, viewportMargin: Infinity }); + var CSRF_TOKEN = jQuery("[name=csrfmiddlewaretoken]").val(); $('#load_example_btn').click(function () { + clear(); editor.setValue(EXAMPLE_CODE); }); $('#clear_btn').click(function () { - editor.setValue(""); + clear(); + }); + + $('#check_btn').click(function () { + $('#run-checker-result').text('Loading...'); + $.ajax({ + url: '/style/ajax/check/', + type: 'POST', + method: 'POST', + data: JSON.stringify({ + user_code: editor.getValue(), + language: 'python3', + }), + contentType: 'application/json; charset=utf-8', + headers: { 'X-CSRFToken': CSRF_TOKEN }, + dataType: 'json', + success: display_style_checker_results, + }); }); }); + + +function display_style_checker_results(data, textStatus, jqXHR) { + $('#run-checker-result').empty(); + data['feedback_html'].forEach(function (line_html, index) { + style_error = $(line_html); + $('#run-checker-result').append(style_error); + }); +} + +function clear() { + editor.setValue(""); + $('#run-checker-result').empty(); +} diff --git a/codewof/style/style_checkers/flake8.ini b/codewof/style/style_checkers/flake8.ini new file mode 100644 index 000000000..11911da2f --- /dev/null +++ b/codewof/style/style_checkers/flake8.ini @@ -0,0 +1,5 @@ +[flake8] +show_source = False +; statistics = True +; ignore = E111 +count = True diff --git a/codewof/style/style_checkers/python.py b/codewof/style/style_checkers/python.py new file mode 100644 index 000000000..c94f08f24 --- /dev/null +++ b/codewof/style/style_checkers/python.py @@ -0,0 +1,110 @@ +import re +import os.path +import uuid +import subprocess +from pathlib import Path +from django.conf import settings +from django.template.loader import render_to_string +from style.style_checkers import python_data + + +LINE_RE = re.compile(r':(?P\d+):(?P\d+): (?P\w\d+) (?P.*)$') +CHARACTER_RE = re.compile(r'\'(?P.*)\'') +TEMP_FILE_ROOT = settings.STYLE_CHECKER_TEMP_FILES_ROOT +TEMP_FILE_EXT = '.py' +# Create folder if it does not exist +Path(TEMP_FILE_ROOT).mkdir(parents=True, exist_ok=True) + +def python_style_check(code): + # Write file to HDD + filename = str(uuid.uuid4()) + TEMP_FILE_EXT + filepath = Path(os.path.join(TEMP_FILE_ROOT, filename)) + f = open(filepath, 'w') + f.write(code) + f.close() + + # Read file with flake8 + checker_result = subprocess.run( + [ + '/docker_venv/bin/flake8', + filepath, + '--config=' + settings.STYLE_CHECKER_PYTHON3_SETTINGS, + ], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + + # Process results + result_text = checker_result.stdout.decode('utf-8') + print(result_text) + result_data = process_results(result_text) + + # Delete file from HDD + filepath.unlink() + + # Send results + return result_data + + +def process_results(result_text): + """Process results into data for response. + + Args: + result_text (str): Text output from style checker. + + Returns: + List of dictionaries of result data. + """ + feedback_html = [] + for line in result_text.split('\n'): + line_html = process_line(line) + if line_html: + feedback_html.append(line_html) + result_data = { + 'feedback_html': feedback_html, + 'error_count': len(feedback_html), + } + return result_data + + +def process_line(line_text): + line_html = '' + re_result = re.search(LINE_RE, line_text) + if re_result: + line_number = re_result.group('line') + char_number = re_result.group('character') + error_code = re_result.group('error_code') + error_message = re_result.group('error_message') + error_data = python_data.PYTHON_ERRORS.get(error_code) + print(error_data) + # Check if message requires rendering + if error_data.get('templated'): + error_title = render_title(error_data, error_message) + else: + error_title = error_data['title'] + print(error_title) + line_html = render_to_string( + 'style/component/style_error.html', + { + 'pep8_code': error_code, + 'title': error_title, + 'line_number': line_number, + 'explanation': error_data['explanation'], + } + ) + return line_html + + +def render_title(error_data, error_message): + template = error_data['title'] + # Find character + re_result = re.search(CHARACTER_RE, error_message) + character = re_result.group('character') + character_description = python_data.CHARACTER_DESCRIPTIONS[character] + template_data = { + 'character': character, + 'character_description': character_description, + 'article': python_data.get_article(character_description), + } + title = template.format(**template_data) + return title diff --git a/codewof/style/style_checkers/python_data.py b/codewof/style/style_checkers/python_data.py new file mode 100644 index 000000000..4f82cb7d4 --- /dev/null +++ b/codewof/style/style_checkers/python_data.py @@ -0,0 +1,553 @@ +PYTHON_ERRORS = { + "E101": { + "original_message": "indentation contains mixed spaces and tabs", + "templated": False, + "title": "This line is indented using a mixture of spaces and tabs.", + "solution": "You should indent your code using only spaces.", + "explanation": "Python expects the indentation method to be consistent line to line. Spaces are the preferred indentation method." + }, + "E111": { + "original_message": "indentation is not a multiple of four", + "templated": False, + "title": "This line has an indentation level that is not a multiple of four.", + "solution": "Ensure that the first indentation level is 4 spaces, the second indentation level is 8 spaces and so on.", + "explanation": "" + }, + "E112": { + "original_message": "expected an indented block", + "templated": False, + "title": "This line is not indented at the correct level.", + "solution": "Add indentation to this line until it is indented at the correct level.", + "explanation": "" + }, + "E113": { + "original_message": "unexpected indentation", + "templated": False, + "title": "This line is indented when it shouldn't be.", + "solution": "Remove indentation from this line until it is indented at the correct level.", + "explanation": "" + }, + "E114": { + "original_message": "indentation is not a multiple of four (comment)", + "templated": False, + "title": "This line has an indentation level that is not a multiple of four.", + "solution": "Ensure that the first indentation level is 4 spaces, the second indentation level is 8 spaces and so on.", + "explanation": "" + }, + "E115": { + "original_message": "expected an indented block (comment)", + "templated": False, + "title": "This line is not indented at the correct level.", + "solution": "Add indentation to this line until it is indented at the correct level.", + "explanation": "Comments should be indented relative to the code block they are in." + }, + "E116": { + "original_message": "unexpected indentation (comment)", + "templated": False, + "title": "This line is indented when it shouldn't be.", + "solution": "Remove indentation from this line until it is indented at the correct level.", + "explanation": "" + }, + "E117": { + "original_message": "over-indented", + "templated": False, + "title": "This line has too many indentation levels.", + "solution": "Remove indentation from this line until it is indented at the correct level.", + "explanation": "" + }, + "E121": { + "original_message": "continuation line under-indented for hanging indent", + "templated": False, + "title": "This line is less indented than it should be.", + "solution": "Add indentation to this line until it is indented at the correct level.", + "explanation": "" + }, + "E122": { + "original_message": "continuation line missing indentation or outdented", + "templated": False, + "title": "This line is not indented as far as it should be or is indented too far.", + "solution": "Add or remove indentation levels until it is indented at the correct level.", + "explanation": "" + }, + "E123": { + "original_message": "closing bracket does not match indentation of opening bracket’s line", + "templated": False, + "title": "This line has a closing bracket that does not match the indentation level of the line that the opening bracket started on.", + "solution": "Add or remove indentation of the closing bracket so it matches the indentation of the line that the opening bracket is on.", + "explanation": "" + }, + "E124": { + "original_message": "closing bracket does not match visual indentation", + "templated": False, + "title": "This line has a closing bracket that does not match the indentation of the opening bracket.", + "solution": "Add or remove indentation of the closing bracket so it matches the indentation of the opening bracket.", + "explanation": "" + }, + "E125": { + "original_message": "continuation line with same indent as next logical line", + "templated": False, + "title": "This line has a continuation that should be indented one extra level so that it can be distinguished from the next logical line.", + "solution": "Add an indentation level to the line continuation so that it is indented one more level than the next logical line.", + "explanation": "Continuation lines should not be indented at the same level as the next logical line. Instead, they should be indented to one more level so as to distinguish them from the next line." + }, + "E126": { + "original_message": "continuation line over-indented for hanging indent", + "templated": False, + "title": "This line is indented more than it should be.", + "solution": "Remove indentation from this line until it is indented at the correct level.", + "explanation": "" + }, + "E127": { + "original_message": "continuation line over-indented for visual indent", + "templated": False, + "title": "This line is indented more than it should be.", + "solution": "Remove indentation from this line until it is indented at the correct level.", + "explanation": "" + }, + "E128": { + "original_message": "continuation line under-indented for visual indent", + "templated": False, + "title": "This line is indented less than it should be.", + "solution": "Add indentation to this line until it is indented at the correct level.", + "explanation": "" + }, + "E129": { + "original_message": "visually indented line with same indent as next logical line", + "templated": False, + "title": "This line has the same indentation as the next logical line.", + "solution": "Add an indentation level to the visually indented line so that it is indented one more level than the next logical line.", + "explanation": "A visually indented line that has the same indentation as the next logical line is hard to read." + }, + "E131": { + "original_message": "continuation line unaligned for hanging indent", + "templated": False, + "title": "This line is not aligned correctly for a hanging indent.", + "solution": "Add or remove indentation so that the lines are aligned with each other.", + "explanation": "" + }, + "E133": { + # This contradicts rule E123. We want to enforce E123. + # See https://lintlyci.github.io/Flake8Rules/rules/E133.html + "original_message": "closing bracket is missing indentation", + "templated": False, + "title": "", + "solution": "", + "explanation": "", + }, + "E201": { + "original_message": "whitespace after '{character}'", + "templated": True, + "title": "This line contains {article} {character_description} that has a space after it.", + "solution": "Remove any spaces that appear after the {character} character.", + "explanation": "" + }, + "E202": { + "original_message": "whitespace before '{character}'", + "templated": True, + "title": "This line contains {article} {character_description} that has a space before it.", + "solution": "Remove any spaces that appear before the {character} character.", + "explanation": "" + }, + "E203": { + "original_message": "whitespace before '{character}'", + "templated": True, + "title": "This line contains {article} {character_description} that has a space before it.", + "solution": "Remove any spaces that appear before the {character} character.", + "explanation": "" + }, + "E211": { + "original_message": "whitespace before '{character}'", + "templated": True, + "title": "This line contains {article} {character_description} that has a space before it.", + "solution": "Remove any spaces that appear before the {character} character.", + "explanation": "" + }, +"E221": { +"original_message": "multiple spaces before operator", +"title": "This line has multiple spaces before an operator.", +"solution": "Remove any extra spaces that appear before the operator on this line.", +"explanation": "" +}, +"E222": { +"original_message": "multiple spaces after operator", +"title": "This line has multiple spaces after an operator.", +"solution": "Remove any extra spaces that appear after the operator on this line.", +"explanation": "" +}, +"E223": { +"original_message": "tab before operator", +"title": "This line contains a tab character before an operator.", +"solution": "Remove any tab characters that appear before the operator on this line. Operators should only have one space before them.", +"explanation": "" +}, +"E224": { +"original_message": "tab after operator", +"title": "This line contains a tab character after an operator.", +"solution": "Remove any tab characters that appear after the operator on this line. Operators should only have one space after them.", +"explanation": "" +}, +"E225": { +"original_message": "missing whitespace around operator", +"title": "This line is missing whitespace around an operator.", +"solution": "Ensure there is one space before and after all operators.", +"explanation": "" +}, +"E226": { +"original_message": "missing whitespace around arithmetic operator", +"title": "This line is missing whitespace around an arithmetic operator (+, -, / and *).", +"solution": "Ensure there is one space before and after all arithmetic operators (+, -, / and *).", +"explanation": "" +}, +"E227": { +"original_message": "missing whitespace around bitwise or shift operator", +"title": "This line is missing whitespace around a bitwise or shift operator (<<, >>, &, |, ^).", +"solution": "Ensure there is one space before and after all bitwise and shift operators (<<, >>, &, |, ^).", +"explanation": "" +}, +"E228": { +"original_message": "missing whitespace around modulo operator", +"title": "This line is missing whitespace around a modulo operator (%).", +"solution": "Ensure there is one space before and after the modulo operator (%).", +"explanation": "" +}, +"E231": { +"original_message": "missing whitespace after ‘,’, ‘;’, or ‘:’", +"title": "This line is missing whitespace around one of the following characters: , ; and :.", +"solution": "Ensure there is one space before and after any of the following characters: , ; and :.", +"explanation": "" +}, +"E241": { +"original_message": "multiple spaces after ‘,’", +"title": "This line has multiple spaces after the ',' character.", +"solution": "Ensure there is one space before and after any ',' characters.", +"explanation": "" +}, +"E242": { +"original_message": "tab after ‘,’", +"title": "This line contains a tab character after the ',' character.", +"solution": "Remove any tab characters and ensure there is one space before and after any ',' characters.", +"explanation": "" +}, +"E251": { +"original_message": "unexpected spaces around keyword / parameter equals", +"title": "This line contains spaces before or after the = in a function definition.", +"solution": "Remove any spaces that appear either before or after the = character in your function definition.", +"explanation": "" +}, +"E261": { +"original_message": "at least two spaces before inline comment", +"title": "This line contains an inline comment that does not have 2 spaces before it.", +"solution": "Ensure that your inline comment has 2 spaces before the '#' character.", +"explanation": "" +}, +"E262": { +"original_message": "inline comment should start with ‘# ‘", +"title": "Comments should start with a '#' character and have one space between the '#' character and the comment itself.", +"solution": "Ensure that your inline comment starts with a '#' character followed by a space and then the comment itself.", +"explanation": "https://lintlyci.github.io/Flake8Rules/rules/E262.html this rule seems to be more about the space after the pound sign though????" +}, +"E265": { +"original_message": "block comment should start with ‘# ‘", +"title": "Comments should start with a '#' character and have one space between the '#' character and the comment itself.", +"solution": "Ensure that your block comment starts with a '#' character followed by a space and then the comment itself.", +"explanation": "" +}, +"E266": { +"original_message": "too many leading ‘#’ for block comment", +"title": "Comments should only start with a single '#' character.", +"solution": "Ensure your comment only starts with one '#' character.", +"explanation": "" +}, +"E271": { +"original_message": "multiple spaces after keyword", +"title": "This line contains more than one space after a keyword.", +"solution": "Ensure there is only one space after any keywords.", +"explanation": "" +}, +"E272": { +"original_message": "multiple spaces before keyword", +"title": "This line contains more than one space before a keyword.", +"solution": "Ensure there is only one space before any keywords.", +"explanation": "" +}, +"E273": { +"original_message": "tab after keyword", +"title": "This line contains a tab character after a keyword.", +"solution": "Ensure there is only one space after any keywords.", +"explanation": "" +}, +"E274": { +"original_message": "tab before keyword", +"title": "This line contains a tab character before a keyword.", +"solution": "Ensure there is only one space before any keywords.", +"explanation": "" +}, +"E275": { +"original_message": "missing whitespace after keyword", +"title": "This line is missing a space after a keyword.", +"solution": "Ensure there is one space after any keywords.", +"explanation": "" +}, +"E301": { +"original_message": "expected 1 blank line, found 0", +"title": "This line is missing a blank line between the methods of a class.", +"solution": "Add a blank line in between your class methods.", +"explanation": "" +}, +"E302": { +"original_message": "expected 2 blank lines, found 0", +"title": "Two blank lines are expected between functions and classes.", +"solution": "Ensure there are two blank lines between functions and classes.", +"explanation": "" +}, +"E303": { +"original_message": "too many blank lines (3)", +"title": "There are too many blank lines.", +"solution": "Ensure there are two blank lines between functions and classes and one blank line between methods of a class.", +"explanation": "" +}, +"E304": { +"original_message": "blank lines found after function decorator", +"title": "This line contains a blank line after a function decorator.", +"solution": "Ensure that there are no blank lines between a function decorator and the function it is decorating.", +"explanation": "" +}, +"E305": { +"original_message": "expected 2 blank lines after end of function or class", +"title": "Functions and classes should have two blank lines after them.", +"solution": "Ensure that functions and classes should have two blank lines after them.", +"explanation": "" +}, +"E306": { +"original_message": "expected 1 blank line before a nested definition", +"title": "Nested definitions should have one blank line before them.", +"solution": "Ensure there is a blank line above your nested definition.", +"explanation": "" +}, +"E401": { +"original_message": "multiple imports on one line", +"title": "This line contains imports from different modules on the same line.", +"solution": "Ensure import statements from different modules occur on their own line.", +"explanation": "" +}, +"E402": { +"original_message": "module level import not at top of file", +"title": "Module imports should be at the top of the file and there should be no statements in between module level imports.", +"solution": "Ensure all import statements are at the top of the file and there are no statements in between imports.", +"explanation": "" +}, +"E501": { +"original_message": "line too long (82 > 79 characters)", +"title": "This line is longer than 79 characters.", +"solution": "You can insert a backslash character (\\) to wrap the text onto the next line.", +"explanation": "Limiting the line width makes it possible to have several files open side-by-side, and works well when using code review tools that present the two versions in adjacent columns." +}, +"E502": { +"original_message": "the backslash is redundant between brackets", +"title": "There is no need for a backslash (\\) between brackets.", +"solution": "Remove any backslashes between brackets.", +"explanation": "" +}, +"E701": { +"original_message": "multiple statements on one line (colon)", +"title": "This line contains multiple statements.", +"solution": "Make sure that each statement is on its own line.", +"explanation": "This improves readability." +}, +"E702": { +"original_message": "multiple statements on one line (semicolon)", +"title": "This line contains multiple statements.", +"solution": "Make sure that each statement is on its own line.", +"explanation": "This improves readability." +}, +"E703": { +"original_message": "statement ends with a semicolon", +"title": "This line ends in a semicolon (;).", +"solution": "Remove the semicolon from the end of the line.", +"explanation": "" +}, +"E704": { +"original_message": "multiple statements on one line (def)", +"title": "This line contains multiple statements.", +"solution": "Make sure multiple statements of a function definition are on their own separate lines.", +"explanation": "" +}, +"E711": { +"original_message": "comparison to None should be ‘if cond is None:’", +"title": "Comparisons to objects such as True, False and None should use 'is' or 'is not' instead of '==' and '!='.", +"solution": "Replace != with 'is not' and '==' with 'is'.", +"explanation": "" +}, +"E712": { +"original_message": "comparison to True should be ‘if cond is True:’ or ‘if cond:’", +"title": "Comparisons to objects such as True, False and None should use 'is' or 'is not' instead of '==' and '!='.", +"solution": "Replace != with 'is not' and '==' with 'is'.", +"explanation": "" +}, +"E713": { +"original_message": "test for membership should be ‘not in’", +"title": "When testing whether or not something is in an object use the form `x not in the_object` instead of `not x in the_object`.", +"solution": "Use the form `not x in the_object` instead of `x not in the_object`.", +"explanation": "This improves readability." +}, +"E714": { +"original_message": "test for object identity should be ‘is not’", +"title": "When testing for object identity use the form `x is not None` rather than `not x is None`.", +"solution": "Use the form `x is not None` rather than `not x is None`.", +"explanation": "This improves readability." +}, +"E721": { +"original_message": "do not compare types, use ‘isinstance()’", +"title": "You should compare an objects type by using `isinstance()` instead of `==`. This is because `isinstance` can handle subclasses as well.", +"solution": "Use `if isinstance(user, User)` instead of `if type(user) == User`.", +"explanation": "" +}, +"E722": { +"original_message": "do not use bare except, specify exception instead", +"title": "", +"solution": "", +"explanation": "" +}, +"E731": { +"original_message": "do not assign a lambda expression, use a def", +"title": "This line assigns a lambda expression instead of defining it as a function using `def`.", +"solution": "", +"explanation": "The primary reason for this is debugging. Lambdas show as in tracebacks, where functions will display the function’s name." +}, +"E741": { +"original_message": "do not use variables named ‘l’, ‘O’, or ‘I’", +"title": "This line uses one of the variables named ‘l’, ‘O’, or ‘I’", +"solution": "Change the names of these variables to something more descriptive.", +"explanation": "Variables named I, O, and l can be very hard to read. This is because the letter I and the letter l are easily confused, and the letter O and the number 0 can be easily confused." +}, +"E742": { +"original_message": "do not define classes named ‘l’, ‘O’, or ‘I’", +"title": "This line contains a class named ‘l’, ‘O’, or ‘I’", +"solution": "Change the names of these classes to something more descriptive.", +"explanation": "Classes named I, O, and l can be very hard to read. This is because the letter I and the letter l are easily confused, and the letter O and the number 0 can be easily confused." +}, +"E743": { +"original_message": "do not define functions named ‘l’, ‘O’, or ‘I’", +"title": "This line contains a function named ‘l’, ‘O’, or ‘I’", +"solution": "Change the names of these functions to something more descriptive.", +"explanation": "Functions named I, O, and l can be very hard to read. This is because the letter I and the letter l are easily confused, and the letter O and the number 0 can be easily confused." +}, +"W191": { +"original_message": "indentation contains tabs", +"title": "This line contains tabs when only spaces are expected.", +"solution": "Replace any tabs in your indentation with spaces.", +"explanation": "" +}, +"W291": { +"original_message": "trailing whitespace", +"title": "This line contains whitespace after the final character.", +"solution": "Remove any extra whitespace at the end of each line.", +"explanation": "" +}, +"W292": { +"original_message": "no newline at end of file", +"title": "Files should end with a newline.", +"solution": "Add a newline to the end of your file.", +"explanation": "" +}, +"W293": { +"original_message": "blank line contains whitespace", +"title": "Blank lines should not contain any tabs or spaces.", +"solution": "Remove any whitespace from blank lines.", +"explanation": "" +}, +"W391": { +"original_message": "blank line at end of file", +"title": "There are either zero, two, or more than two blank lines at the end of your file.", +"solution": "Ensure there is only one blank line at the end of your file.", +"explanation": "" +}, +"W503": { +"original_message": "line break before binary operator", +"title": "", +"solution": "", +"explanation": "This seems contradicitng... https://lintlyci.github.io/Flake8Rules/rules/W503.html" +}, +"W504": { +"original_message": "line break after binary operator", +"title": "", +"solution": "", +"explanation": "same as above https://lintlyci.github.io/Flake8Rules/rules/W504.html" +}, +"W505": { +"original_message": "doc line too long (82 > 79 characters)", +"title": "This line is longer than 79 characters.", +"solution": "You can insert a backslash character (\\) to wrap the text onto the next line.", +"explanation": "" +}, +"W601": { +"original_message": ".has_key() is deprecated, use ‘in’", +"title": ".has_key() was deprecated in Python 2. It is recommended to use the in operator instead.", +"solution": "", +"explanation": "" +}, +"W602": { +"original_message": "deprecated form of raising exception", +"title": "The raise Exception, message form of raising exceptions is no longer supported. Use the new form.", +"solution": "Instead of the form raise ErrorType, 'Error message' use the form raise ErrorType('Error message')", +"explanation": "" +}, +"W603": { +"original_message": "‘<>’ is deprecated, use ‘!=’", +"title": "<> has been removed in Python 3.", +"solution": "Replace any occurences of <> with !=.", +"explanation": "" +}, +"W604": { +"original_message": "backticks are deprecated, use ‘repr()’", +"title": "Backticks have been removed in Python 3.", +"solution": "Use the built-in function repr() instead.", +"explanation": "" +}, +"W605": { +"original_message": "invalid escape sequence ‘x’", +"title": "", +"solution": "", +"explanation": "" +}, +"W606": { +"original_message": "‘async’ and ‘await’ are reserved keywords starting with Python 3.7", +"title": "", +"solution": "", +"explanation": "" +} +} + +CHARACTER_DESCRIPTIONS = { + '(': 'opening bracket', + ')': 'closing bracket', + '[': 'opening square bracket', + ']': 'closing square bracket', + '{': 'opening curly bracket', + '}': 'closing curly bracket', + "'": 'single quote', + '"': 'double quote', + ':': 'colon', + ';': 'semicolon', + ' ': 'space', + ',': 'comma', + '.': 'full stop', +} + +def get_article(word): + """Return English article for word. + + Returns 'an' if word starts with vowel. Technically + it should check the word sound, compared to the + letter but this shouldn't occur with our words. + + Args: + word (str): Word to create article for. + + Returns: + 'a' or 'an' (str) depending if word starts with vowel. + """ + if word[0].lower() in 'aeiou': + return 'an' + else: + return 'a' diff --git a/codewof/style/urls.py b/codewof/style/urls.py index abfa10bb3..8017a0620 100644 --- a/codewof/style/urls.py +++ b/codewof/style/urls.py @@ -7,4 +7,5 @@ urlpatterns = [ path('', views.HomeView.as_view(), name='home'), path('/', views.LanguageStyleCheckerView.as_view(), name='language'), + path('ajax/check/', views.check_code, name='check_code'), ] diff --git a/codewof/style/views.py b/codewof/style/views.py index 1d3a86fb3..e978642b6 100644 --- a/codewof/style/views.py +++ b/codewof/style/views.py @@ -1,12 +1,14 @@ """Views for style application.""" +import json from django.urls import reverse_lazy -from django.http import Http404 +from django.http import Http404, JsonResponse from django.template.loader import get_template from django.template import TemplateDoesNotExist from django.views.generic import ( TemplateView, ) +from style.style_checkers.python import python_style_check LANGUAGE_PATH_TEMPLATE = 'style/{}.html' @@ -29,3 +31,28 @@ def get_template_names(self): raise Http404 else: return [template_path] + + +def check_code(request): + """Check the user's code for style errors. + + Args: + request (Request): AJAX request from user. + + Returns: + JSON response with result. + """ + result = { + 'success': False, + } + if request.is_ajax(): + # TODO: Check submission length + request_json = json.loads(request.body.decode('utf-8')) + user_code = request_json['user_code'] + language = request_json['language'] + if language == 'python3': + checker_result = python_style_check(user_code) + result['success'] = True + result.update(checker_result) + # else raise error language isn't supported + return JsonResponse(result) diff --git a/codewof/templates/style/component/style_error.html b/codewof/templates/style/component/style_error.html new file mode 100644 index 000000000..df3c8b0d5 --- /dev/null +++ b/codewof/templates/style/component/style_error.html @@ -0,0 +1,18 @@ +
+
+ + Line {{ line_number }} + +
+ + {{ pep8_code }} + + {{ title }} +
+ {% if explanation %} +

+ {{ explanation }} +

+ {% endif %} +
+
diff --git a/codewof/templates/style/python.html b/codewof/templates/style/python.html index 55918fe30..43ea02ced 100644 --- a/codewof/templates/style/python.html +++ b/codewof/templates/style/python.html @@ -33,21 +33,25 @@

Your code

-

Style errors found

-

- - Run the style checker and any errors will appear here. - -

+
+

+ + Run the style checker and any errors will appear here. + +

+
{% endblock content_container %} {% block scripts %} + {{ block.super }} + {% csrf_token %} {% endblock scripts %} diff --git a/requirements/base.txt b/requirements/base.txt index f7528d437..d0f17efca 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -31,4 +31,5 @@ python-markdown-math==0.6 PyYAML==5.1.2 # Style checker -pycodestyle==2.5.0 +# Replace flake8 dependency once new release is created +git+git://github.com/PyCQA/flake8.git@20906d43046096c31dfcd9b8bc536dbd21f043ef diff --git a/requirements/local.txt b/requirements/local.txt index db12bc04b..fdf2ac570 100644 --- a/requirements/local.txt +++ b/requirements/local.txt @@ -11,7 +11,7 @@ pytest-sugar==0.9.2 # https://github.com/Frozenball/pytest-sugar # Code quality # ------------------------------------------------------------------------------ -flake8==3.7.8 # https://github.com/PyCQA/flake8 +# flake8 is installed as dependency in base.txt coverage==4.5.4 # https://github.com/nedbat/coveragepy pydocstyle==4.0.1 From 6bcbdd07071d16218a5b7d35e4449f614969efd3 Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Thu, 13 Feb 2020 10:57:48 +1300 Subject: [PATCH 05/48] Render all HTML server side and highlight lines --- codewof/static/js/style_checkers/python.js | 30 +++++++++-- codewof/static/scss/style_checker.scss | 10 ++++ codewof/style/style_checkers/python.py | 54 +++++++++---------- codewof/style/style_checkers/python_data.py | 4 +- codewof/style/views.py | 4 +- .../style/component/feedback_result.html | 28 ++++++++++ .../style/component/style_error.html | 18 ------- codewof/templates/style/python.html | 19 +++---- 8 files changed, 103 insertions(+), 64 deletions(-) create mode 100644 codewof/static/scss/style_checker.scss create mode 100644 codewof/templates/style/component/feedback_result.html delete mode 100644 codewof/templates/style/component/style_error.html diff --git a/codewof/static/js/style_checkers/python.js b/codewof/static/js/style_checkers/python.js index c05310b55..997fdcb9c 100644 --- a/codewof/static/js/style_checkers/python.js +++ b/codewof/static/js/style_checkers/python.js @@ -2,6 +2,7 @@ var editor; var CodeMirror = require('codemirror'); require('codemirror/mode/python/python.js'); +var HIGHLIGHT_CLASS = 'style-highlight'; var EXAMPLE_CODE = `def fizzbuzz(): for i in range(1 ,100): if i % 3 == 0 and i % 5 == 0 : @@ -55,17 +56,36 @@ $(document).ready(function () { success: display_style_checker_results, }); }); + + $('#run-checker-result').on('click', 'div[data-line-number]', function () { + toggle_highlight($(this), true); + }); }); function display_style_checker_results(data, textStatus, jqXHR) { - $('#run-checker-result').empty(); - data['feedback_html'].forEach(function (line_html, index) { - style_error = $(line_html); - $('#run-checker-result').append(style_error); - }); + $('#run-checker-result').html(data['feedback_html']); } + +function toggle_highlight(issue_button, remove_existing) { + var line_number = issue_button.data('line-number') - 1; + if (issue_button.hasClass(HIGHLIGHT_CLASS)) { + editor.removeLineClass(line_number, 'background', HIGHLIGHT_CLASS); + issue_button.removeClass(HIGHLIGHT_CLASS); + } else { + // Remove existing highlights + if (remove_existing) { + $('div[data-line-number].' + HIGHLIGHT_CLASS).each(function () { + toggle_highlight($(this), false); + }); + } + issue_button.addClass(HIGHLIGHT_CLASS); + editor.addLineClass(line_number, 'background', HIGHLIGHT_CLASS); + } +} + + function clear() { editor.setValue(""); $('#run-checker-result').empty(); diff --git a/codewof/static/scss/style_checker.scss b/codewof/static/scss/style_checker.scss new file mode 100644 index 000000000..bc0f8e63a --- /dev/null +++ b/codewof/static/scss/style_checker.scss @@ -0,0 +1,10 @@ +$highlight_colour: #ffeb3b; + +.style-highlight { + background-color: $highlight_colour; +} + +.issue-card { + user-select: none; + cursor: pointer; +} diff --git a/codewof/style/style_checkers/python.py b/codewof/style/style_checkers/python.py index c94f08f24..8d4b70b64 100644 --- a/codewof/style/style_checkers/python.py +++ b/codewof/style/style_checkers/python.py @@ -36,7 +36,6 @@ def python_style_check(code): # Process results result_text = checker_result.stdout.decode('utf-8') - print(result_text) result_data = process_results(result_text) # Delete file from HDD @@ -55,20 +54,24 @@ def process_results(result_text): Returns: List of dictionaries of result data. """ - feedback_html = [] + issues = [] for line in result_text.split('\n'): - line_html = process_line(line) - if line_html: - feedback_html.append(line_html) - result_data = { - 'feedback_html': feedback_html, - 'error_count': len(feedback_html), - } - return result_data + issue_data = process_line(line) + if issue_data: + issues.append(issue_data) + # TODO: Check for at least one comment + result_html = render_to_string( + 'style/component/feedback_result.html', + { + 'issues': issues, + 'issue_count': len(issues), + } + ) + return result_html def process_line(line_text): - line_html = '' + issue_data = dict() re_result = re.search(LINE_RE, line_text) if re_result: line_number = re_result.group('line') @@ -76,28 +79,23 @@ def process_line(line_text): error_code = re_result.group('error_code') error_message = re_result.group('error_message') error_data = python_data.PYTHON_ERRORS.get(error_code) - print(error_data) - # Check if message requires rendering if error_data.get('templated'): - error_title = render_title(error_data, error_message) + error_title = render_text(error_data['title'], error_message) + error_solution = render_text(error_data['solution'], error_message) else: error_title = error_data['title'] - print(error_title) - line_html = render_to_string( - 'style/component/style_error.html', - { - 'pep8_code': error_code, - 'title': error_title, - 'line_number': line_number, - 'explanation': error_data['explanation'], - } - ) - return line_html + error_solution = error_data['solution'] + issue_data = { + 'pep8_code': error_code, + 'title': error_title, + 'line_number': line_number, + 'solution': error_solution, + 'explanation': error_data['explanation'], + } + return issue_data -def render_title(error_data, error_message): - template = error_data['title'] - # Find character +def render_text(template, error_message): re_result = re.search(CHARACTER_RE, error_message) character = re_result.group('character') character_description = python_data.CHARACTER_DESCRIPTIONS[character] diff --git a/codewof/style/style_checkers/python_data.py b/codewof/style/style_checkers/python_data.py index 4f82cb7d4..8133a89a3 100644 --- a/codewof/style/style_checkers/python_data.py +++ b/codewof/style/style_checkers/python_data.py @@ -542,10 +542,10 @@ def get_article(word): letter but this shouldn't occur with our words. Args: - word (str): Word to create article for. + word (str): Word to create article for. Returns: - 'a' or 'an' (str) depending if word starts with vowel. + 'a' or 'an' (str) depending if word starts with vowel. """ if word[0].lower() in 'aeiou': return 'an' diff --git a/codewof/style/views.py b/codewof/style/views.py index e978642b6..434fbe943 100644 --- a/codewof/style/views.py +++ b/codewof/style/views.py @@ -51,8 +51,8 @@ def check_code(request): user_code = request_json['user_code'] language = request_json['language'] if language == 'python3': - checker_result = python_style_check(user_code) + result_html = python_style_check(user_code) result['success'] = True - result.update(checker_result) + result['feedback_html'] = result_html # else raise error language isn't supported return JsonResponse(result) diff --git a/codewof/templates/style/component/feedback_result.html b/codewof/templates/style/component/feedback_result.html new file mode 100644 index 000000000..693ad62b4 --- /dev/null +++ b/codewof/templates/style/component/feedback_result.html @@ -0,0 +1,28 @@ +{% if issue_count %} +

{{ issue_count }} style issue{{ issue_count|pluralize }} found

+ + {% for issue in issues %} +
+
+ + Line {{ issue.line_number }} + - {{ issue.pep8_code }} + + + +
+ {{ issue.solution|safe }} +
+ {% if issue.explanation %} +
+ {{ issue.explanation }} +
+ {% endif %} +
+
+ {% endfor %} +{% else %} +

No style issues found!

+{% endif %} diff --git a/codewof/templates/style/component/style_error.html b/codewof/templates/style/component/style_error.html deleted file mode 100644 index df3c8b0d5..000000000 --- a/codewof/templates/style/component/style_error.html +++ /dev/null @@ -1,18 +0,0 @@ -
-
- - Line {{ line_number }} - -
- - {{ pep8_code }} - - {{ title }} -
- {% if explanation %} -

- {{ explanation }} -

- {% endif %} -
-
diff --git a/codewof/templates/style/python.html b/codewof/templates/style/python.html index 43ea02ced..147dfcbf7 100644 --- a/codewof/templates/style/python.html +++ b/codewof/templates/style/python.html @@ -38,18 +38,19 @@

Your code

-
-

Style errors found

-
-

- - Run the style checker and any errors will appear here. - -

-
+
+

+ + Run the style checker and any errors will appear here. + +

{% endblock content_container %} +{% block css %} + +{% endblock css %} + {% block scripts %} {{ block.super }} {% csrf_token %} From 19374f07a5edd28cd67d26642970e329df373618 Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Thu, 13 Feb 2020 10:59:25 +1300 Subject: [PATCH 06/48] Change term from 'style errors' to 'style issues' --- codewof/style/style_checkers/python.py | 2 +- codewof/style/style_checkers/python_data.py | 2 +- codewof/style/views.py | 2 +- codewof/templates/style/python.html | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/codewof/style/style_checkers/python.py b/codewof/style/style_checkers/python.py index 8d4b70b64..be9a345ec 100644 --- a/codewof/style/style_checkers/python.py +++ b/codewof/style/style_checkers/python.py @@ -78,7 +78,7 @@ def process_line(line_text): char_number = re_result.group('character') error_code = re_result.group('error_code') error_message = re_result.group('error_message') - error_data = python_data.PYTHON_ERRORS.get(error_code) + error_data = python_data.PYTHON_ISSUES.get(error_code) if error_data.get('templated'): error_title = render_text(error_data['title'], error_message) error_solution = render_text(error_data['solution'], error_message) diff --git a/codewof/style/style_checkers/python_data.py b/codewof/style/style_checkers/python_data.py index 8133a89a3..6564bcd5d 100644 --- a/codewof/style/style_checkers/python_data.py +++ b/codewof/style/style_checkers/python_data.py @@ -1,4 +1,4 @@ -PYTHON_ERRORS = { +PYTHON_ISSUES = { "E101": { "original_message": "indentation contains mixed spaces and tabs", "templated": False, diff --git a/codewof/style/views.py b/codewof/style/views.py index 434fbe943..c518a3ef3 100644 --- a/codewof/style/views.py +++ b/codewof/style/views.py @@ -34,7 +34,7 @@ def get_template_names(self): def check_code(request): - """Check the user's code for style errors. + """Check the user's code for style issues. Args: request (Request): AJAX request from user. diff --git a/codewof/templates/style/python.html b/codewof/templates/style/python.html index 147dfcbf7..a6552266c 100644 --- a/codewof/templates/style/python.html +++ b/codewof/templates/style/python.html @@ -34,14 +34,14 @@

Your code

Load example

- Run the style checker and any errors will appear here. + Run the style checker and any issues will appear here.

From e1efdca1d863ae841c314ec46eebf765982f8b9e Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Thu, 13 Feb 2020 11:15:32 +1300 Subject: [PATCH 07/48] Show error message if no simplified version found --- codewof/style/style_checkers/python.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/codewof/style/style_checkers/python.py b/codewof/style/style_checkers/python.py index be9a345ec..3c563fd1d 100644 --- a/codewof/style/style_checkers/python.py +++ b/codewof/style/style_checkers/python.py @@ -78,19 +78,23 @@ def process_line(line_text): char_number = re_result.group('character') error_code = re_result.group('error_code') error_message = re_result.group('error_message') - error_data = python_data.PYTHON_ISSUES.get(error_code) + error_data = python_data.PYTHON_ISSUES.get(error_code, dict()) if error_data.get('templated'): error_title = render_text(error_data['title'], error_message) error_solution = render_text(error_data['solution'], error_message) else: - error_title = error_data['title'] - error_solution = error_data['solution'] + try: + error_title = error_data['title'] + error_solution = error_data['solution'] + except KeyError: + error_title = error_data.get('original_message', error_message) + error_solution = '' issue_data = { 'pep8_code': error_code, 'title': error_title, 'line_number': line_number, 'solution': error_solution, - 'explanation': error_data['explanation'], + 'explanation': error_data.get('explanation', ''), } return issue_data From 7e8a1369d6f8052c722a4d29af951bf70c638ceb Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Thu, 13 Feb 2020 13:45:26 +1300 Subject: [PATCH 08/48] Render all text fields as HTML --- codewof/style/style_checkers/python.py | 3 ++- codewof/templates/style/component/feedback_result.html | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/codewof/style/style_checkers/python.py b/codewof/style/style_checkers/python.py index 3c563fd1d..9f028c2bc 100644 --- a/codewof/style/style_checkers/python.py +++ b/codewof/style/style_checkers/python.py @@ -89,8 +89,9 @@ def process_line(line_text): except KeyError: error_title = error_data.get('original_message', error_message) error_solution = '' + # TODO: Link to https://www.flake8rules.com if available issue_data = { - 'pep8_code': error_code, + 'error_code': error_code, 'title': error_title, 'line_number': line_number, 'solution': error_solution, diff --git a/codewof/templates/style/component/feedback_result.html b/codewof/templates/style/component/feedback_result.html index 693ad62b4..fd1cab268 100644 --- a/codewof/templates/style/component/feedback_result.html +++ b/codewof/templates/style/component/feedback_result.html @@ -5,19 +5,19 @@

{{ issue_count }} style issue{{ issue_count|pluralize }} found

- Line {{ issue.line_number }} - - {{ issue.pep8_code }} + Line {{ issue.line_number }} + - {{ issue.error_code }}
{{ issue.solution|safe }}
{% if issue.explanation %}
- {{ issue.explanation }} + {{ issue.explanation|safe }}
{% endif %}
From 963d329e15621cfbb9596dcfb494b50288e848d3 Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Thu, 13 Feb 2020 13:46:20 +1300 Subject: [PATCH 09/48] Add checkers for docstrings, naming, and quotes --- codewof/static/js/style_checkers/python.js | 7 +- codewof/style/style_checkers/flake8.ini | 9 +- codewof/style/style_checkers/python_data.py | 163 +++++++++++--------- requirements/base.txt | 3 + 4 files changed, 106 insertions(+), 76 deletions(-) diff --git a/codewof/static/js/style_checkers/python.js b/codewof/static/js/style_checkers/python.js index 997fdcb9c..2788be58c 100644 --- a/codewof/static/js/style_checkers/python.js +++ b/codewof/static/js/style_checkers/python.js @@ -3,15 +3,16 @@ var CodeMirror = require('codemirror'); require('codemirror/mode/python/python.js'); var HIGHLIGHT_CLASS = 'style-highlight'; -var EXAMPLE_CODE = `def fizzbuzz(): +var EXAMPLE_CODE = `"""a simple fizzbuzz program.""" + +def fizzbuzz(): for i in range(1 ,100): if i % 3 == 0 and i % 5 == 0 : print("FizzBuzz") elif i%3 == 0: print( "Fizz") elif i % 5==0: - print("Buzz") - + print("Buzz") else: print(i)`; diff --git a/codewof/style/style_checkers/flake8.ini b/codewof/style/style_checkers/flake8.ini index 11911da2f..bcb894ca2 100644 --- a/codewof/style/style_checkers/flake8.ini +++ b/codewof/style/style_checkers/flake8.ini @@ -1,5 +1,8 @@ [flake8] show_source = False -; statistics = True -; ignore = E111 -count = True +statistics = False +count = False +ignore = Q000, Q001, Q002 + +[darglint] +strictness = long diff --git a/codewof/style/style_checkers/python_data.py b/codewof/style/style_checkers/python_data.py index 6564bcd5d..d74eb65c7 100644 --- a/codewof/style/style_checkers/python_data.py +++ b/codewof/style/style_checkers/python_data.py @@ -55,6 +55,7 @@ "solution": "Remove indentation from this line until it is indented at the correct level.", "explanation": "" }, + # E121 ignored by default "E121": { "original_message": "continuation line under-indented for hanging indent", "templated": False, @@ -69,6 +70,7 @@ "solution": "Add or remove indentation levels until it is indented at the correct level.", "explanation": "" }, + # E123 ignored by default "E123": { "original_message": "closing bracket does not match indentation of opening bracket’s line", "templated": False, @@ -90,6 +92,7 @@ "solution": "Add an indentation level to the line continuation so that it is indented one more level than the next logical line.", "explanation": "Continuation lines should not be indented at the same level as the next logical line. Instead, they should be indented to one more level so as to distinguish them from the next line." }, + # E126 ignored by default "E126": { "original_message": "continuation line over-indented for hanging indent", "templated": False, @@ -125,9 +128,8 @@ "solution": "Add or remove indentation so that the lines are aligned with each other.", "explanation": "" }, + # E133 ignored by default "E133": { - # This contradicts rule E123. We want to enforce E123. - # See https://lintlyci.github.io/Flake8Rules/rules/E133.html "original_message": "closing bracket is missing indentation", "templated": False, "title": "", @@ -162,72 +164,84 @@ "solution": "Remove any spaces that appear before the {character} character.", "explanation": "" }, -"E221": { -"original_message": "multiple spaces before operator", -"title": "This line has multiple spaces before an operator.", -"solution": "Remove any extra spaces that appear before the operator on this line.", -"explanation": "" -}, -"E222": { -"original_message": "multiple spaces after operator", -"title": "This line has multiple spaces after an operator.", -"solution": "Remove any extra spaces that appear after the operator on this line.", -"explanation": "" -}, -"E223": { -"original_message": "tab before operator", -"title": "This line contains a tab character before an operator.", -"solution": "Remove any tab characters that appear before the operator on this line. Operators should only have one space before them.", -"explanation": "" -}, -"E224": { -"original_message": "tab after operator", -"title": "This line contains a tab character after an operator.", -"solution": "Remove any tab characters that appear after the operator on this line. Operators should only have one space after them.", -"explanation": "" -}, -"E225": { -"original_message": "missing whitespace around operator", -"title": "This line is missing whitespace around an operator.", -"solution": "Ensure there is one space before and after all operators.", -"explanation": "" -}, -"E226": { -"original_message": "missing whitespace around arithmetic operator", -"title": "This line is missing whitespace around an arithmetic operator (+, -, / and *).", -"solution": "Ensure there is one space before and after all arithmetic operators (+, -, / and *).", -"explanation": "" -}, -"E227": { -"original_message": "missing whitespace around bitwise or shift operator", -"title": "This line is missing whitespace around a bitwise or shift operator (<<, >>, &, |, ^).", -"solution": "Ensure there is one space before and after all bitwise and shift operators (<<, >>, &, |, ^).", -"explanation": "" -}, -"E228": { -"original_message": "missing whitespace around modulo operator", -"title": "This line is missing whitespace around a modulo operator (%).", -"solution": "Ensure there is one space before and after the modulo operator (%).", -"explanation": "" -}, -"E231": { -"original_message": "missing whitespace after ‘,’, ‘;’, or ‘:’", -"title": "This line is missing whitespace around one of the following characters: , ; and :.", -"solution": "Ensure there is one space before and after any of the following characters: , ; and :.", -"explanation": "" -}, -"E241": { -"original_message": "multiple spaces after ‘,’", -"title": "This line has multiple spaces after the ',' character.", -"solution": "Ensure there is one space before and after any ',' characters.", -"explanation": "" -}, -"E242": { -"original_message": "tab after ‘,’", -"title": "This line contains a tab character after the ',' character.", -"solution": "Remove any tab characters and ensure there is one space before and after any ',' characters.", -"explanation": "" -}, + "E221": { + "templated": False, + "original_message": "multiple spaces before operator", + "title": "This line has multiple spaces before an operator.", + "solution": "Remove any extra spaces that appear before the operator on this line.", + "explanation": "" + }, + "E222": { + "templated": False, + "original_message": "multiple spaces after operator", + "title": "This line has multiple spaces after an operator.", + "solution": "Remove any extra spaces that appear after the operator on this line.", + "explanation": "" + }, + "E223": { + "templated": False, + "original_message": "tab before operator", + "title": "This line contains a tab character before an operator.", + "solution": "Remove any tab characters that appear before the operator on this line. Operators should only have one space before them.", + "explanation": "" + }, + "E224": { + "templated": False, + "original_message": "tab after operator", + "title": "This line contains a tab character after an operator.", + "solution": "Remove any tab characters that appear after the operator on this line. Operators should only have one space after them.", + "explanation": "" + }, + "E225": { + "templated": False, + "original_message": "missing whitespace around operator", + "title": "This line is missing whitespace around an operator.", + "solution": "Ensure there is one space before and after all operators.", + "explanation": "" + }, + # E226 ignored by default + "E226": { + "templated": False, + "original_message": "missing whitespace around arithmetic operator", + "title": "This line is missing whitespace around an arithmetic operator (+, -, / and *).", + "solution": "Ensure there is one space before and after all arithmetic operators (+, -, / and *).", + "explanation": "" + }, + "E227": { + "templated": False, + "original_message": "missing whitespace around bitwise or shift operator", + "title": "This line is missing whitespace around a bitwise or shift operator (<<, >>, &, |, ^).", + "solution": "Ensure there is one space before and after all bitwise and shift operators (<<, >>, &, |, ^).", + "explanation": "" + }, + "E228": { + "templated": False, + "original_message": "missing whitespace around modulo operator", + "title": "This line is missing whitespace around a modulo operator (%).", + "solution": "Ensure there is one space before and after the modulo operator (%).", + "explanation": "" + }, + # TODO: Check for specific character + "E231": { + "original_message": "missing whitespace after ‘,’, ‘;’, or ‘:’", + "title": "This line is missing whitespace around one of the following characters: , ; and :.", + "solution": "Ensure there is one space before and after any of the following characters: , ; and :.", + "explanation": "" + }, + # E241 ignored by default + "E241": { + "original_message": "multiple spaces after ‘,’", + "title": "This line has multiple spaces after the ',' character.", + "solution": "Ensure there is one space before and after any ',' characters.", + "explanation": "" + }, + # E242 ignored by default + "E242": { + "original_message": "tab after ‘,’", + "title": "This line contains a tab character after the ',' character.", + "solution": "Remove any tab characters and ensure there is one space before and after any ',' characters.", + "explanation": "" + }, "E251": { "original_message": "unexpected spaces around keyword / parameter equals", "title": "This line contains spaces before or after the = in a function definition.", @@ -366,6 +380,7 @@ "solution": "Remove the semicolon from the end of the line.", "explanation": "" }, + # E704 ignored by default "E704": { "original_message": "multiple statements on one line (def)", "title": "This line contains multiple statements.", @@ -462,18 +477,23 @@ "solution": "Ensure there is only one blank line at the end of your file.", "explanation": "" }, + # W503 ignored by default + # This seems contradicitng... https://lintlyci.github.io/Flake8Rules/rules/W503.html "W503": { "original_message": "line break before binary operator", "title": "", "solution": "", -"explanation": "This seems contradicitng... https://lintlyci.github.io/Flake8Rules/rules/W503.html" +"explanation": "" }, + # W504 ignored by default + # same as above https://lintlyci.github.io/Flake8Rules/rules/W504.html "W504": { "original_message": "line break after binary operator", "title": "", "solution": "", -"explanation": "same as above https://lintlyci.github.io/Flake8Rules/rules/W504.html" +"explanation": "" }, + # W505 ignored by default "W505": { "original_message": "doc line too long (82 > 79 characters)", "title": "This line is longer than 79 characters.", @@ -516,6 +536,9 @@ "solution": "", "explanation": "" } + # TODO: Add http://www.pydocstyle.org/en/5.0.2/error_codes.html + # TODO: Add https://github.com/PyCQA/pep8-naming#plugin-for-flake8 + # TODO: Add https://github.com/zheller/flake8-quotes#warnings } CHARACTER_DESCRIPTIONS = { diff --git a/requirements/base.txt b/requirements/base.txt index d0f17efca..33b0f3ade 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -33,3 +33,6 @@ PyYAML==5.1.2 # Style checker # Replace flake8 dependency once new release is created git+git://github.com/PyCQA/flake8.git@20906d43046096c31dfcd9b8bc536dbd21f043ef +flake8-docstrings==1.5.0 +flake8-quotes==2.1.1 +pep8-naming==0.9.1 From 7227287f5210bd418e1ef8cd13fce9bb5e713c4f Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Thu, 13 Feb 2020 13:46:36 +1300 Subject: [PATCH 10/48] Clarify style checker is for schools --- codewof/templates/style/python.html | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/codewof/templates/style/python.html b/codewof/templates/style/python.html index a6552266c..8173c85c3 100644 --- a/codewof/templates/style/python.html +++ b/codewof/templates/style/python.html @@ -2,7 +2,7 @@ {% load static svg %} -{% block title %}Python Style Checker{% endblock %} +{% block title %}Python Style Checker for schools{% endblock %} {% block page_heading %}
@@ -10,7 +10,7 @@ {% svg 'devicon-python' %}

- Python Style Checker + Python Style Checker for schools

{% endblock page_heading %} @@ -18,7 +18,8 @@

{% block content_container %}

- This style checker will check your code against the PEP 8 Python Style Guide. + This style checker will check your code against PEP 8 (Style Guide for Python Code) and PEP 257 (Docstring Conventions). + Aligning your code to pass these common conventions ensures a high readability of your Python code.

@@ -38,13 +39,7 @@

Your code

-
-

- - Run the style checker and any issues will appear here. - -

-
+
{% endblock content_container %} {% block css %} From 26ce6f133079c1697eade9d3fa08df4cb39ff4b1 Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Thu, 13 Feb 2020 14:22:29 +1300 Subject: [PATCH 11/48] Set result heading to colour of status --- codewof/templates/style/component/feedback_result.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/codewof/templates/style/component/feedback_result.html b/codewof/templates/style/component/feedback_result.html index fd1cab268..a0d45355e 100644 --- a/codewof/templates/style/component/feedback_result.html +++ b/codewof/templates/style/component/feedback_result.html @@ -1,5 +1,6 @@ {% if issue_count %} -

{{ issue_count }} style issue{{ issue_count|pluralize }} found

+

{{ issue_count }} style issue{{ issue_count|pluralize }} found

+

Click a style issue to see where it occurs in your code.

{% for issue in issues %}
From 3ea1f527ee5ed5bb0a2a8cccfe3e892fdd241143 Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Thu, 13 Feb 2020 15:03:24 +1300 Subject: [PATCH 12/48] Sticky code container within view --- codewof/static/scss/style_checker.scss | 5 +++ codewof/templates/style/python.html | 45 +++++++++++++------------- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/codewof/static/scss/style_checker.scss b/codewof/static/scss/style_checker.scss index bc0f8e63a..8f2c14d14 100644 --- a/codewof/static/scss/style_checker.scss +++ b/codewof/static/scss/style_checker.scss @@ -8,3 +8,8 @@ $highlight_colour: #ffeb3b; user-select: none; cursor: pointer; } + +#code-container { + position: sticky; + top: 0; +} diff --git a/codewof/templates/style/python.html b/codewof/templates/style/python.html index 8173c85c3..00142de8f 100644 --- a/codewof/templates/style/python.html +++ b/codewof/templates/style/python.html @@ -13,33 +13,34 @@

Python Style Checker for schools

+

+ This style checker will check your code against the more important rules in PEP 8 (Style + Guide for Python Code) and PEP 257 (Docstring Conventions). + Aligning your code to pass these common conventions ensures a high readability of your Python code. +

{% endblock page_heading %} {% block content_container %} -
-

- This style checker will check your code against PEP 8 (Style Guide for Python Code) and PEP 257 (Docstring Conventions). - Aligning your code to pass these common conventions ensures a high readability of your Python code. -

-
-
-

Your code

-
- -
-
- - - +
+
+

Your code

+
+ +
+
+ + + +
-
+
{% endblock content_container %} {% block css %} From 6bf664734130e98b665b052651e20213901d8537 Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Thu, 13 Feb 2020 15:03:45 +1300 Subject: [PATCH 13/48] Change clear button to reset button --- codewof/static/js/style_checkers/python.js | 8 ++++---- codewof/templates/style/python.html | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/codewof/static/js/style_checkers/python.js b/codewof/static/js/style_checkers/python.js index 2788be58c..df6b6f76c 100644 --- a/codewof/static/js/style_checkers/python.js +++ b/codewof/static/js/style_checkers/python.js @@ -33,12 +33,12 @@ $(document).ready(function () { var CSRF_TOKEN = jQuery("[name=csrfmiddlewaretoken]").val(); $('#load_example_btn').click(function () { - clear(); + reset(); editor.setValue(EXAMPLE_CODE); }); - $('#clear_btn').click(function () { - clear(); + $('#reset_btn').click(function () { + reset(); }); $('#check_btn').click(function () { @@ -87,7 +87,7 @@ function toggle_highlight(issue_button, remove_existing) { } -function clear() { +function reset() { editor.setValue(""); $('#run-checker-result').empty(); } diff --git a/codewof/templates/style/python.html b/codewof/templates/style/python.html index 00142de8f..b1cab8b61 100644 --- a/codewof/templates/style/python.html +++ b/codewof/templates/style/python.html @@ -28,8 +28,8 @@

Your code

-

- This style checker will check your code against the more important rules in PEP 8 (Style - Guide for Python Code) and PEP 257 (Docstring Conventions). - Aligning your code to pass these common conventions ensures a high readability of your Python code. + This style checker will check your code against the main conventions recommended for Python in PEP 8 (Style Guide for Python Code) and PEP 257 (Docstring Conventions). + Fine tuning your code to pass these common conventions makes it easy for others to read your Python code.

{% endblock page_heading %} From c9fcec22fec344f292f680116ac0bba1936f5fc1 Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Thu, 13 Feb 2020 15:07:18 +1300 Subject: [PATCH 15/48] Add descriptor text for issue codes --- codewof/templates/style/component/feedback_result.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codewof/templates/style/component/feedback_result.html b/codewof/templates/style/component/feedback_result.html index a0d45355e..ddc02a045 100644 --- a/codewof/templates/style/component/feedback_result.html +++ b/codewof/templates/style/component/feedback_result.html @@ -7,7 +7,7 @@

{{ issue_count }} style issue{{ issue_count|pluralize }}
Line {{ issue.line_number }} - - {{ issue.error_code }} + - Issue code: {{ issue.error_code }}

-
+
+
+
+ Sorry! + We have encountered an error. + Feel free to contact us if this error continues. +
+
+ {% endblock content_container %} {% block css %} @@ -49,5 +57,8 @@

Your code

{% block scripts %} {{ block.super }} {% csrf_token %} + {% endblock scripts %} From cc24c30d42b8acc6c406a06a0cde296dd326954b Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Fri, 14 Feb 2020 11:08:25 +1300 Subject: [PATCH 17/48] Fix HTML tag --- codewof/templates/style/python.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codewof/templates/style/python.html b/codewof/templates/style/python.html index 00d106be7..d5411ece5 100644 --- a/codewof/templates/style/python.html +++ b/codewof/templates/style/python.html @@ -14,7 +14,7 @@

- This style checker will check your code against the main conventions recommended for Python in PEP 8 (Style Guide for Python Code) and PEP 257 (Docstring Conventions). + This style checker will check your code against the main conventions recommended for Python in PEP 8 (Style Guide for Python Code) and PEP 257 (Docstring Conventions). Fine tuning your code to pass these common conventions makes it easy for others to read your Python code.

{% endblock page_heading %} From 01533d8601e68b79ffa41dbf9918fbd94ac34bdd Mon Sep 17 00:00:00 2001 From: courtneycb Date: Wed, 4 Mar 2020 15:43:32 +1300 Subject: [PATCH 18/48] Add missing docstring and whitespace issue error codes. --- codewof/style/style_checkers/python_data.py | 138 +++++++++++++++++++- 1 file changed, 135 insertions(+), 3 deletions(-) diff --git a/codewof/style/style_checkers/python_data.py b/codewof/style/style_checkers/python_data.py index d74eb65c7..f31a1a63e 100644 --- a/codewof/style/style_checkers/python_data.py +++ b/codewof/style/style_checkers/python_data.py @@ -535,10 +535,142 @@ "title": "", "solution": "", "explanation": "" +}, +"D100": { +"original_message": "Missing docstring in public module", +"title": "Modules should have docstrings.", +"solution": "Add a docstring to your module.", +"explanation": "" +}, +"D101": { +"original_message": "Missing docstring in public class", +"title": "Classes should have docstrings.", +"solution": "Add a docstring to your class.", +"explanation": "" +}, +"D102": { +"original_message": "Missing docstring in public method", +"title": "Methods should have docstrings.", +"solution": "Add a docstring to your method.", +"explanation": "" +}, +"D103": { +"original_message": "Missing docstring in public function", +"title": "Functions should have docstrings.", +"solution": "Add a docstring to your function.", +"explanation": "" +}, +"D104": { +"original_message": "Missing docstring in public package", +"title": "Packages should have docstrings.", +"solution": "Add a docstring to your package.", +"explanation": "" +}, +"D105": { +"original_message": "Missing docstring in magic method", +"title": "Magic methods should have docstrings.", +"solution": "Add a docstring to your magic method.", +"explanation": "" +}, +"D106": { +"original_message": "Missing docstring in public nested class", +"title": "Public nested classes should have docstrings.", +"solution": "Add a docstring to your public nested class.", +"explanation": "" +}, +"D107": { +"original_message": "Missing docstring in __init__", +"title": "The __init__ method should have a docstring.", +"solution": "Add a docstring to your __init__ method.", +"explanation": "" +}, +"D200": { +"original_message": "One-line docstring should fit on one line with quotes", +"title": "Docstrings that are one line long should fit on one line with quotes.", +"solution": "Put your docstring on one line with quotes.", +"explanation": "" +}, +"D201": { +"original_message": "No blank lines allowed before function docstring", +"title": "Function docstrings should not have blank lines before them.", +"solution": "Remove any blank lines before your function docstring.", +"explanation": "" +}, +"D202": { +"original_message": "No blank lines allowed after function docstring", +"title": "Function docstrings should not have blank lines after them.", +"solution": "Remove any blank lines after your function docstring.", +"explanation": "" +}, +"D203": { +"original_message": "1 blank line required before class docstring", +"title": "Class docstrings should have 1 blank line before them.", +"solution": "Insert 1 blank line before your class docstring.", +"explanation": "" +}, +"D204": { +"original_message": "1 blank line required after class docstring", +"title": "Class docstrings should have 1 blank line after them.", +"solution": "Insert 1 blank line after your class docstring.", +"explanation": "" +}, +"D205": { +"original_message": "1 blank line required between summary line and description", +"title": "There should be 1 blank line between the summary line and the description.", +"solution": "Insert 1 blank line between the summary line and the description.", +"explanation": "" +}, +"D206": { +"original_message": "Docstring should be indented with spaces, not tabs", +"title": "Docstrings should be indented using spaces, not tabs.", +"solution": "Make sure your docstrings are indented using spaces instead of tabs.", +"explanation": "" +}, +"D207": { +"original_message": "Docstring is under-indented", +"title": "Docstring is under-indented.", +"solution": "Add indentation levels to your docstring until it is at the correct indentation level.", +"explanation": "" +}, +"D208": { +"original_message": "Docstring is over-indented", +"title": "Docstring is over-indented.", +"solution": "Remove indentation levels from your docstring until it is at the correct indentation level.", +"explanation": "" +}, +"D209": { +"original_message": "Multi-line docstring closing quotes should be on a separate line", +"title": "Docstrings that are longer than one line should have closing quotes on a separate line.", +"solution": "Put the closing quotes of your docstring on a separate line.", +"explanation": "" +}, +"D210": { +"original_message": "No whitespaces allowed surrounding docstring text", +"title": "Text in docstrings should not be surrounded by whitespace.", +"solution": "Remove any whitespace from the start and end of your docstring.", +"explanation": "" +}, +"D211": { +"original_message": "No blank lines allowed before class docstring", +"title": "Class docstrings should not have blank lines before them.", +"solution": "Remove any blank lines before your class docstring.", +"explanation": "" +}, +"D212": { +"original_message": "Multi-line docstring summary should start at the first line", +"title": "Docstrings that are more than one line long should start at the first line.", +"solution": "Ensure your docstring starts on the first line with quotes.", +"explanation": "" +}, +"D213": { +"original_message": "Multi-line docstring summary should start at the second line", +"title": "Docstrings that are more than one line long should start at the second line.", +"solution": "Ensure your docstring starts on the second line, which is the first line without quotes.", +"explanation": "" } - # TODO: Add http://www.pydocstyle.org/en/5.0.2/error_codes.html - # TODO: Add https://github.com/PyCQA/pep8-naming#plugin-for-flake8 - # TODO: Add https://github.com/zheller/flake8-quotes#warnings +# TODO: Add http://www.pydocstyle.org/en/5.0.2/error_codes.html +# TODO: Add https://github.com/PyCQA/pep8-naming#plugin-for-flake8 +# TODO: Add https://github.com/zheller/flake8-quotes#warnings } CHARACTER_DESCRIPTIONS = { From 9dfe911c73deaed90584c063b958394458327d96 Mon Sep 17 00:00:00 2001 From: courtneycb Date: Thu, 5 Mar 2020 14:04:16 +1300 Subject: [PATCH 19/48] Add the rest of docstring error codes. --- codewof/style/style_checkers/python_data.py | 138 ++++++++++++++++++++ 1 file changed, 138 insertions(+) diff --git a/codewof/style/style_checkers/python_data.py b/codewof/style/style_checkers/python_data.py index f31a1a63e..0df90b13b 100644 --- a/codewof/style/style_checkers/python_data.py +++ b/codewof/style/style_checkers/python_data.py @@ -667,6 +667,144 @@ "title": "Docstrings that are more than one line long should start at the second line.", "solution": "Ensure your docstring starts on the second line, which is the first line without quotes.", "explanation": "" +}, +"D214": { +"original_message": "Section is over-indented", +"title": "Section is indented by too many levels.", +"solution": "Remove indentation levels from this section until it is at the correct indentation level.", +"explanation": "" +}, +"D215": { +"original_message": "Section underline is over-indented", +"title": "Section underline is indented by too many levels.", +"solution": "Remove indentation levels from this section underline until it is at the correct indentation level.", +"explanation": "" +}, +"D300": { +"original_message": "Use “”“triple double quotes”“”", +"title": "Use “”“triple double quotes”“” around docstrings.", +"solution": "Use “”“triple double quotes”“” around your docstring.", +"explanation": "" +}, +"D301": { +"original_message": "Use r”“” if any backslashes in a docstring", +"title": "Use r”“” if there are any backslashes in a docstring.", +"solution": "Use r”“” at the beginning of your docstring if it contains any backslashes.", +"explanation": "" +}, +"D302": { +"original_message": "Use u”“” for Unicode docstrings", +"title": "Use u”“” for docstrings that contain Unicode.", +"solution": "Use u”“” at the beginning of your docstring if it contains any Unicode.", +"explanation": "" +}, +"D400": { +"original_message": "First line should end with a period", +"title": "The first line in docstrings should end with a period.", +"solution": "Add a period to the end of the first line in your docstring.", +"explanation": "" +}, +"D401": { +"original_message": "First line should be in imperative mood", +"title": "The first line in docstrings should read like a command.", +"solution": "Ensure the first line in your docstring reads like a command, not a description. For example 'Do this' instead of 'Does this', 'Return this' instead of 'Returns this'.", +"explanation": "" +}, +"D402": { +"original_message": "First line should not be the function’s “signature”", +"title": "The first line in docstrings should not be the function’s “signature”.", +"solution": "Move the function’s “signature” to a different line or remove it completely.", +"explanation": "" +}, +"D403": { +"original_message": "First line should not be the function’s “signature”", +"title": "The first line in docstrings should not be the function’s “signature”.", +"solution": "Move the function’s “signature” to a different line or remove it completely.", +"explanation": "" +}, +"D404": { +"original_message": "First word of the docstring should not be 'This'", +"title": "First word of the docstring should not be 'This'.", +"solution": "Rephrase the docstring so that the first word is not 'This'.", +"explanation": "" +}, +"D405": { +"original_message": "Section name should be properly capitalized", +"title": "Section name should be properly capitalised.", +"solution": "Capitalise the section name.", +"explanation": "" +}, +"D406": { +"original_message": "Section name should end with a newline", +"title": "Section names should end with a newline.", +"solution": "Add a newline after the section name.", +"explanation": "" +}, +"D407": { +"original_message": "Missing dashed underline after section", +"title": "Section names should have a dashed line underneath them.", +"solution": "Add a dashed line underneath the section name.", +"explanation": "" +}, +"D408": { +"original_message": "Section underline should be in the line following the section’s name", +"title": "Dashed line should be on the line following the section's name.", +"solution": "Put the dashed underline on the line immediately following the section name.", +"explanation": "" +}, +"D409": { +"original_message": "Section underline should match the length of its name", +"title": "Dashed section underline should match the length of the section's name.", +"solution": "Add or remove dashes from the dashed underline until it matches the length of the section name.", +"explanation": "" +}, +"D410": { +"original_message": "Missing blank line after section", +"title": "Section should have a blank line after it.", +"solution": "Add a blank line after the section.", +"explanation": "" +}, +"D411": { +"original_message": "Missing blank line before section", +"title": "Section should have a blank line before it.", +"solution": "Add a blank line before the section.", +"explanation": "" +}, +"D412": { +"original_message": "No blank lines allowed between a section header and its content", +"title": "There should be no blank lines between a section header and its content.", +"solution": "Remove any blank lines that are between the section header and its content.", +"explanation": "" +}, +"D413": { +"original_message": "Missing blank line after last section", +"title": "The last section in a docstring should have a blank line after it.", +"solution": "Add a blank line after the last section in your docstring.", +"explanation": "" +}, +"D414": { +"original_message": "Section has no content", +"title": "Sections in docstrings must have content.", +"solution": "Add content to the section in your docstring.", +"explanation": "" +}, +"D415": { +"original_message": "First line should end with a period, question mark, or exclamation point", +"title": "The first line in your docstring should end with a period, question mark, or exclamation point.", +"solution": "Add a period, question mark, or exclamation point to the end of the first line in your docstring.", +"explanation": "" +}, +"D416": { +"original_message": "Section name should end with a colon", +"title": "Section names should end with a colon.", +"solution": "Add a colon to the end of your section name.", +"explanation": "" +}, +"D417": { +"original_message": "Missing argument descriptions in the docstring", +"title": "Docstrings should include argument descriptions.", +"solution": "Add argument descriptions to your docstring.", +"explanation": "" } # TODO: Add http://www.pydocstyle.org/en/5.0.2/error_codes.html # TODO: Add https://github.com/PyCQA/pep8-naming#plugin-for-flake8 From 986219bc5677b7ee46ee33a2203314f3cd3f1ede Mon Sep 17 00:00:00 2001 From: courtneycb Date: Thu, 5 Mar 2020 15:38:13 +1300 Subject: [PATCH 20/48] Add flake8 errors. --- codewof/style/style_checkers/python_data.py | 85 +++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/codewof/style/style_checkers/python_data.py b/codewof/style/style_checkers/python_data.py index 0df90b13b..1f8f8250e 100644 --- a/codewof/style/style_checkers/python_data.py +++ b/codewof/style/style_checkers/python_data.py @@ -805,6 +805,91 @@ "title": "Docstrings should include argument descriptions.", "solution": "Add argument descriptions to your docstring.", "explanation": "" +}, +"N801": { +"original_message": "class names should use CapWords convention", +"title": "Class names should use the CapWords convention.", +"solution": "Edit your class names to follow the CapWords convention.", +"explanation": "" +}, +"N802": { +"original_message": "function name should be lowercase", +"title": "Function names should be lowercase.", +"solution": "Edit your function names to be lowercase.", +"explanation": "" +}, +"N803": { +"original_message": "argument name should be lowercase", +"title": "Argument names should be lowercase.", +"solution": "Edit your argument names to be lowercase.", +"explanation": "" +}, +"N804": { +"original_message": "first argument of a classmethod should be named 'cls'", +"title": "The first argument of a classmethod should be named 'cls'.", +"solution": "Edit the first argument in your classmethod to be named 'cls'.", +"explanation": "" +}, +"N805": { +"original_message": "first argument of a method should be named 'self'", +"title": "The first argument of a method should be named 'self'.", +"solution": "Edit the first argument in your method to be named 'self'.", +"explanation": "" +}, +"N806": { +"original_message": "variable in function should be lowercase", +"title": "Variables in functions should be lowercase.", +"solution": "Edit the variable in your function to be lowercase.", +"explanation": "" +}, +"N807": { +"original_message": "function name should not start and end with '__'", +"title": "Function names should not start and end with '__'", +"solution": "Edit the function name so that it does not start and end with '__'", +"explanation": "" +}, +"N811": { +"original_message": "constant imported as non constant", +"title": "Import that should have been imported as a constant has been imported as a non constant.", +"solution": "Edit the import to be imported as a constant (use all capital letters in the 'import as...' name).", +"explanation": "" +}, +"N812": { +"original_message": "lowercase imported as non lowercase", +"title": "Import that should have been imported as lowercase has been imported as non lowercase", +"solution": "Edit the import to be imported as lowercase (use lowercase in the 'import as...' name).", +"explanation": "" +}, +"N813": { +"original_message": "camelcase imported as lowercase", +"title": "Import that should have been imported as camelCase has been imported as lowercase.", +"solution": "Edit the import to be imported as camelCase (use camelCase in the 'import as ...' name).", +"explanation": "" +}, +"N814": { +"original_message": "camelcase imported as constant", +"title": "Import that should have been imported as camelCase has been imported as a constant.", +"solution": "Edit the import to be imported as camelCase (use camelCase in the 'import as ...' name).", +"explanation": "" +}, +"N815": { +"original_message": "mixedCase variable in class scope", +"title": "Mixed case variable used in the class scope", +"solution": "Edit the variable name so that it doesn't use mixedCase.", +"explanation": "" +}, +"N816": { +"original_message": "mixedCase variable in global scope", +"title": "Mixed case variable used in the global scope", +"solution": "Edit the variable name so that it doesn't use mixedCase.", +"explanation": "" +}, +"N817": { +"original_message": "camelcase imported as acronym", +"title": "Import that should have been imported as camelCase has been imported as an acronym.", +"solution": "Edit the import to be imported as camelCase (use camelCase in the 'import as...' name).", +"explanation": "" +} } # TODO: Add http://www.pydocstyle.org/en/5.0.2/error_codes.html # TODO: Add https://github.com/PyCQA/pep8-naming#plugin-for-flake8 From 78cb3b3239031022dd2620c4f16ab75999fa1c5c Mon Sep 17 00:00:00 2001 From: courtneycb Date: Fri, 6 Mar 2020 12:39:04 +1300 Subject: [PATCH 21/48] Add flake9 quotes errors. --- codewof/style/style_checkers/python_data.py | 28 ++++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/codewof/style/style_checkers/python_data.py b/codewof/style/style_checkers/python_data.py index 1f8f8250e..f011ed0fa 100644 --- a/codewof/style/style_checkers/python_data.py +++ b/codewof/style/style_checkers/python_data.py @@ -889,12 +889,32 @@ "title": "Import that should have been imported as camelCase has been imported as an acronym.", "solution": "Edit the import to be imported as camelCase (use camelCase in the 'import as...' name).", "explanation": "" +}, +"Q000": { +"original_message": "Remove bad quotes", +"title": "Use single quotes (') instead of double quotes (\").", +"solution": "Replace any double quotes with single quotes.", +"explanation": "" +}, +"Q001": { +"original_message": "Remove bad quotes from multiline string", +"title": "Use single quotes (') instead of double quotes (\") in multiline strings.", +"solution": "Replace any double quotes in your multiline string with single quotes.", +"explanation": "" +}, +"Q002": { +"original_message": "Remove bad quotes from docstring", +"title": "Use single quotes (') instead of double quotes (\") in docstrings.", +"solution": "Replace any double quotes in your docstring with single quotes.", +"explanation": "" +}, +"Q003": { +"original_message": "Change outer quotes to avoid escaping inner quotes", +"title": "Change outer quotes to avoid escaping inner quotes.", +"solution": "Make either the outer quotes single (') and the inner quotes double (\"), or the outer quotes double and the inner quotes single.", +"explanation": "" } } -# TODO: Add http://www.pydocstyle.org/en/5.0.2/error_codes.html -# TODO: Add https://github.com/PyCQA/pep8-naming#plugin-for-flake8 -# TODO: Add https://github.com/zheller/flake8-quotes#warnings -} CHARACTER_DESCRIPTIONS = { '(': 'opening bracket', From 69c25412979d3f120c1a1d79850213b5babf39bd Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Tue, 17 Mar 2020 11:56:29 +1300 Subject: [PATCH 22/48] (WIP) Style checker updates --- codewof/static/js/style_checkers/python.js | 14 +++++- codewof/static/scss/style_checker.scss | 4 +- codewof/style/style_checkers/python.py | 11 +---- codewof/style/utils.py | 43 +++++++++++++++++++ codewof/style/views.py | 15 +++++-- .../{feedback_result.html => result.html} | 5 ++- codewof/templates/style/component/result.txt | 24 +++++++++++ codewof/templates/style/python.html | 17 +++++--- 8 files changed, 107 insertions(+), 26 deletions(-) create mode 100644 codewof/style/utils.py rename codewof/templates/style/component/{feedback_result.html => result.html} (88%) create mode 100644 codewof/templates/style/component/result.txt diff --git a/codewof/static/js/style_checkers/python.js b/codewof/static/js/style_checkers/python.js index 7071c47e0..2d66ce492 100644 --- a/codewof/static/js/style_checkers/python.js +++ b/codewof/static/js/style_checkers/python.js @@ -3,6 +3,7 @@ var CodeMirror = require('codemirror'); require('codemirror/mode/python/python.js'); var HIGHLIGHT_CLASS = 'style-highlight'; +// TODO: Ignore this code in database. var EXAMPLE_CODE = `"""a simple fizzbuzz program.""" def fizzbuzz(): @@ -37,11 +38,12 @@ $(document).ready(function () { editor.setValue(EXAMPLE_CODE); }); - $('#reset_btn').click(function () { + $('#reset-btn').click(function () { reset(); }); $('#check_btn').click(function () { + $('#run-checker-error').hide(); var user_code = editor.getValue(); if (user_code.length == 0) { $('#run-checker-result').text('No code submitted!'); @@ -78,7 +80,11 @@ $(document).ready(function () { function display_style_checker_results(data, textStatus, jqXHR) { if (data['success']) { - $('#run-checker-result').html(data['feedback_html']); + $('#run-checker-result').html(data['result_html']); + result_text = data['result_text']; + $('#check_btn').hide(); + $('#reset-btn').show(); + $('#download-file-btn').show(); } else { display_style_checker_error(); } @@ -112,7 +118,11 @@ function toggle_highlight(issue_button, remove_existing) { function reset() { editor.setValue(''); editor.setOption('readOnly', false); + result_text = ''; + $('#reset-btn').hide(); $('#run-checker-error').hide(); + $('#download-file-btn').hide(); $('.CodeMirror').removeClass('read-only'); $('#run-checker-result').empty(); + $('#check_btn').show(); } diff --git a/codewof/static/scss/style_checker.scss b/codewof/static/scss/style_checker.scss index f13412191..592ddab36 100644 --- a/codewof/static/scss/style_checker.scss +++ b/codewof/static/scss/style_checker.scss @@ -22,6 +22,8 @@ $highlight_colour: #ffeb3b; background-color: #e6e6e6; } -#run-checker-error { +#run-checker-error, +#download-file-btn, +#reset-btn { display: none; } diff --git a/codewof/style/style_checkers/python.py b/codewof/style/style_checkers/python.py index 9f028c2bc..2138078a6 100644 --- a/codewof/style/style_checkers/python.py +++ b/codewof/style/style_checkers/python.py @@ -4,7 +4,6 @@ import subprocess from pathlib import Path from django.conf import settings -from django.template.loader import render_to_string from style.style_checkers import python_data @@ -59,15 +58,7 @@ def process_results(result_text): issue_data = process_line(line) if issue_data: issues.append(issue_data) - # TODO: Check for at least one comment - result_html = render_to_string( - 'style/component/feedback_result.html', - { - 'issues': issues, - 'issue_count': len(issues), - } - ) - return result_html + return issues def process_line(line_text): diff --git a/codewof/style/utils.py b/codewof/style/utils.py new file mode 100644 index 000000000..b301a8a31 --- /dev/null +++ b/codewof/style/utils.py @@ -0,0 +1,43 @@ +"""Utilities for the style checker application.""" + +from django.template.loader import render_to_string + + +def render_results_as_html(issues): + """Render style issue data as HTML. + + Args: + issues (list): List of style issues. + + Returns: + HTML string. + """ + result_html = render_to_string( + 'style/component/result.html', + { + 'issues': issues, + 'issue_count': len(issues), + } + ) + return result_html + + +def render_results_as_text(user_code, issues): + """Render style issue data as HTML. + + Args: + user_code (str): String of user code. + issues (list): List of style issues. + + Returns: + String of text. + """ + result_text = render_to_string( + 'style/component/result.txt', + { + 'user_code': user_code, + 'issues': issues, + 'issue_count': len(issues), + } + ) + return result_text diff --git a/codewof/style/views.py b/codewof/style/views.py index fb9dce9b6..d0ce61dc5 100644 --- a/codewof/style/views.py +++ b/codewof/style/views.py @@ -3,13 +3,17 @@ import json from django.conf import settings from django.urls import reverse_lazy -from django.http import Http404, JsonResponse +from django.http import Http404, JsonResponse, HttpResponse from django.template.loader import get_template from django.template import TemplateDoesNotExist from django.views.generic import ( TemplateView, ) from style.style_checkers.python import python_style_check +from style.utils import ( + render_results_as_html, + render_results_as_text, +) LANGUAGE_PATH_TEMPLATE = 'style/{}.html' @@ -58,8 +62,11 @@ def check_code(request): if 0 < len(user_code) <= settings.STYLE_CHECKER_MAX_CHARACTER_COUNT: language = request_json['language'] if language == 'python3': - result_html = python_style_check(user_code) + result_data = python_style_check(user_code) result['success'] = True - result['feedback_html'] = result_html - # TODO: else raise error language isn't supported + else: + # TODO: else raise error language isn't supported + pass + result['result_html'] = render_results_as_html(result_data) + result['result_text'] = render_results_as_text(user_code, result_data) return JsonResponse(result) diff --git a/codewof/templates/style/component/feedback_result.html b/codewof/templates/style/component/result.html similarity index 88% rename from codewof/templates/style/component/feedback_result.html rename to codewof/templates/style/component/result.html index ddc02a045..eb2b88d3f 100644 --- a/codewof/templates/style/component/feedback_result.html +++ b/codewof/templates/style/component/result.html @@ -5,8 +5,9 @@

{{ issue_count }} style issue{{ issue_count|pluralize }} {% for issue in issues %}
- - Line {{ issue.line_number }} + + Line {{ issue.line_number }} + - Issue code: {{ issue.error_code }} diff --git a/codewof/templates/style/component/result.txt b/codewof/templates/style/component/result.txt new file mode 100644 index 000000000..4ad28143d --- /dev/null +++ b/codewof/templates/style/component/result.txt @@ -0,0 +1,24 @@ +============================================================================== +SUBMITTED CODE +------------------------------------------------------------------------------ +{{ user_code|safe }} +============================================================================== + +============================================================================== +{% spaceless %} +{% if issue_count %} +{{ issue_count }} STYLE ISSUE{{ issue_count|pluralize:"S" }} FOUND +{% else %} +No style issues found! +{% endif %} +{% endspaceless %} +{% for issue in issues %}------------------------------------------------------------------------------ +ISSUE {{ forloop.counter }} +- Line {{ issue.line_number }} +- Issue code: {{ issue.error_code }} + +{{ issue.title|striptags }} +{{ issue.solution|striptags }} +{{ issue.explanation|striptags }} +{% endfor %} +============================================================================== diff --git a/codewof/templates/style/python.html b/codewof/templates/style/python.html index d5411ece5..994c9d1a2 100644 --- a/codewof/templates/style/python.html +++ b/codewof/templates/style/python.html @@ -21,19 +21,19 @@

{% block content_container %}
-
+

Your code

-
- - - +
@@ -46,6 +46,9 @@

Your code

We have encountered an error. Feel free to contact us if this error continues.
+
{% endblock content_container %} From 699fe73ad04907ef68358a85d96b7c2eb436f6e9 Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Tue, 17 Mar 2020 13:13:39 +1300 Subject: [PATCH 23/48] Store error counts for style errors --- codewof/style/migrations/0001_initial.py | 23 +++++++++++++++++++++++ codewof/style/migrations/__init__.py | 0 codewof/style/models.py | 15 +++++++++++++++ codewof/style/utils.py | 10 ++++++++++ codewof/style/views.py | 2 ++ 5 files changed, 50 insertions(+) create mode 100644 codewof/style/migrations/0001_initial.py create mode 100644 codewof/style/migrations/__init__.py create mode 100644 codewof/style/models.py diff --git a/codewof/style/migrations/0001_initial.py b/codewof/style/migrations/0001_initial.py new file mode 100644 index 000000000..e85e43a2f --- /dev/null +++ b/codewof/style/migrations/0001_initial.py @@ -0,0 +1,23 @@ +# Generated by Django 2.1.5 on 2020-03-17 00:01 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Error', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('code', models.CharField(max_length=20)), + ('language', models.CharField(max_length=20)), + ('count', models.PositiveIntegerField(default=0)), + ], + ), + ] diff --git a/codewof/style/migrations/__init__.py b/codewof/style/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/codewof/style/models.py b/codewof/style/models.py new file mode 100644 index 000000000..30219adc7 --- /dev/null +++ b/codewof/style/models.py @@ -0,0 +1,15 @@ +"""Models for style checker application.""" + +from django.db import models + + +class Error(models.Model): + """Model to track style checker error code counts.""" + + language = models.CharField(max_length=20) + code = models.CharField(max_length=20) + count = models.PositiveIntegerField(default=0) + + def __str__(self): + """Text representation of an error.""" + return self.code diff --git a/codewof/style/utils.py b/codewof/style/utils.py index b301a8a31..52657a385 100644 --- a/codewof/style/utils.py +++ b/codewof/style/utils.py @@ -1,6 +1,8 @@ """Utilities for the style checker application.""" +from django.db.models import F from django.template.loader import render_to_string +from style.models import Error def render_results_as_html(issues): @@ -41,3 +43,11 @@ def render_results_as_text(user_code, issues): } ) return result_text + + +def update_error_counts(language, result_data): + """Update error counts for given errors.""" + for error_data in result_data: + error, created = Error.objects.get_or_create(language=language, code=error_data['error_code']) + error.count = F('count') + 1 + error.save() diff --git a/codewof/style/views.py b/codewof/style/views.py index d0ce61dc5..8d5d1577c 100644 --- a/codewof/style/views.py +++ b/codewof/style/views.py @@ -13,6 +13,7 @@ from style.utils import ( render_results_as_html, render_results_as_text, + update_error_counts, ) LANGUAGE_PATH_TEMPLATE = 'style/{}.html' @@ -67,6 +68,7 @@ def check_code(request): else: # TODO: else raise error language isn't supported pass + update_error_counts(language, result_data) result['result_html'] = render_results_as_html(result_data) result['result_text'] = render_results_as_text(user_code, result_data) return JsonResponse(result) From 971024e8413d53f536748721157fff829f5a07fe Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Sun, 7 Jun 2020 15:17:38 +1200 Subject: [PATCH 24/48] Rewrite style checker to use errors in database --- codewof/config/settings/base.py | 23 ++- .../style_checkers/{python.js => python3.js} | 12 -- codewof/style/management/__init__.py | 1 + codewof/style/management/commands/__init__.py | 1 + .../management/commands/load_style_errors.py | 40 ++++++ codewof/style/migrations/0001_initial.py | 9 +- .../migrations/0002_auto_20200607_1425.py | 18 +++ codewof/style/models.py | 7 +- codewof/style/style_checkers/python.py | 104 -------------- codewof/style/style_checkers/python3.py | 133 ++++++++++++++++++ .../{python_data.py => python3_data.py} | 103 +++++--------- codewof/style/urls.py | 1 + codewof/style/utils.py | 66 +++++++-- codewof/style/views.py | 44 ++++-- codewof/templates/base.html | 30 ++-- codewof/templates/style/home.html | 25 +++- .../language-components/python3-header.html | 16 +++ .../templates/style/language-statistics.html | 44 ++++++ .../style/{python.html => language.html} | 32 ++--- dev | 6 + requirements/base.txt | 1 + 21 files changed, 472 insertions(+), 244 deletions(-) rename codewof/static/js/style_checkers/{python.js => python3.js} (91%) create mode 100644 codewof/style/management/__init__.py create mode 100644 codewof/style/management/commands/__init__.py create mode 100644 codewof/style/management/commands/load_style_errors.py create mode 100644 codewof/style/migrations/0002_auto_20200607_1425.py delete mode 100644 codewof/style/style_checkers/python.py create mode 100644 codewof/style/style_checkers/python3.py rename codewof/style/style_checkers/{python_data.py => python3_data.py} (95%) create mode 100644 codewof/templates/style/language-components/python3-header.html create mode 100644 codewof/templates/style/language-statistics.html rename codewof/templates/style/{python.html => language.html} (64%) diff --git a/codewof/config/settings/base.py b/codewof/config/settings/base.py index 01db37b0c..a2b9b32fc 100644 --- a/codewof/config/settings/base.py +++ b/codewof/config/settings/base.py @@ -118,6 +118,7 @@ 'ckeditor', 'ckeditor_uploader', 'captcha', + 'django_bootstrap_breadcrumbs', ] LOCAL_APPS = [ 'general.apps.GeneralAppConfig', @@ -380,6 +381,7 @@ # Other # ------------------------------------------------------------------------------ +BREADCRUMBS_TEMPLATE = "django_bootstrap_breadcrumbs/bootstrap4.html" DEPLOYMENT_TYPE = env("DEPLOYMENT", default='local') QUESTIONS_BASE_PATH = os.path.join(str(ROOT_DIR.path("programming")), "content") CUSTOM_VERTO_TEMPLATES = os.path.join(str(ROOT_DIR.path("utils")), "custom_converter_templates", "") @@ -388,8 +390,27 @@ SVG_DIRS = [ os.path.join(str(STATIC_ROOT), 'svg') ] +STYLE_CHECKER_LANGUAGES = { + 'python3': { + 'name': 'Python 3', + 'svg-icon': 'devicon-python.svg', + 'checker-config': os.path.join(str(ROOT_DIR), 'style', 'style_checkers', 'flake8.ini'), + 'example-code': """\"\"\"a simple fizzbuzz program.\"\"\" + +def fizzbuzz(): + for i in range(1 ,100): + if i % 3 == 0 and i % 5 == 0 : + print("FizzBuzz") + elif i%3 == 0: + print( "Fizz") + elif i % 5==0: + print("Buzz") + else: + print(i) +""" + }, +} STYLE_CHECKER_TEMP_FILES_ROOT = os.path.join(str(ROOT_DIR), 'temp', 'style') -STYLE_CHECKER_PYTHON3_SETTINGS = os.path.join(str(ROOT_DIR), 'style', 'style_checkers', 'flake8.ini') STYLE_CHECKER_MAX_CHARACTER_COUNT = 10000 # reCAPTCHA diff --git a/codewof/static/js/style_checkers/python.js b/codewof/static/js/style_checkers/python3.js similarity index 91% rename from codewof/static/js/style_checkers/python.js rename to codewof/static/js/style_checkers/python3.js index 2d66ce492..626ebfa35 100644 --- a/codewof/static/js/style_checkers/python.js +++ b/codewof/static/js/style_checkers/python3.js @@ -3,19 +3,7 @@ var CodeMirror = require('codemirror'); require('codemirror/mode/python/python.js'); var HIGHLIGHT_CLASS = 'style-highlight'; -// TODO: Ignore this code in database. -var EXAMPLE_CODE = `"""a simple fizzbuzz program.""" -def fizzbuzz(): - for i in range(1 ,100): - if i % 3 == 0 and i % 5 == 0 : - print("FizzBuzz") - elif i%3 == 0: - print( "Fizz") - elif i % 5==0: - print("Buzz") - else: - print(i)`; $(document).ready(function () { editor = CodeMirror.fromTextArea(document.getElementById('code'), { diff --git a/codewof/style/management/__init__.py b/codewof/style/management/__init__.py new file mode 100644 index 000000000..6d63bc8c4 --- /dev/null +++ b/codewof/style/management/__init__.py @@ -0,0 +1 @@ +"""Module for the management of the style application.""" diff --git a/codewof/style/management/commands/__init__.py b/codewof/style/management/commands/__init__.py new file mode 100644 index 000000000..04ce04d5a --- /dev/null +++ b/codewof/style/management/commands/__init__.py @@ -0,0 +1 @@ +"""Module for the custom commands for the style appliation.""" diff --git a/codewof/style/management/commands/load_style_errors.py b/codewof/style/management/commands/load_style_errors.py new file mode 100644 index 000000000..74c5c32d6 --- /dev/null +++ b/codewof/style/management/commands/load_style_errors.py @@ -0,0 +1,40 @@ +"""Module for the custom Django load_style_errors command.""" + +import importlib +from django.core import management +from style.utils import get_language_slugs +from style.models import Error + +BASE_DATA_MODULE_PATH = 'style.style_checkers.{}_data' + + +class Command(management.base.BaseCommand): + """Required command class for the custom Django load_style_errors command.""" + + help = "Load progress outcomes to database." + + def handle(self, *args, **options): + """Automatically called when the load_style_errors command is given.""" + created_count = 0 + updated_count = 0 + + for language_code in get_language_slugs(): + # Import langauge data + module_path = BASE_DATA_MODULE_PATH.format(language_code) + module = importlib.import_module(module_path) + language_data = getattr(module, 'DATA') + + # Load errors into database + for code, code_data in language_data.items(): + obj, created = Error.objects.update_or_create( + language=language_code, + code=code, + defaults=code_data, + ) + if created: + created_count += 1 + print('Created {}'.format(obj)) + else: + updated_count += 1 + print('Updated {}'.format(obj)) + print('Style errors loaded ({} created, {} updated).'.format(created_count, updated_count)) diff --git a/codewof/style/migrations/0001_initial.py b/codewof/style/migrations/0001_initial.py index e85e43a2f..9e9837873 100644 --- a/codewof/style/migrations/0001_initial.py +++ b/codewof/style/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.1.5 on 2020-03-17 00:01 +# Generated by Django 2.1.5 on 2020-04-27 01:45 from django.db import migrations, models @@ -15,9 +15,14 @@ class Migration(migrations.Migration): name='Error', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('code', models.CharField(max_length=20)), ('language', models.CharField(max_length=20)), + ('code', models.CharField(max_length=20)), ('count', models.PositiveIntegerField(default=0)), + ('original_message', models.TextField(blank=True)), + ('title', models.TextField()), + ('title_templated', models.TextField(blank=True)), + ('solution', models.TextField()), + ('explanation', models.TextField()), ], ), ] diff --git a/codewof/style/migrations/0002_auto_20200607_1425.py b/codewof/style/migrations/0002_auto_20200607_1425.py new file mode 100644 index 000000000..d63790ec6 --- /dev/null +++ b/codewof/style/migrations/0002_auto_20200607_1425.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1.5 on 2020-06-07 02:25 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('style', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='error', + name='title_templated', + field=models.BooleanField(default=False), + ), + ] diff --git a/codewof/style/models.py b/codewof/style/models.py index 30219adc7..12ad3eb43 100644 --- a/codewof/style/models.py +++ b/codewof/style/models.py @@ -9,7 +9,12 @@ class Error(models.Model): language = models.CharField(max_length=20) code = models.CharField(max_length=20) count = models.PositiveIntegerField(default=0) + original_message = models.TextField(blank=True) + title = models.TextField() + title_templated = models.BooleanField(default=False) + solution = models.TextField() + explanation = models.TextField() def __str__(self): """Text representation of an error.""" - return self.code + return '{} - {}'.format(self.language, self.code) diff --git a/codewof/style/style_checkers/python.py b/codewof/style/style_checkers/python.py deleted file mode 100644 index 2138078a6..000000000 --- a/codewof/style/style_checkers/python.py +++ /dev/null @@ -1,104 +0,0 @@ -import re -import os.path -import uuid -import subprocess -from pathlib import Path -from django.conf import settings -from style.style_checkers import python_data - - -LINE_RE = re.compile(r':(?P\d+):(?P\d+): (?P\w\d+) (?P.*)$') -CHARACTER_RE = re.compile(r'\'(?P.*)\'') -TEMP_FILE_ROOT = settings.STYLE_CHECKER_TEMP_FILES_ROOT -TEMP_FILE_EXT = '.py' -# Create folder if it does not exist -Path(TEMP_FILE_ROOT).mkdir(parents=True, exist_ok=True) - -def python_style_check(code): - # Write file to HDD - filename = str(uuid.uuid4()) + TEMP_FILE_EXT - filepath = Path(os.path.join(TEMP_FILE_ROOT, filename)) - f = open(filepath, 'w') - f.write(code) - f.close() - - # Read file with flake8 - checker_result = subprocess.run( - [ - '/docker_venv/bin/flake8', - filepath, - '--config=' + settings.STYLE_CHECKER_PYTHON3_SETTINGS, - ], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - ) - - # Process results - result_text = checker_result.stdout.decode('utf-8') - result_data = process_results(result_text) - - # Delete file from HDD - filepath.unlink() - - # Send results - return result_data - - -def process_results(result_text): - """Process results into data for response. - - Args: - result_text (str): Text output from style checker. - - Returns: - List of dictionaries of result data. - """ - issues = [] - for line in result_text.split('\n'): - issue_data = process_line(line) - if issue_data: - issues.append(issue_data) - return issues - - -def process_line(line_text): - issue_data = dict() - re_result = re.search(LINE_RE, line_text) - if re_result: - line_number = re_result.group('line') - char_number = re_result.group('character') - error_code = re_result.group('error_code') - error_message = re_result.group('error_message') - error_data = python_data.PYTHON_ISSUES.get(error_code, dict()) - if error_data.get('templated'): - error_title = render_text(error_data['title'], error_message) - error_solution = render_text(error_data['solution'], error_message) - else: - try: - error_title = error_data['title'] - error_solution = error_data['solution'] - except KeyError: - error_title = error_data.get('original_message', error_message) - error_solution = '' - # TODO: Link to https://www.flake8rules.com if available - issue_data = { - 'error_code': error_code, - 'title': error_title, - 'line_number': line_number, - 'solution': error_solution, - 'explanation': error_data.get('explanation', ''), - } - return issue_data - - -def render_text(template, error_message): - re_result = re.search(CHARACTER_RE, error_message) - character = re_result.group('character') - character_description = python_data.CHARACTER_DESCRIPTIONS[character] - template_data = { - 'character': character, - 'character_description': character_description, - 'article': python_data.get_article(character_description), - } - title = template.format(**template_data) - return title diff --git a/codewof/style/style_checkers/python3.py b/codewof/style/style_checkers/python3.py new file mode 100644 index 000000000..179c8591b --- /dev/null +++ b/codewof/style/style_checkers/python3.py @@ -0,0 +1,133 @@ +import re +import os.path +import uuid +import subprocess +from pathlib import Path +from django.conf import settings +from django.core.exceptions import ObjectDoesNotExist +from style.style_checkers import python3_data +from style.utils import ( + CHARACTER_DESCRIPTIONS, + get_language_info, + get_article, +) +from style.models import Error + + +LINE_RE = re.compile(r':(?P\d+):(?P\d+): (?P\w\d+) (?P.*)$') +CHARACTER_RE = re.compile(r'\'(?P.*)\'') +TEMP_FILE_ROOT = settings.STYLE_CHECKER_TEMP_FILES_ROOT +TEMP_FILE_EXT = '.py' +# Create folder if it does not exist +Path(TEMP_FILE_ROOT).mkdir(parents=True, exist_ok=True) +PYTHON3_DETAILS = get_language_info('python3') + + +def python3_style_check(code): + # Write file to HDD + filename = str(uuid.uuid4()) + TEMP_FILE_EXT + filepath = Path(os.path.join(TEMP_FILE_ROOT, filename)) + f = open(filepath, 'w') + f.write(code) + f.close() + + # Read file with flake8 + checker_result = subprocess.run( + [ + '/docker_venv/bin/flake8', + filepath, + '--config=' + PYTHON3_DETAILS['checker-config'], + ], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + + # Process results + result_text = checker_result.stdout.decode('utf-8') + is_example_code = code == PYTHON3_DETAILS['example-code'] + result_data = process_results(result_text, is_example_code) + + # Delete file from HDD + filepath.unlink() + + # Send results + return result_data + + +def process_results(result_text, is_example_code): + """Process results into data for response. + + Args: + result_text (str): Text output from style checker. + is_example_code (bool): True if provide code matches the example code. + + Returns: + List of dictionaries of result data. + """ + issues = [] + for line in result_text.split('\n'): + issue_data = process_line(line, is_example_code) + if issue_data: + issues.append(issue_data) + return issues + + +def process_line(line_text, is_example_code): + """ + + Note: Could at extracting parts of this function to a generic + utility function. + """ + issue_data = dict() + re_result = re.search(LINE_RE, line_text) + if re_result: + line_number = re_result.group('line') + char_number = re_result.group('character') + error_code = re_result.group('error_code') + error_message = re_result.group('error_message') + + try: + error = Error.objects.get( + language='python3', + code=error_code, + ) + # Increment error occurence count, if not example code + if not is_example_code: + error.count = F('count') + 1 + error.save() + + if error.title_templated: + error_title = render_text(error.title, error_message) + error_solution = render_text(error.solution, error_message) + else: + error_title = error.title + error_solution = error.solution + + issue_data = { + 'error_code': error_code, + 'title': error_title, + 'line_number': line_number, + 'solution': error_solution, + 'explanation': error.explanation, + } + except ObjectDoesNotExist: + # If error is not defined in database. + issue_data = { + 'error_code': error_code, + 'title': error_message, + 'line_number': line_number, + } + return issue_data + + +def render_text(template, error_message): + re_result = re.search(CHARACTER_RE, error_message) + character = re_result.group('character') + character_description = CHARACTER_DESCRIPTIONS[character] + template_data = { + 'character': character, + 'character_description': character_description, + 'article': get_article(character_description), + } + title = template.format(**template_data) + return title diff --git a/codewof/style/style_checkers/python_data.py b/codewof/style/style_checkers/python3_data.py similarity index 95% rename from codewof/style/style_checkers/python_data.py rename to codewof/style/style_checkers/python3_data.py index f011ed0fa..b65382d73 100644 --- a/codewof/style/style_checkers/python_data.py +++ b/codewof/style/style_checkers/python3_data.py @@ -1,56 +1,56 @@ -PYTHON_ISSUES = { +DATA = { "E101": { "original_message": "indentation contains mixed spaces and tabs", - "templated": False, + "title_templated": False, "title": "This line is indented using a mixture of spaces and tabs.", "solution": "You should indent your code using only spaces.", "explanation": "Python expects the indentation method to be consistent line to line. Spaces are the preferred indentation method." }, "E111": { "original_message": "indentation is not a multiple of four", - "templated": False, + "title_templated": False, "title": "This line has an indentation level that is not a multiple of four.", "solution": "Ensure that the first indentation level is 4 spaces, the second indentation level is 8 spaces and so on.", "explanation": "" }, "E112": { "original_message": "expected an indented block", - "templated": False, + "title_templated": False, "title": "This line is not indented at the correct level.", "solution": "Add indentation to this line until it is indented at the correct level.", "explanation": "" }, "E113": { "original_message": "unexpected indentation", - "templated": False, + "title_templated": False, "title": "This line is indented when it shouldn't be.", "solution": "Remove indentation from this line until it is indented at the correct level.", "explanation": "" }, "E114": { "original_message": "indentation is not a multiple of four (comment)", - "templated": False, + "title_templated": False, "title": "This line has an indentation level that is not a multiple of four.", "solution": "Ensure that the first indentation level is 4 spaces, the second indentation level is 8 spaces and so on.", "explanation": "" }, "E115": { "original_message": "expected an indented block (comment)", - "templated": False, + "title_templated": False, "title": "This line is not indented at the correct level.", "solution": "Add indentation to this line until it is indented at the correct level.", "explanation": "Comments should be indented relative to the code block they are in." }, "E116": { "original_message": "unexpected indentation (comment)", - "templated": False, + "title_templated": False, "title": "This line is indented when it shouldn't be.", "solution": "Remove indentation from this line until it is indented at the correct level.", "explanation": "" }, "E117": { "original_message": "over-indented", - "templated": False, + "title_templated": False, "title": "This line has too many indentation levels.", "solution": "Remove indentation from this line until it is indented at the correct level.", "explanation": "" @@ -58,14 +58,14 @@ # E121 ignored by default "E121": { "original_message": "continuation line under-indented for hanging indent", - "templated": False, + "title_templated": False, "title": "This line is less indented than it should be.", "solution": "Add indentation to this line until it is indented at the correct level.", "explanation": "" }, "E122": { "original_message": "continuation line missing indentation or outdented", - "templated": False, + "title_templated": False, "title": "This line is not indented as far as it should be or is indented too far.", "solution": "Add or remove indentation levels until it is indented at the correct level.", "explanation": "" @@ -73,21 +73,21 @@ # E123 ignored by default "E123": { "original_message": "closing bracket does not match indentation of opening bracket’s line", - "templated": False, + "title_templated": False, "title": "This line has a closing bracket that does not match the indentation level of the line that the opening bracket started on.", "solution": "Add or remove indentation of the closing bracket so it matches the indentation of the line that the opening bracket is on.", "explanation": "" }, "E124": { "original_message": "closing bracket does not match visual indentation", - "templated": False, + "title_templated": False, "title": "This line has a closing bracket that does not match the indentation of the opening bracket.", "solution": "Add or remove indentation of the closing bracket so it matches the indentation of the opening bracket.", "explanation": "" }, "E125": { "original_message": "continuation line with same indent as next logical line", - "templated": False, + "title_templated": False, "title": "This line has a continuation that should be indented one extra level so that it can be distinguished from the next logical line.", "solution": "Add an indentation level to the line continuation so that it is indented one more level than the next logical line.", "explanation": "Continuation lines should not be indented at the same level as the next logical line. Instead, they should be indented to one more level so as to distinguish them from the next line." @@ -95,35 +95,35 @@ # E126 ignored by default "E126": { "original_message": "continuation line over-indented for hanging indent", - "templated": False, + "title_templated": False, "title": "This line is indented more than it should be.", "solution": "Remove indentation from this line until it is indented at the correct level.", "explanation": "" }, "E127": { "original_message": "continuation line over-indented for visual indent", - "templated": False, + "title_templated": False, "title": "This line is indented more than it should be.", "solution": "Remove indentation from this line until it is indented at the correct level.", "explanation": "" }, "E128": { "original_message": "continuation line under-indented for visual indent", - "templated": False, + "title_templated": False, "title": "This line is indented less than it should be.", "solution": "Add indentation to this line until it is indented at the correct level.", "explanation": "" }, "E129": { "original_message": "visually indented line with same indent as next logical line", - "templated": False, + "title_templated": False, "title": "This line has the same indentation as the next logical line.", "solution": "Add an indentation level to the visually indented line so that it is indented one more level than the next logical line.", "explanation": "A visually indented line that has the same indentation as the next logical line is hard to read." }, "E131": { "original_message": "continuation line unaligned for hanging indent", - "templated": False, + "title_templated": False, "title": "This line is not aligned correctly for a hanging indent.", "solution": "Add or remove indentation so that the lines are aligned with each other.", "explanation": "" @@ -131,69 +131,69 @@ # E133 ignored by default "E133": { "original_message": "closing bracket is missing indentation", - "templated": False, + "title_templated": False, "title": "", "solution": "", "explanation": "", }, "E201": { "original_message": "whitespace after '{character}'", - "templated": True, + "title_templated": True, "title": "This line contains {article} {character_description} that has a space after it.", "solution": "Remove any spaces that appear after the {character} character.", "explanation": "" }, "E202": { "original_message": "whitespace before '{character}'", - "templated": True, + "title_templated": True, "title": "This line contains {article} {character_description} that has a space before it.", "solution": "Remove any spaces that appear before the {character} character.", "explanation": "" }, "E203": { "original_message": "whitespace before '{character}'", - "templated": True, + "title_templated": True, "title": "This line contains {article} {character_description} that has a space before it.", "solution": "Remove any spaces that appear before the {character} character.", "explanation": "" }, "E211": { "original_message": "whitespace before '{character}'", - "templated": True, + "title_templated": True, "title": "This line contains {article} {character_description} that has a space before it.", "solution": "Remove any spaces that appear before the {character} character.", "explanation": "" }, "E221": { - "templated": False, + "title_templated": False, "original_message": "multiple spaces before operator", "title": "This line has multiple spaces before an operator.", "solution": "Remove any extra spaces that appear before the operator on this line.", "explanation": "" }, "E222": { - "templated": False, + "title_templated": False, "original_message": "multiple spaces after operator", "title": "This line has multiple spaces after an operator.", "solution": "Remove any extra spaces that appear after the operator on this line.", "explanation": "" }, "E223": { - "templated": False, + "title_templated": False, "original_message": "tab before operator", "title": "This line contains a tab character before an operator.", "solution": "Remove any tab characters that appear before the operator on this line. Operators should only have one space before them.", "explanation": "" }, "E224": { - "templated": False, + "title_templated": False, "original_message": "tab after operator", "title": "This line contains a tab character after an operator.", "solution": "Remove any tab characters that appear after the operator on this line. Operators should only have one space after them.", "explanation": "" }, "E225": { - "templated": False, + "title_templated": False, "original_message": "missing whitespace around operator", "title": "This line is missing whitespace around an operator.", "solution": "Ensure there is one space before and after all operators.", @@ -201,21 +201,21 @@ }, # E226 ignored by default "E226": { - "templated": False, + "title_templated": False, "original_message": "missing whitespace around arithmetic operator", "title": "This line is missing whitespace around an arithmetic operator (+, -, / and *).", "solution": "Ensure there is one space before and after all arithmetic operators (+, -, / and *).", "explanation": "" }, "E227": { - "templated": False, + "title_templated": False, "original_message": "missing whitespace around bitwise or shift operator", "title": "This line is missing whitespace around a bitwise or shift operator (<<, >>, &, |, ^).", "solution": "Ensure there is one space before and after all bitwise and shift operators (<<, >>, &, |, ^).", "explanation": "" }, "E228": { - "templated": False, + "title_templated": False, "original_message": "missing whitespace around modulo operator", "title": "This line is missing whitespace around a modulo operator (%).", "solution": "Ensure there is one space before and after the modulo operator (%).", @@ -540,7 +540,7 @@ "original_message": "Missing docstring in public module", "title": "Modules should have docstrings.", "solution": "Add a docstring to your module.", -"explanation": "" +"explanation": "A docstring is a comment at the top of your module that briefly explains the purpose of the module." }, "D101": { "original_message": "Missing docstring in public class", @@ -558,7 +558,8 @@ "original_message": "Missing docstring in public function", "title": "Functions should have docstrings.", "solution": "Add a docstring to your function.", -"explanation": "" + "explanation": "A docstring is a comment at the top of your function that briefly explains the purpose of the module.
For example:
def get_waypoint_latlng(number):
\"\"\"Return the latitude and longitude values of the waypoint.\"\"\"
" + # TODO: Check this works }, "D104": { "original_message": "Missing docstring in public package", @@ -915,37 +916,3 @@ "explanation": "" } } - -CHARACTER_DESCRIPTIONS = { - '(': 'opening bracket', - ')': 'closing bracket', - '[': 'opening square bracket', - ']': 'closing square bracket', - '{': 'opening curly bracket', - '}': 'closing curly bracket', - "'": 'single quote', - '"': 'double quote', - ':': 'colon', - ';': 'semicolon', - ' ': 'space', - ',': 'comma', - '.': 'full stop', -} - -def get_article(word): - """Return English article for word. - - Returns 'an' if word starts with vowel. Technically - it should check the word sound, compared to the - letter but this shouldn't occur with our words. - - Args: - word (str): Word to create article for. - - Returns: - 'a' or 'an' (str) depending if word starts with vowel. - """ - if word[0].lower() in 'aeiou': - return 'an' - else: - return 'a' diff --git a/codewof/style/urls.py b/codewof/style/urls.py index 8017a0620..766d89dfd 100644 --- a/codewof/style/urls.py +++ b/codewof/style/urls.py @@ -7,5 +7,6 @@ urlpatterns = [ path('', views.HomeView.as_view(), name='home'), path('/', views.LanguageStyleCheckerView.as_view(), name='language'), + path('/statistics/', views.LanguageStatisticsView.as_view(), name='language_statistics'), path('ajax/check/', views.check_code, name='check_code'), ] diff --git a/codewof/style/utils.py b/codewof/style/utils.py index 52657a385..f9b319ce3 100644 --- a/codewof/style/utils.py +++ b/codewof/style/utils.py @@ -1,10 +1,68 @@ """Utilities for the style checker application.""" +from django.conf import settings from django.db.models import F from django.template.loader import render_to_string from style.models import Error +CHARACTER_DESCRIPTIONS = { + '(': 'opening bracket', + ')': 'closing bracket', + '[': 'opening square bracket', + ']': 'closing square bracket', + '{': 'opening curly bracket', + '}': 'closing curly bracket', + "'": 'single quote', + '"': 'double quote', + ':': 'colon', + ';': 'semicolon', + ' ': 'space', + ',': 'comma', + '.': 'full stop', +} + + +def get_language_slugs(): + """Return all programming language slugs. + + Returns: + Iteratable of programming language slugs. + """ + return settings.STYLE_CHECKER_LANGUAGES.keys() + + +def get_language_info(slug): + """Return information about a programming language. + + Args: + slug (str): The slug of the given language. + + Returns: + Dictionary of information about the given programming language. + """ + return settings.STYLE_CHECKER_LANGUAGES.get(slug, dict()) + + +def get_article(word): + """Return English article for word. + + Returns 'an' if word starts with vowel. Technically + it should check the word sound, compared to the + letter but this shouldn't occur with our words. + + Args: + word (str): Word to create article for. + + Returns: + 'a' or 'an' (str) depending if word starts with vowel. + """ + if word[0].lower() in 'aeiou': + return 'an' + else: + return 'a' + + def render_results_as_html(issues): """Render style issue data as HTML. @@ -43,11 +101,3 @@ def render_results_as_text(user_code, issues): } ) return result_text - - -def update_error_counts(language, result_data): - """Update error counts for given errors.""" - for error_data in result_data: - error, created = Error.objects.get_or_create(language=language, code=error_data['error_code']) - error.count = F('count') + 1 - error.save() diff --git a/codewof/style/views.py b/codewof/style/views.py index 8d5d1577c..c0e807a65 100644 --- a/codewof/style/views.py +++ b/codewof/style/views.py @@ -8,43 +8,60 @@ from django.template import TemplateDoesNotExist from django.views.generic import ( TemplateView, + ListView, ) -from style.style_checkers.python import python_style_check +from style.style_checkers.python3 import python3_style_check +from style.models import Error from style.utils import ( render_results_as_html, render_results_as_text, - update_error_counts, + get_language_info, ) LANGUAGE_PATH_TEMPLATE = 'style/{}.html' -class HomeView(TemplateView): +class HomeView(ListView): """View for style homepage.""" template_name = 'style/home.html' + context_object_name = 'languages' + + def get_queryset(self): + """Get iterate of languages for page.""" + return settings.STYLE_CHECKER_LANGUAGES class LanguageStyleCheckerView(TemplateView): """View for a language style checker.""" - def get_template_names(self): - language_slug = self.kwargs.get('language', '') - template_path = LANGUAGE_PATH_TEMPLATE.format(language_slug) - try: - get_template(template_path) - except TemplateDoesNotExist: - raise Http404 - else: - return [template_path] + template_name = 'style/language.html' def get_context_data(self, **kwargs): """Get additional context data for template.""" context = super().get_context_data(**kwargs) + language_slug = self.kwargs.get('language', '') + context['language'] = get_language_info(language_slug) + context['language_header'] = 'style/language-components/{}-header.html'.format(language_slug) + context['language_js'] = 'js/style_checkers/{}.js'.format(language_slug) context['MAX_CHARACTER_COUNT'] = settings.STYLE_CHECKER_MAX_CHARACTER_COUNT return context +class LanguageStatisticsView(TemplateView): + """View for a language statistics.""" + + template_name = 'style/language-statistics.html' + + def get_context_data(self, **kwargs): + """Get additional context data for template.""" + context = super().get_context_data(**kwargs) + language = self.kwargs.get('language', '') + context['language_header'] = 'style/language-components/{}-header.html'.format(language) + context['errors'] = Error.objects.filter(language=language).order_by('-count') + return context + + def check_code(request): """Check the user's code for style issues. @@ -63,12 +80,11 @@ def check_code(request): if 0 < len(user_code) <= settings.STYLE_CHECKER_MAX_CHARACTER_COUNT: language = request_json['language'] if language == 'python3': - result_data = python_style_check(user_code) + result_data = python3_style_check(user_code) result['success'] = True else: # TODO: else raise error language isn't supported pass - update_error_counts(language, result_data) result['result_html'] = render_results_as_html(result_data) result['result_text'] = render_results_as_text(user_code, result_data) return JsonResponse(result) diff --git a/codewof/templates/base.html b/codewof/templates/base.html index 05841e3ba..2107fe261 100644 --- a/codewof/templates/base.html +++ b/codewof/templates/base.html @@ -1,4 +1,4 @@ -{% load static i18n activeurl svg %} +{% load static i18n activeurl svg django_bootstrap_breadcrumbs i18n %} @@ -75,22 +75,26 @@ {% if messages %} -
- {% for message in messages %} - - {% endfor %} -
- {% endif %} +
+ {% for message in messages %} + + {% endfor %} +
+ {% endif %} + + {% block breadcrumbs %} + {% endblock breadcrumbs %} {% block body_container %} -
@@ -62,6 +58,8 @@

Your code

{% csrf_token %} - + + {% endblock scripts %} diff --git a/dev b/dev index b60b4d977..6a2af87ac 100755 --- a/dev +++ b/dev @@ -170,6 +170,12 @@ cmd_load_questions() { } defhelp load_questions "Load questions." +# Run load_style_errors command +cmd_load_style_errors() { + docker-compose exec django /docker_venv/bin/python3 ./manage.py load_style_errors +} +defhelp load_style_errors "Load style errors." + cmd_createsuperuser() { docker-compose exec django /docker_venv/bin/python3 ./manage.py createsuperuser } diff --git a/requirements/base.txt b/requirements/base.txt index 33b0f3ade..b9dcada02 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -21,6 +21,7 @@ django-modeltranslation==0.13.3 django-inline-svg==0.1.1 django-ckeditor==5.7.1 django-recaptcha==2.0.5 +django-bootstrap-breadcrumbs==0.9.2 # Google Cloud Platform google-api-python-client==1.7.11 From acd8539befa1cc20ee8478c966cf546dc9eeb2ec Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Sun, 7 Jun 2020 23:00:56 +1200 Subject: [PATCH 25/48] Store error text as Markdown --- codewof/config/settings/base.py | 10 +- codewof/static/js/style_checkers/python3.js | 2 - codewof/static/scss/style_checker.scss | 15 +- .../management/commands/load_style_errors.py | 41 +- codewof/style/models.py | 3 +- codewof/style/style_checkers/python3.py | 3 +- codewof/style/style_checkers/python3_data.py | 1371 +++++++++-------- codewof/templates/style/component/result.html | 6 +- codewof/templates/style/language.html | 3 - 9 files changed, 761 insertions(+), 693 deletions(-) diff --git a/codewof/config/settings/base.py b/codewof/config/settings/base.py index a2b9b32fc..4b4c6595c 100644 --- a/codewof/config/settings/base.py +++ b/codewof/config/settings/base.py @@ -390,12 +390,18 @@ SVG_DIRS = [ os.path.join(str(STATIC_ROOT), 'svg') ] +# Key 'example_code' uses underscore to be accessible in templates STYLE_CHECKER_LANGUAGES = { 'python3': { 'name': 'Python 3', 'svg-icon': 'devicon-python.svg', - 'checker-config': os.path.join(str(ROOT_DIR), 'style', 'style_checkers', 'flake8.ini'), - 'example-code': """\"\"\"a simple fizzbuzz program.\"\"\" + 'checker-config': os.path.join( + str(ROOT_DIR), + 'style', + 'style_checkers', + 'flake8.ini' + ), + 'example_code': """\"\"\"a simple fizzbuzz program.\"\"\" def fizzbuzz(): for i in range(1 ,100): diff --git a/codewof/static/js/style_checkers/python3.js b/codewof/static/js/style_checkers/python3.js index 626ebfa35..3ba4179ae 100644 --- a/codewof/static/js/style_checkers/python3.js +++ b/codewof/static/js/style_checkers/python3.js @@ -72,7 +72,6 @@ function display_style_checker_results(data, textStatus, jqXHR) { result_text = data['result_text']; $('#check_btn').hide(); $('#reset-btn').show(); - $('#download-file-btn').show(); } else { display_style_checker_error(); } @@ -109,7 +108,6 @@ function reset() { result_text = ''; $('#reset-btn').hide(); $('#run-checker-error').hide(); - $('#download-file-btn').hide(); $('.CodeMirror').removeClass('read-only'); $('#run-checker-result').empty(); $('#check_btn').show(); diff --git a/codewof/static/scss/style_checker.scss b/codewof/static/scss/style_checker.scss index 592ddab36..41919c470 100644 --- a/codewof/static/scss/style_checker.scss +++ b/codewof/static/scss/style_checker.scss @@ -1,3 +1,9 @@ +@import "node_modules/bootstrap/scss/functions"; +@import "node_modules/bootstrap/scss/mixins"; +@import "core-variables"; +@import "functions"; +@import "node_modules/bootstrap/scss/variables"; + $highlight_colour: #ffeb3b; .style-highlight { @@ -23,7 +29,14 @@ $highlight_colour: #ffeb3b; } #run-checker-error, -#download-file-btn, #reset-btn { display: none; } + +.feedback-title { + p { + margin-bottom: 0; + color: $red; + font-weight: bold;; + } +} diff --git a/codewof/style/management/commands/load_style_errors.py b/codewof/style/management/commands/load_style_errors.py index 74c5c32d6..8111461cd 100644 --- a/codewof/style/management/commands/load_style_errors.py +++ b/codewof/style/management/commands/load_style_errors.py @@ -2,17 +2,35 @@ import importlib from django.core import management +from verto import Verto +from verto.errors.Error import Error as VertoError from style.utils import get_language_slugs from style.models import Error +from utils.errors.VertoConversionError import VertoConversionError -BASE_DATA_MODULE_PATH = 'style.style_checkers.{}_data' +BASE_DATA_MODULE_PATH = 'style.style_checkers.{}_data' +MARKDOWN_CONVERTER = Verto( + extensions=[ + "markdown.extensions.fenced_code", + "markdown.extensions.codehilite", + ], +) class Command(management.base.BaseCommand): """Required command class for the custom Django load_style_errors command.""" help = "Load progress outcomes to database." + def convert_markdown(self, module_path, code, field, markdown): + MARKDOWN_CONVERTER.clear_saved_data() + try: + result = MARKDOWN_CONVERTER.convert(markdown) + except VertoError as e: + location = '{} - {} - {}'.format(module_path, code, field) + raise VertoConversionError(location, e) from e + return result.html_string + def handle(self, *args, **options): """Automatically called when the load_style_errors command is given.""" created_count = 0 @@ -26,6 +44,27 @@ def handle(self, *args, **options): # Load errors into database for code, code_data in language_data.items(): + code_data['title'] = self.convert_markdown( + module_path, + code, + 'title', + code_data['title'], + ) + if code_data.get('solution'): + code_data['solution'] = self.convert_markdown( + module_path, + code, + 'solution', + code_data['solution'], + ) + if code_data.get('explanation'): + code_data['explanation'] = self.convert_markdown( + module_path, + code, + 'explanation', + code_data['explanation'], + ) + obj, created = Error.objects.update_or_create( language=language_code, code=code, diff --git a/codewof/style/models.py b/codewof/style/models.py index 12ad3eb43..4e373c470 100644 --- a/codewof/style/models.py +++ b/codewof/style/models.py @@ -10,8 +10,9 @@ class Error(models.Model): code = models.CharField(max_length=20) count = models.PositiveIntegerField(default=0) original_message = models.TextField(blank=True) - title = models.TextField() title_templated = models.BooleanField(default=False) + # The following fields are stored as HTML + title = models.TextField() solution = models.TextField() explanation = models.TextField() diff --git a/codewof/style/style_checkers/python3.py b/codewof/style/style_checkers/python3.py index 179c8591b..0ed25ae27 100644 --- a/codewof/style/style_checkers/python3.py +++ b/codewof/style/style_checkers/python3.py @@ -44,7 +44,7 @@ def python3_style_check(code): # Process results result_text = checker_result.stdout.decode('utf-8') - is_example_code = code == PYTHON3_DETAILS['example-code'] + is_example_code = code == PYTHON3_DETAILS['example_code'] result_data = process_results(result_text, is_example_code) # Delete file from HDD @@ -117,6 +117,7 @@ def process_line(line_text, is_example_code): 'title': error_message, 'line_number': line_number, } + print(issue_data) return issue_data diff --git a/codewof/style/style_checkers/python3_data.py b/codewof/style/style_checkers/python3_data.py index b65382d73..7133fd77f 100644 --- a/codewof/style/style_checkers/python3_data.py +++ b/codewof/style/style_checkers/python3_data.py @@ -1,3 +1,7 @@ +# The following fields are rendered as HTML. +# - solution +# - explanation + DATA = { "E101": { "original_message": "indentation contains mixed spaces and tabs", @@ -140,28 +144,28 @@ "original_message": "whitespace after '{character}'", "title_templated": True, "title": "This line contains {article} {character_description} that has a space after it.", - "solution": "Remove any spaces that appear after the {character} character.", + "solution": "Remove any spaces that appear after the `{character}` character.", "explanation": "" }, "E202": { "original_message": "whitespace before '{character}'", "title_templated": True, "title": "This line contains {article} {character_description} that has a space before it.", - "solution": "Remove any spaces that appear before the {character} character.", + "solution": "Remove any spaces that appear before the `{character}` character.", "explanation": "" }, "E203": { "original_message": "whitespace before '{character}'", "title_templated": True, "title": "This line contains {article} {character_description} that has a space before it.", - "solution": "Remove any spaces that appear before the {character} character.", + "solution": "Remove any spaces that appear before the `{character}` character.", "explanation": "" }, "E211": { "original_message": "whitespace before '{character}'", "title_templated": True, "title": "This line contains {article} {character_description} that has a space before it.", - "solution": "Remove any spaces that appear before the {character} character.", + "solution": "Remove any spaces that appear before the `{character}` character.", "explanation": "" }, "E221": { @@ -217,8 +221,8 @@ "E228": { "title_templated": False, "original_message": "missing whitespace around modulo operator", - "title": "This line is missing whitespace around a modulo operator (%).", - "solution": "Ensure there is one space before and after the modulo operator (%).", + "title": "This line is missing whitespace around a modulo operator (`%`).", + "solution": "Ensure there is one space before and after the modulo operator (`%`).", "explanation": "" }, # TODO: Check for specific character @@ -242,677 +246,686 @@ "solution": "Remove any tab characters and ensure there is one space before and after any ',' characters.", "explanation": "" }, -"E251": { -"original_message": "unexpected spaces around keyword / parameter equals", -"title": "This line contains spaces before or after the = in a function definition.", -"solution": "Remove any spaces that appear either before or after the = character in your function definition.", -"explanation": "" -}, -"E261": { -"original_message": "at least two spaces before inline comment", -"title": "This line contains an inline comment that does not have 2 spaces before it.", -"solution": "Ensure that your inline comment has 2 spaces before the '#' character.", -"explanation": "" -}, -"E262": { -"original_message": "inline comment should start with ‘# ‘", -"title": "Comments should start with a '#' character and have one space between the '#' character and the comment itself.", -"solution": "Ensure that your inline comment starts with a '#' character followed by a space and then the comment itself.", -"explanation": "https://lintlyci.github.io/Flake8Rules/rules/E262.html this rule seems to be more about the space after the pound sign though????" -}, -"E265": { -"original_message": "block comment should start with ‘# ‘", -"title": "Comments should start with a '#' character and have one space between the '#' character and the comment itself.", -"solution": "Ensure that your block comment starts with a '#' character followed by a space and then the comment itself.", -"explanation": "" -}, -"E266": { -"original_message": "too many leading ‘#’ for block comment", -"title": "Comments should only start with a single '#' character.", -"solution": "Ensure your comment only starts with one '#' character.", -"explanation": "" -}, -"E271": { -"original_message": "multiple spaces after keyword", -"title": "This line contains more than one space after a keyword.", -"solution": "Ensure there is only one space after any keywords.", -"explanation": "" -}, -"E272": { -"original_message": "multiple spaces before keyword", -"title": "This line contains more than one space before a keyword.", -"solution": "Ensure there is only one space before any keywords.", -"explanation": "" -}, -"E273": { -"original_message": "tab after keyword", -"title": "This line contains a tab character after a keyword.", -"solution": "Ensure there is only one space after any keywords.", -"explanation": "" -}, -"E274": { -"original_message": "tab before keyword", -"title": "This line contains a tab character before a keyword.", -"solution": "Ensure there is only one space before any keywords.", -"explanation": "" -}, -"E275": { -"original_message": "missing whitespace after keyword", -"title": "This line is missing a space after a keyword.", -"solution": "Ensure there is one space after any keywords.", -"explanation": "" -}, -"E301": { -"original_message": "expected 1 blank line, found 0", -"title": "This line is missing a blank line between the methods of a class.", -"solution": "Add a blank line in between your class methods.", -"explanation": "" -}, -"E302": { -"original_message": "expected 2 blank lines, found 0", -"title": "Two blank lines are expected between functions and classes.", -"solution": "Ensure there are two blank lines between functions and classes.", -"explanation": "" -}, -"E303": { -"original_message": "too many blank lines (3)", -"title": "There are too many blank lines.", -"solution": "Ensure there are two blank lines between functions and classes and one blank line between methods of a class.", -"explanation": "" -}, -"E304": { -"original_message": "blank lines found after function decorator", -"title": "This line contains a blank line after a function decorator.", -"solution": "Ensure that there are no blank lines between a function decorator and the function it is decorating.", -"explanation": "" -}, -"E305": { -"original_message": "expected 2 blank lines after end of function or class", -"title": "Functions and classes should have two blank lines after them.", -"solution": "Ensure that functions and classes should have two blank lines after them.", -"explanation": "" -}, -"E306": { -"original_message": "expected 1 blank line before a nested definition", -"title": "Nested definitions should have one blank line before them.", -"solution": "Ensure there is a blank line above your nested definition.", -"explanation": "" -}, -"E401": { -"original_message": "multiple imports on one line", -"title": "This line contains imports from different modules on the same line.", -"solution": "Ensure import statements from different modules occur on their own line.", -"explanation": "" -}, -"E402": { -"original_message": "module level import not at top of file", -"title": "Module imports should be at the top of the file and there should be no statements in between module level imports.", -"solution": "Ensure all import statements are at the top of the file and there are no statements in between imports.", -"explanation": "" -}, -"E501": { -"original_message": "line too long (82 > 79 characters)", -"title": "This line is longer than 79 characters.", -"solution": "You can insert a backslash character (\\) to wrap the text onto the next line.", -"explanation": "Limiting the line width makes it possible to have several files open side-by-side, and works well when using code review tools that present the two versions in adjacent columns." -}, -"E502": { -"original_message": "the backslash is redundant between brackets", -"title": "There is no need for a backslash (\\) between brackets.", -"solution": "Remove any backslashes between brackets.", -"explanation": "" -}, -"E701": { -"original_message": "multiple statements on one line (colon)", -"title": "This line contains multiple statements.", -"solution": "Make sure that each statement is on its own line.", -"explanation": "This improves readability." -}, -"E702": { -"original_message": "multiple statements on one line (semicolon)", -"title": "This line contains multiple statements.", -"solution": "Make sure that each statement is on its own line.", -"explanation": "This improves readability." -}, -"E703": { -"original_message": "statement ends with a semicolon", -"title": "This line ends in a semicolon (;).", -"solution": "Remove the semicolon from the end of the line.", -"explanation": "" -}, - # E704 ignored by default -"E704": { -"original_message": "multiple statements on one line (def)", -"title": "This line contains multiple statements.", -"solution": "Make sure multiple statements of a function definition are on their own separate lines.", -"explanation": "" -}, -"E711": { -"original_message": "comparison to None should be ‘if cond is None:’", -"title": "Comparisons to objects such as True, False and None should use 'is' or 'is not' instead of '==' and '!='.", -"solution": "Replace != with 'is not' and '==' with 'is'.", -"explanation": "" -}, -"E712": { -"original_message": "comparison to True should be ‘if cond is True:’ or ‘if cond:’", -"title": "Comparisons to objects such as True, False and None should use 'is' or 'is not' instead of '==' and '!='.", -"solution": "Replace != with 'is not' and '==' with 'is'.", -"explanation": "" -}, -"E713": { -"original_message": "test for membership should be ‘not in’", -"title": "When testing whether or not something is in an object use the form `x not in the_object` instead of `not x in the_object`.", -"solution": "Use the form `not x in the_object` instead of `x not in the_object`.", -"explanation": "This improves readability." -}, -"E714": { -"original_message": "test for object identity should be ‘is not’", -"title": "When testing for object identity use the form `x is not None` rather than `not x is None`.", -"solution": "Use the form `x is not None` rather than `not x is None`.", -"explanation": "This improves readability." -}, -"E721": { -"original_message": "do not compare types, use ‘isinstance()’", -"title": "You should compare an objects type by using `isinstance()` instead of `==`. This is because `isinstance` can handle subclasses as well.", -"solution": "Use `if isinstance(user, User)` instead of `if type(user) == User`.", -"explanation": "" -}, -"E722": { -"original_message": "do not use bare except, specify exception instead", -"title": "", -"solution": "", -"explanation": "" -}, -"E731": { -"original_message": "do not assign a lambda expression, use a def", -"title": "This line assigns a lambda expression instead of defining it as a function using `def`.", -"solution": "", -"explanation": "The primary reason for this is debugging. Lambdas show as in tracebacks, where functions will display the function’s name." -}, -"E741": { -"original_message": "do not use variables named ‘l’, ‘O’, or ‘I’", -"title": "This line uses one of the variables named ‘l’, ‘O’, or ‘I’", -"solution": "Change the names of these variables to something more descriptive.", -"explanation": "Variables named I, O, and l can be very hard to read. This is because the letter I and the letter l are easily confused, and the letter O and the number 0 can be easily confused." -}, -"E742": { -"original_message": "do not define classes named ‘l’, ‘O’, or ‘I’", -"title": "This line contains a class named ‘l’, ‘O’, or ‘I’", -"solution": "Change the names of these classes to something more descriptive.", -"explanation": "Classes named I, O, and l can be very hard to read. This is because the letter I and the letter l are easily confused, and the letter O and the number 0 can be easily confused." -}, -"E743": { -"original_message": "do not define functions named ‘l’, ‘O’, or ‘I’", -"title": "This line contains a function named ‘l’, ‘O’, or ‘I’", -"solution": "Change the names of these functions to something more descriptive.", -"explanation": "Functions named I, O, and l can be very hard to read. This is because the letter I and the letter l are easily confused, and the letter O and the number 0 can be easily confused." -}, -"W191": { -"original_message": "indentation contains tabs", -"title": "This line contains tabs when only spaces are expected.", -"solution": "Replace any tabs in your indentation with spaces.", -"explanation": "" -}, -"W291": { -"original_message": "trailing whitespace", -"title": "This line contains whitespace after the final character.", -"solution": "Remove any extra whitespace at the end of each line.", -"explanation": "" -}, -"W292": { -"original_message": "no newline at end of file", -"title": "Files should end with a newline.", -"solution": "Add a newline to the end of your file.", -"explanation": "" -}, -"W293": { -"original_message": "blank line contains whitespace", -"title": "Blank lines should not contain any tabs or spaces.", -"solution": "Remove any whitespace from blank lines.", -"explanation": "" -}, -"W391": { -"original_message": "blank line at end of file", -"title": "There are either zero, two, or more than two blank lines at the end of your file.", -"solution": "Ensure there is only one blank line at the end of your file.", -"explanation": "" -}, - # W503 ignored by default - # This seems contradicitng... https://lintlyci.github.io/Flake8Rules/rules/W503.html -"W503": { -"original_message": "line break before binary operator", -"title": "", -"solution": "", -"explanation": "" -}, - # W504 ignored by default - # same as above https://lintlyci.github.io/Flake8Rules/rules/W504.html -"W504": { -"original_message": "line break after binary operator", -"title": "", -"solution": "", -"explanation": "" -}, - # W505 ignored by default -"W505": { -"original_message": "doc line too long (82 > 79 characters)", -"title": "This line is longer than 79 characters.", -"solution": "You can insert a backslash character (\\) to wrap the text onto the next line.", -"explanation": "" -}, -"W601": { -"original_message": ".has_key() is deprecated, use ‘in’", -"title": ".has_key() was deprecated in Python 2. It is recommended to use the in operator instead.", -"solution": "", -"explanation": "" -}, -"W602": { -"original_message": "deprecated form of raising exception", -"title": "The raise Exception, message form of raising exceptions is no longer supported. Use the new form.", -"solution": "Instead of the form raise ErrorType, 'Error message' use the form raise ErrorType('Error message')", -"explanation": "" -}, -"W603": { -"original_message": "‘<>’ is deprecated, use ‘!=’", -"title": "<> has been removed in Python 3.", -"solution": "Replace any occurences of <> with !=.", -"explanation": "" -}, -"W604": { -"original_message": "backticks are deprecated, use ‘repr()’", -"title": "Backticks have been removed in Python 3.", -"solution": "Use the built-in function repr() instead.", -"explanation": "" -}, -"W605": { -"original_message": "invalid escape sequence ‘x’", -"title": "", -"solution": "", -"explanation": "" -}, -"W606": { -"original_message": "‘async’ and ‘await’ are reserved keywords starting with Python 3.7", -"title": "", -"solution": "", -"explanation": "" -}, -"D100": { -"original_message": "Missing docstring in public module", -"title": "Modules should have docstrings.", -"solution": "Add a docstring to your module.", -"explanation": "A docstring is a comment at the top of your module that briefly explains the purpose of the module." -}, -"D101": { -"original_message": "Missing docstring in public class", -"title": "Classes should have docstrings.", -"solution": "Add a docstring to your class.", -"explanation": "" -}, -"D102": { -"original_message": "Missing docstring in public method", -"title": "Methods should have docstrings.", -"solution": "Add a docstring to your method.", -"explanation": "" -}, -"D103": { -"original_message": "Missing docstring in public function", -"title": "Functions should have docstrings.", -"solution": "Add a docstring to your function.", - "explanation": "A docstring is a comment at the top of your function that briefly explains the purpose of the module.
For example:
def get_waypoint_latlng(number):
\"\"\"Return the latitude and longitude values of the waypoint.\"\"\"
" - # TODO: Check this works -}, -"D104": { -"original_message": "Missing docstring in public package", -"title": "Packages should have docstrings.", -"solution": "Add a docstring to your package.", -"explanation": "" -}, -"D105": { -"original_message": "Missing docstring in magic method", -"title": "Magic methods should have docstrings.", -"solution": "Add a docstring to your magic method.", -"explanation": "" -}, -"D106": { -"original_message": "Missing docstring in public nested class", -"title": "Public nested classes should have docstrings.", -"solution": "Add a docstring to your public nested class.", -"explanation": "" -}, -"D107": { -"original_message": "Missing docstring in __init__", -"title": "The __init__ method should have a docstring.", -"solution": "Add a docstring to your __init__ method.", -"explanation": "" -}, -"D200": { -"original_message": "One-line docstring should fit on one line with quotes", -"title": "Docstrings that are one line long should fit on one line with quotes.", -"solution": "Put your docstring on one line with quotes.", -"explanation": "" -}, -"D201": { -"original_message": "No blank lines allowed before function docstring", -"title": "Function docstrings should not have blank lines before them.", -"solution": "Remove any blank lines before your function docstring.", -"explanation": "" -}, -"D202": { -"original_message": "No blank lines allowed after function docstring", -"title": "Function docstrings should not have blank lines after them.", -"solution": "Remove any blank lines after your function docstring.", -"explanation": "" -}, -"D203": { -"original_message": "1 blank line required before class docstring", -"title": "Class docstrings should have 1 blank line before them.", -"solution": "Insert 1 blank line before your class docstring.", -"explanation": "" -}, -"D204": { -"original_message": "1 blank line required after class docstring", -"title": "Class docstrings should have 1 blank line after them.", -"solution": "Insert 1 blank line after your class docstring.", -"explanation": "" -}, -"D205": { -"original_message": "1 blank line required between summary line and description", -"title": "There should be 1 blank line between the summary line and the description.", -"solution": "Insert 1 blank line between the summary line and the description.", -"explanation": "" -}, -"D206": { -"original_message": "Docstring should be indented with spaces, not tabs", -"title": "Docstrings should be indented using spaces, not tabs.", -"solution": "Make sure your docstrings are indented using spaces instead of tabs.", -"explanation": "" -}, -"D207": { -"original_message": "Docstring is under-indented", -"title": "Docstring is under-indented.", -"solution": "Add indentation levels to your docstring until it is at the correct indentation level.", -"explanation": "" -}, -"D208": { -"original_message": "Docstring is over-indented", -"title": "Docstring is over-indented.", -"solution": "Remove indentation levels from your docstring until it is at the correct indentation level.", -"explanation": "" -}, -"D209": { -"original_message": "Multi-line docstring closing quotes should be on a separate line", -"title": "Docstrings that are longer than one line should have closing quotes on a separate line.", -"solution": "Put the closing quotes of your docstring on a separate line.", -"explanation": "" -}, -"D210": { -"original_message": "No whitespaces allowed surrounding docstring text", -"title": "Text in docstrings should not be surrounded by whitespace.", -"solution": "Remove any whitespace from the start and end of your docstring.", -"explanation": "" -}, -"D211": { -"original_message": "No blank lines allowed before class docstring", -"title": "Class docstrings should not have blank lines before them.", -"solution": "Remove any blank lines before your class docstring.", -"explanation": "" -}, -"D212": { -"original_message": "Multi-line docstring summary should start at the first line", -"title": "Docstrings that are more than one line long should start at the first line.", -"solution": "Ensure your docstring starts on the first line with quotes.", -"explanation": "" -}, -"D213": { -"original_message": "Multi-line docstring summary should start at the second line", -"title": "Docstrings that are more than one line long should start at the second line.", -"solution": "Ensure your docstring starts on the second line, which is the first line without quotes.", -"explanation": "" -}, -"D214": { -"original_message": "Section is over-indented", -"title": "Section is indented by too many levels.", -"solution": "Remove indentation levels from this section until it is at the correct indentation level.", -"explanation": "" -}, -"D215": { -"original_message": "Section underline is over-indented", -"title": "Section underline is indented by too many levels.", -"solution": "Remove indentation levels from this section underline until it is at the correct indentation level.", -"explanation": "" -}, -"D300": { -"original_message": "Use “”“triple double quotes”“”", -"title": "Use “”“triple double quotes”“” around docstrings.", -"solution": "Use “”“triple double quotes”“” around your docstring.", -"explanation": "" -}, -"D301": { -"original_message": "Use r”“” if any backslashes in a docstring", -"title": "Use r”“” if there are any backslashes in a docstring.", -"solution": "Use r”“” at the beginning of your docstring if it contains any backslashes.", -"explanation": "" -}, -"D302": { -"original_message": "Use u”“” for Unicode docstrings", -"title": "Use u”“” for docstrings that contain Unicode.", -"solution": "Use u”“” at the beginning of your docstring if it contains any Unicode.", -"explanation": "" -}, -"D400": { -"original_message": "First line should end with a period", -"title": "The first line in docstrings should end with a period.", -"solution": "Add a period to the end of the first line in your docstring.", -"explanation": "" -}, -"D401": { -"original_message": "First line should be in imperative mood", -"title": "The first line in docstrings should read like a command.", -"solution": "Ensure the first line in your docstring reads like a command, not a description. For example 'Do this' instead of 'Does this', 'Return this' instead of 'Returns this'.", -"explanation": "" -}, -"D402": { -"original_message": "First line should not be the function’s “signature”", -"title": "The first line in docstrings should not be the function’s “signature”.", -"solution": "Move the function’s “signature” to a different line or remove it completely.", -"explanation": "" -}, -"D403": { -"original_message": "First line should not be the function’s “signature”", -"title": "The first line in docstrings should not be the function’s “signature”.", -"solution": "Move the function’s “signature” to a different line or remove it completely.", -"explanation": "" -}, -"D404": { -"original_message": "First word of the docstring should not be 'This'", -"title": "First word of the docstring should not be 'This'.", -"solution": "Rephrase the docstring so that the first word is not 'This'.", -"explanation": "" -}, -"D405": { -"original_message": "Section name should be properly capitalized", -"title": "Section name should be properly capitalised.", -"solution": "Capitalise the section name.", -"explanation": "" -}, -"D406": { -"original_message": "Section name should end with a newline", -"title": "Section names should end with a newline.", -"solution": "Add a newline after the section name.", -"explanation": "" -}, -"D407": { -"original_message": "Missing dashed underline after section", -"title": "Section names should have a dashed line underneath them.", -"solution": "Add a dashed line underneath the section name.", -"explanation": "" -}, -"D408": { -"original_message": "Section underline should be in the line following the section’s name", -"title": "Dashed line should be on the line following the section's name.", -"solution": "Put the dashed underline on the line immediately following the section name.", -"explanation": "" -}, -"D409": { -"original_message": "Section underline should match the length of its name", -"title": "Dashed section underline should match the length of the section's name.", -"solution": "Add or remove dashes from the dashed underline until it matches the length of the section name.", -"explanation": "" -}, -"D410": { -"original_message": "Missing blank line after section", -"title": "Section should have a blank line after it.", -"solution": "Add a blank line after the section.", -"explanation": "" -}, -"D411": { -"original_message": "Missing blank line before section", -"title": "Section should have a blank line before it.", -"solution": "Add a blank line before the section.", -"explanation": "" -}, -"D412": { -"original_message": "No blank lines allowed between a section header and its content", -"title": "There should be no blank lines between a section header and its content.", -"solution": "Remove any blank lines that are between the section header and its content.", -"explanation": "" -}, -"D413": { -"original_message": "Missing blank line after last section", -"title": "The last section in a docstring should have a blank line after it.", -"solution": "Add a blank line after the last section in your docstring.", -"explanation": "" -}, -"D414": { -"original_message": "Section has no content", -"title": "Sections in docstrings must have content.", -"solution": "Add content to the section in your docstring.", -"explanation": "" -}, -"D415": { -"original_message": "First line should end with a period, question mark, or exclamation point", -"title": "The first line in your docstring should end with a period, question mark, or exclamation point.", -"solution": "Add a period, question mark, or exclamation point to the end of the first line in your docstring.", -"explanation": "" -}, -"D416": { -"original_message": "Section name should end with a colon", -"title": "Section names should end with a colon.", -"solution": "Add a colon to the end of your section name.", -"explanation": "" -}, -"D417": { -"original_message": "Missing argument descriptions in the docstring", -"title": "Docstrings should include argument descriptions.", -"solution": "Add argument descriptions to your docstring.", -"explanation": "" -}, -"N801": { -"original_message": "class names should use CapWords convention", -"title": "Class names should use the CapWords convention.", -"solution": "Edit your class names to follow the CapWords convention.", -"explanation": "" -}, -"N802": { -"original_message": "function name should be lowercase", -"title": "Function names should be lowercase.", -"solution": "Edit your function names to be lowercase.", -"explanation": "" -}, -"N803": { -"original_message": "argument name should be lowercase", -"title": "Argument names should be lowercase.", -"solution": "Edit your argument names to be lowercase.", -"explanation": "" -}, -"N804": { -"original_message": "first argument of a classmethod should be named 'cls'", -"title": "The first argument of a classmethod should be named 'cls'.", -"solution": "Edit the first argument in your classmethod to be named 'cls'.", -"explanation": "" -}, -"N805": { -"original_message": "first argument of a method should be named 'self'", -"title": "The first argument of a method should be named 'self'.", -"solution": "Edit the first argument in your method to be named 'self'.", -"explanation": "" -}, -"N806": { -"original_message": "variable in function should be lowercase", -"title": "Variables in functions should be lowercase.", -"solution": "Edit the variable in your function to be lowercase.", -"explanation": "" -}, -"N807": { -"original_message": "function name should not start and end with '__'", -"title": "Function names should not start and end with '__'", -"solution": "Edit the function name so that it does not start and end with '__'", -"explanation": "" -}, -"N811": { -"original_message": "constant imported as non constant", -"title": "Import that should have been imported as a constant has been imported as a non constant.", -"solution": "Edit the import to be imported as a constant (use all capital letters in the 'import as...' name).", -"explanation": "" -}, -"N812": { -"original_message": "lowercase imported as non lowercase", -"title": "Import that should have been imported as lowercase has been imported as non lowercase", -"solution": "Edit the import to be imported as lowercase (use lowercase in the 'import as...' name).", -"explanation": "" -}, -"N813": { -"original_message": "camelcase imported as lowercase", -"title": "Import that should have been imported as camelCase has been imported as lowercase.", -"solution": "Edit the import to be imported as camelCase (use camelCase in the 'import as ...' name).", -"explanation": "" -}, -"N814": { -"original_message": "camelcase imported as constant", -"title": "Import that should have been imported as camelCase has been imported as a constant.", -"solution": "Edit the import to be imported as camelCase (use camelCase in the 'import as ...' name).", -"explanation": "" -}, -"N815": { -"original_message": "mixedCase variable in class scope", -"title": "Mixed case variable used in the class scope", -"solution": "Edit the variable name so that it doesn't use mixedCase.", -"explanation": "" -}, -"N816": { -"original_message": "mixedCase variable in global scope", -"title": "Mixed case variable used in the global scope", -"solution": "Edit the variable name so that it doesn't use mixedCase.", -"explanation": "" -}, -"N817": { -"original_message": "camelcase imported as acronym", -"title": "Import that should have been imported as camelCase has been imported as an acronym.", -"solution": "Edit the import to be imported as camelCase (use camelCase in the 'import as...' name).", -"explanation": "" -}, -"Q000": { -"original_message": "Remove bad quotes", -"title": "Use single quotes (') instead of double quotes (\").", -"solution": "Replace any double quotes with single quotes.", -"explanation": "" -}, -"Q001": { -"original_message": "Remove bad quotes from multiline string", -"title": "Use single quotes (') instead of double quotes (\") in multiline strings.", -"solution": "Replace any double quotes in your multiline string with single quotes.", -"explanation": "" -}, -"Q002": { -"original_message": "Remove bad quotes from docstring", -"title": "Use single quotes (') instead of double quotes (\") in docstrings.", -"solution": "Replace any double quotes in your docstring with single quotes.", -"explanation": "" -}, -"Q003": { -"original_message": "Change outer quotes to avoid escaping inner quotes", -"title": "Change outer quotes to avoid escaping inner quotes.", -"solution": "Make either the outer quotes single (') and the inner quotes double (\"), or the outer quotes double and the inner quotes single.", -"explanation": "" -} + "E251": { + "original_message": "unexpected spaces around keyword / parameter equals", + "title": "This line contains spaces before or after the = in a function definition.", + "solution": "Remove any spaces that appear either before or after the = character in your function definition.", + "explanation": "" + }, + "E261": { + "original_message": "at least two spaces before inline comment", + "title": "This line contains an inline comment that does not have 2 spaces before it.", + "solution": "Ensure that your inline comment has 2 spaces before the '#' character.", + "explanation": "" + }, + "E262": { + "original_message": "inline comment should start with ‘# ‘", + "title": "Comments should start with a '#' character and have one space between the '#' character and the comment itself.", + "solution": "Ensure that your inline comment starts with a '#' character followed by a space and then the comment itself.", + "explanation": "https://lintlyci.github.io/Flake8Rules/rules/E262.html this rule seems to be more about the space after the pound sign though????" + }, + "E265": { + "original_message": "block comment should start with ‘# ‘", + "title": "Comments should start with a '#' character and have one space between the '#' character and the comment itself.", + "solution": "Ensure that your block comment starts with a '#' character followed by a space and then the comment itself.", + "explanation": "" + }, + "E266": { + "original_message": "too many leading ‘#’ for block comment", + "title": "Comments should only start with a single '#' character.", + "solution": "Ensure your comment only starts with one '#' character.", + "explanation": "" + }, + "E271": { + "original_message": "multiple spaces after keyword", + "title": "This line contains more than one space after a keyword.", + "solution": "Ensure there is only one space after any keywords.", + "explanation": "" + }, + "E272": { + "original_message": "multiple spaces before keyword", + "title": "This line contains more than one space before a keyword.", + "solution": "Ensure there is only one space before any keywords.", + "explanation": "" + }, + "E273": { + "original_message": "tab after keyword", + "title": "This line contains a tab character after a keyword.", + "solution": "Ensure there is only one space after any keywords.", + "explanation": "" + }, + "E274": { + "original_message": "tab before keyword", + "title": "This line contains a tab character before a keyword.", + "solution": "Ensure there is only one space before any keywords.", + "explanation": "" + }, + "E275": { + "original_message": "missing whitespace after keyword", + "title": "This line is missing a space after a keyword.", + "solution": "Ensure there is one space after any keywords.", + "explanation": "" + }, + "E301": { + "original_message": "expected 1 blank line, found 0", + "title": "This line is missing a blank line between the methods of a class.", + "solution": "Add a blank line in between your class methods.", + "explanation": "" + }, + "E302": { + "original_message": "expected 2 blank lines, found 0", + "title": "Two blank lines are expected between functions and classes.", + "solution": "Ensure there are two blank lines between functions and classes.", + "explanation": "" + }, + "E303": { + "original_message": "too many blank lines (3)", + "title": "There are too many blank lines.", + "solution": "Ensure there are two blank lines between functions and classes and one blank line between methods of a class.", + "explanation": "" + }, + "E304": { + "original_message": "blank lines found after function decorator", + "title": "This line contains a blank line after a function decorator.", + "solution": "Ensure that there are no blank lines between a function decorator and the function it is decorating.", + "explanation": "" + }, + "E305": { + "original_message": "expected 2 blank lines after end of function or class", + "title": "Functions and classes should have two blank lines after them.", + "solution": "Ensure that functions and classes should have two blank lines after them.", + "explanation": "" + }, + "E306": { + "original_message": "expected 1 blank line before a nested definition", + "title": "Nested definitions should have one blank line before them.", + "solution": "Ensure there is a blank line above your nested definition.", + "explanation": "" + }, + "E401": { + "original_message": "multiple imports on one line", + "title": "This line contains imports from different modules on the same line.", + "solution": "Ensure import statements from different modules occur on their own line.", + "explanation": "" + }, + "E402": { + "original_message": "module level import not at top of file", + "title": "Module imports should be at the top of the file and there should be no statements in between module level imports.", + "solution": "Ensure all import statements are at the top of the file and there are no statements in between imports.", + "explanation": "" + }, + "E501": { + "original_message": "line too long (82 > 79 characters)", + "title": "This line is longer than 79 characters.", + "solution": "You can insert a backslash character (\\) to wrap the text onto the next line.", + "explanation": "Limiting the line width makes it possible to have several files open side-by-side, and works well when using code review tools that present the two versions in adjacent columns." + }, + "E502": { + "original_message": "the backslash is redundant between brackets", + "title": "There is no need for a backslash (\\) between brackets.", + "solution": "Remove any backslashes between brackets.", + "explanation": "" + }, + "E701": { + "original_message": "multiple statements on one line (colon)", + "title": "This line contains multiple statements.", + "solution": "Make sure that each statement is on its own line.", + "explanation": "This improves readability." + }, + "E702": { + "original_message": "multiple statements on one line (semicolon)", + "title": "This line contains multiple statements.", + "solution": "Make sure that each statement is on its own line.", + "explanation": "This improves readability." + }, + "E703": { + "original_message": "statement ends with a semicolon", + "title": "This line ends in a semicolon (;).", + "solution": "Remove the semicolon from the end of the line.", + "explanation": "" + }, + # E704 ignored by default + "E704": { + "original_message": "multiple statements on one line (def)", + "title": "This line contains multiple statements.", + "solution": "Make sure multiple statements of a function definition are on their own separate lines.", + "explanation": "" + }, + "E711": { + "original_message": "comparison to None should be ‘if cond is None:’", + "title": "Comparisons to objects such as True, False and None should use 'is' or 'is not' instead of '==' and '!='.", + "solution": "Replace != with 'is not' and '==' with 'is'.", + "explanation": "" + }, + "E712": { + "original_message": "comparison to True should be ‘if cond is True:’ or ‘if cond:’", + "title": "Comparisons to objects such as True, False and None should use 'is' or 'is not' instead of '==' and '!='.", + "solution": "Replace != with 'is not' and '==' with 'is'.", + "explanation": "" + }, + "E713": { + "original_message": "test for membership should be ‘not in’", + "title": "When testing whether or not something is in an object use the form `x not in the_object` instead of `not x in the_object`.", + "solution": "Use the form `not x in the_object` instead of `x not in the_object`.", + "explanation": "This improves readability." + }, + "E714": { + "original_message": "test for object identity should be ‘is not’", + "title": "When testing for object identity use the form `x is not None` rather than `not x is None`.", + "solution": "Use the form `x is not None` rather than `not x is None`.", + "explanation": "This improves readability." + }, + "E721": { + "original_message": "do not compare types, use ‘isinstance()’", + "title": "You should compare an objects type by using `isinstance()` instead of `==`. This is because `isinstance` can handle subclasses as well.", + "solution": "Use `if isinstance(user, User)` instead of `if type(user) == User`.", + "explanation": "" + }, + "E722": { + "original_message": "do not use bare except, specify exception instead", + "title": "", + "solution": "", + "explanation": "" + }, + "E731": { + "original_message": "do not assign a lambda expression, use a def", + "title": "This line assigns a lambda expression instead of defining it as a function using `def`.", + "solution": "", + "explanation": "The primary reason for this is debugging. Lambdas show as `` in tracebacks, where functions will display the function’s name." + }, + "E741": { + "original_message": "do not use variables named ‘l’, ‘O’, or ‘I’", + "title": "This line uses one of the variables named ‘l’, ‘O’, or ‘I’", + "solution": "Change the names of these variables to something more descriptive.", + "explanation": "Variables named I, O, and l can be very hard to read. This is because the letter I and the letter l are easily confused, and the letter O and the number 0 can be easily confused." + }, + "E742": { + "original_message": "do not define classes named ‘l’, ‘O’, or ‘I’", + "title": "This line contains a class named ‘l’, ‘O’, or ‘I’", + "solution": "Change the names of these classes to something more descriptive.", + "explanation": "Classes named I, O, and l can be very hard to read. This is because the letter I and the letter l are easily confused, and the letter O and the number 0 can be easily confused." + }, + "E743": { + "original_message": "do not define functions named ‘l’, ‘O’, or ‘I’", + "title": "This line contains a function named ‘l’, ‘O’, or ‘I’", + "solution": "Change the names of these functions to something more descriptive.", + "explanation": "Functions named I, O, and l can be very hard to read. This is because the letter I and the letter l are easily confused, and the letter O and the number 0 can be easily confused." + }, + "W191": { + "original_message": "indentation contains tabs", + "title": "This line contains tabs when only spaces are expected.", + "solution": "Replace any tabs in your indentation with spaces.", + "explanation": "" + }, + "W291": { + "original_message": "trailing whitespace", + "title": "This line contains whitespace after the final character.", + "solution": "Remove any extra whitespace at the end of each line.", + "explanation": "" + }, + "W292": { + "original_message": "no newline at end of file", + "title": "Files should end with a newline.", + "solution": "Add a newline to the end of your file.", + "explanation": "" + }, + "W293": { + "original_message": "blank line contains whitespace", + "title": "Blank lines should not contain any tabs or spaces.", + "solution": "Remove any whitespace from blank lines.", + "explanation": "" + }, + "W391": { + "original_message": "blank line at end of file", + "title": "There are either zero, two, or more than two blank lines at the end of your file.", + "solution": "Ensure there is only one blank line at the end of your file.", + "explanation": "" + }, + # W503 ignored by default + # This seems contradicitng... https://lintlyci.github.io/Flake8Rules/rules/W503.html + "W503": { + "original_message": "line break before binary operator", + "title": "", + "solution": "", + "explanation": "" + }, + # W504 ignored by default + # same as above https://lintlyci.github.io/Flake8Rules/rules/W504.html + "W504": { + "original_message": "line break after binary operator", + "title": "", + "solution": "", + "explanation": "" + }, + # W505 ignored by default + "W505": { + "original_message": "doc line too long (82 > 79 characters)", + "title": "This line is longer than 79 characters.", + "solution": "You can insert a backslash character (\\) to wrap the text onto the next line.", + "explanation": "" + }, + "W601": { + "original_message": ".has_key() is deprecated, use ‘in’", + "title": ".has_key() was deprecated in Python 2. It is recommended to use the in operator instead.", + "solution": "", + "explanation": "" + }, + "W602": { + "original_message": "deprecated form of raising exception", + "title": "The raise Exception, message form of raising exceptions is no longer supported. Use the new form.", + "solution": "Instead of the form raise ErrorType, 'Error message' use the form raise ErrorType('Error message')", + "explanation": "" + }, + "W603": { + "original_message": "‘<>’ is deprecated, use ‘!=’", + "title": "<> has been removed in Python 3.", + "solution": "Replace any occurences of <> with !=.", + "explanation": "" + }, + "W604": { + "original_message": "backticks are deprecated, use ‘repr()’", + "title": "Backticks have been removed in Python 3.", + "solution": "Use the built-in function repr() instead.", + "explanation": "" + }, + "W605": { + "original_message": "invalid escape sequence ‘x’", + "title": "", + "solution": "", + "explanation": "" + }, + "W606": { + "original_message": "‘async’ and ‘await’ are reserved keywords starting with Python 3.7", + "title": "", + "solution": "", + "explanation": "" + }, + "D100": { + "original_message": "Missing docstring in public module", + "title": "Modules should have docstrings.", + "solution": "Add a docstring to your module.", + "explanation": "A docstring is a comment at the top of your module that briefly explains the purpose of the module." + }, + "D101": { + "original_message": "Missing docstring in public class", + "title": "Classes should have docstrings.", + "solution": "Add a docstring to your class.", + "explanation": "" + }, + "D102": { + "original_message": "Missing docstring in public method", + "title": "Methods should have docstrings.", + "solution": "Add a docstring to your method.", + "explanation": "" + }, + # TODO: Still need to check rest of error. + "D103": { + "original_message": "Missing docstring in public function", + "title": "Functions should have docstrings.", + "solution": "Add a docstring to your function.", + "explanation": """ +A docstring is a comment at the top of your function that briefly explains the purpose of the module. + +For example: + +```python3 +def get_waypoint_latlng(number): + \"\"\"Return the latitude and longitude values of the waypoint.\"\"\" +``` +""" + }, + "D104": { + "original_message": "Missing docstring in public package", + "title": "Packages should have docstrings.", + "solution": "Add a docstring to your package.", + "explanation": "" + }, + "D105": { + "original_message": "Missing docstring in magic method", + "title": "Magic methods should have docstrings.", + "solution": "Add a docstring to your magic method.", + "explanation": "" + }, + "D106": { + "original_message": "Missing docstring in public nested class", + "title": "Public nested classes should have docstrings.", + "solution": "Add a docstring to your public nested class.", + "explanation": "" + }, + "D107": { + "original_message": "Missing docstring in __init__", + "title": "The __init__ method should have a docstring.", + "solution": "Add a docstring to your __init__ method.", + "explanation": "" + }, + "D200": { + "original_message": "One-line docstring should fit on one line with quotes", + "title": "Docstrings that are one line long should fit on one line with quotes.", + "solution": "Put your docstring on one line with quotes.", + "explanation": "" + }, + "D201": { + "original_message": "No blank lines allowed before function docstring", + "title": "Function docstrings should not have blank lines before them.", + "solution": "Remove any blank lines before your function docstring.", + "explanation": "" + }, + "D202": { + "original_message": "No blank lines allowed after function docstring", + "title": "Function docstrings should not have blank lines after them.", + "solution": "Remove any blank lines after your function docstring.", + "explanation": "" + }, + "D203": { + "original_message": "1 blank line required before class docstring", + "title": "Class docstrings should have 1 blank line before them.", + "solution": "Insert 1 blank line before your class docstring.", + "explanation": "" + }, + "D204": { + "original_message": "1 blank line required after class docstring", + "title": "Class docstrings should have 1 blank line after them.", + "solution": "Insert 1 blank line after your class docstring.", + "explanation": "" + }, + "D205": { + "original_message": "1 blank line required between summary line and description", + "title": "There should be 1 blank line between the summary line and the description.", + "solution": "Insert 1 blank line between the summary line and the description.", + "explanation": "" + }, + "D206": { + "original_message": "Docstring should be indented with spaces, not tabs", + "title": "Docstrings should be indented using spaces, not tabs.", + "solution": "Make sure your docstrings are indented using spaces instead of tabs.", + "explanation": "" + }, + "D207": { + "original_message": "Docstring is under-indented", + "title": "Docstring is under-indented.", + "solution": "Add indentation levels to your docstring until it is at the correct indentation level.", + "explanation": "" + }, + "D208": { + "original_message": "Docstring is over-indented", + "title": "Docstring is over-indented.", + "solution": "Remove indentation levels from your docstring until it is at the correct indentation level.", + "explanation": "" + }, + "D209": { + "original_message": "Multi-line docstring closing quotes should be on a separate line", + "title": "Docstrings that are longer than one line should have closing quotes on a separate line.", + "solution": "Put the closing quotes of your docstring on a separate line.", + "explanation": "" + }, + "D210": { + "original_message": "No whitespaces allowed surrounding docstring text", + "title": "Text in docstrings should not be surrounded by whitespace.", + "solution": "Remove any whitespace from the start and end of your docstring.", + "explanation": "" + }, + "D211": { + "original_message": "No blank lines allowed before class docstring", + "title": "Class docstrings should not have blank lines before them.", + "solution": "Remove any blank lines before your class docstring.", + "explanation": "" + }, + "D212": { + "original_message": "Multi-line docstring summary should start at the first line", + "title": "Docstrings that are more than one line long should start at the first line.", + "solution": "Ensure your docstring starts on the first line with quotes.", + "explanation": "" + }, + "D213": { + "original_message": "Multi-line docstring summary should start at the second line", + "title": "Docstrings that are more than one line long should start at the second line.", + "solution": "Ensure your docstring starts on the second line, which is the first line without quotes.", + "explanation": "" + }, + "D214": { + "original_message": "Section is over-indented", + "title": "Section is indented by too many levels.", + "solution": "Remove indentation levels from this section until it is at the correct indentation level.", + "explanation": "" + }, + "D215": { + "original_message": "Section underline is over-indented", + "title": "Section underline is indented by too many levels.", + "solution": "Remove indentation levels from this section underline until it is at the correct indentation level.", + "explanation": "" + }, + "D300": { + "original_message": "Use “”“triple double quotes”“”", + "title": "Use “”“triple double quotes”“” around docstrings.", + "solution": "Use “”“triple double quotes”“” around your docstring.", + "explanation": "" + }, + "D301": { + "original_message": "Use r”“” if any backslashes in a docstring", + "title": "Use r”“” if there are any backslashes in a docstring.", + "solution": "Use r”“” at the beginning of your docstring if it contains any backslashes.", + "explanation": "" + }, + "D302": { + "original_message": "Use u”“” for Unicode docstrings", + "title": "Use u”“” for docstrings that contain Unicode.", + "solution": "Use u”“” at the beginning of your docstring if it contains any Unicode.", + "explanation": "" + }, + "D400": { + "original_message": "First line should end with a period", + "title": "The first line in docstrings should end with a period.", + "solution": "Add a period to the end of the first line in your docstring.", + "explanation": "" + }, + "D401": { + "original_message": "First line should be in imperative mood", + "title": "The first line in docstrings should read like a command.", + "solution": "Ensure the first line in your docstring reads like a command, not a description. For example 'Do this' instead of 'Does this', 'Return this' instead of 'Returns this'.", + "explanation": "" + }, + "D402": { + "original_message": "First line should not be the function’s “signature”", + "title": "The first line in docstrings should not be the function’s “signature”.", + "solution": "Move the function’s “signature” to a different line or remove it completely.", + "explanation": "" + }, + "D403": { + "original_message": "First line should not be the function’s “signature”", + "title": "The first line in docstrings should not be the function’s “signature”.", + "solution": "Move the function’s “signature” to a different line or remove it completely.", + "explanation": "" + }, + "D404": { + "original_message": "First word of the docstring should not be 'This'", + "title": "First word of the docstring should not be 'This'.", + "solution": "Rephrase the docstring so that the first word is not 'This'.", + "explanation": "" + }, + "D405": { + "original_message": "Section name should be properly capitalized", + "title": "Section name should be properly capitalised.", + "solution": "Capitalise the section name.", + "explanation": "" + }, + "D406": { + "original_message": "Section name should end with a newline", + "title": "Section names should end with a newline.", + "solution": "Add a newline after the section name.", + "explanation": "" + }, + "D407": { + "original_message": "Missing dashed underline after section", + "title": "Section names should have a dashed line underneath them.", + "solution": "Add a dashed line underneath the section name.", + "explanation": "" + }, + "D408": { + "original_message": "Section underline should be in the line following the section’s name", + "title": "Dashed line should be on the line following the section's name.", + "solution": "Put the dashed underline on the line immediately following the section name.", + "explanation": "" + }, + "D409": { + "original_message": "Section underline should match the length of its name", + "title": "Dashed section underline should match the length of the section's name.", + "solution": "Add or remove dashes from the dashed underline until it matches the length of the section name.", + "explanation": "" + }, + "D410": { + "original_message": "Missing blank line after section", + "title": "Section should have a blank line after it.", + "solution": "Add a blank line after the section.", + "explanation": "" + }, + "D411": { + "original_message": "Missing blank line before section", + "title": "Section should have a blank line before it.", + "solution": "Add a blank line before the section.", + "explanation": "" + }, + "D412": { + "original_message": "No blank lines allowed between a section header and its content", + "title": "There should be no blank lines between a section header and its content.", + "solution": "Remove any blank lines that are between the section header and its content.", + "explanation": "" + }, + "D413": { + "original_message": "Missing blank line after last section", + "title": "The last section in a docstring should have a blank line after it.", + "solution": "Add a blank line after the last section in your docstring.", + "explanation": "" + }, + "D414": { + "original_message": "Section has no content", + "title": "Sections in docstrings must have content.", + "solution": "Add content to the section in your docstring.", + "explanation": "" + }, + "D415": { + "original_message": "First line should end with a period, question mark, or exclamation point", + "title": "The first line in your docstring should end with a period, question mark, or exclamation point.", + "solution": "Add a period, question mark, or exclamation point to the end of the first line in your docstring.", + "explanation": "" + }, + "D416": { + "original_message": "Section name should end with a colon", + "title": "Section names should end with a colon.", + "solution": "Add a colon to the end of your section name.", + "explanation": "" + }, + "D417": { + "original_message": "Missing argument descriptions in the docstring", + "title": "Docstrings should include argument descriptions.", + "solution": "Add argument descriptions to your docstring.", + "explanation": "" + }, + "N801": { + "original_message": "class names should use CapWords convention", + "title": "Class names should use the CapWords convention.", + "solution": "Edit your class names to follow the CapWords convention.", + "explanation": "" + }, + "N802": { + "original_message": "function name should be lowercase", + "title": "Function names should be lowercase.", + "solution": "Edit your function names to be lowercase.", + "explanation": "" + }, + "N803": { + "original_message": "argument name should be lowercase", + "title": "Argument names should be lowercase.", + "solution": "Edit your argument names to be lowercase.", + "explanation": "" + }, + "N804": { + "original_message": "first argument of a classmethod should be named 'cls'", + "title": "The first argument of a classmethod should be named 'cls'.", + "solution": "Edit the first argument in your classmethod to be named 'cls'.", + "explanation": "" + }, + "N805": { + "original_message": "first argument of a method should be named 'self'", + "title": "The first argument of a method should be named 'self'.", + "solution": "Edit the first argument in your method to be named 'self'.", + "explanation": "" + }, + "N806": { + "original_message": "variable in function should be lowercase", + "title": "Variables in functions should be lowercase.", + "solution": "Edit the variable in your function to be lowercase.", + "explanation": "" + }, + "N807": { + "original_message": "function name should not start and end with '__'", + "title": "Function names should not start and end with '__'", + "solution": "Edit the function name so that it does not start and end with '__'", + "explanation": "" + }, + "N811": { + "original_message": "constant imported as non constant", + "title": "Import that should have been imported as a constant has been imported as a non constant.", + "solution": "Edit the import to be imported as a constant (use all capital letters in the 'import as...' name).", + "explanation": "" + }, + "N812": { + "original_message": "lowercase imported as non lowercase", + "title": "Import that should have been imported as lowercase has been imported as non lowercase", + "solution": "Edit the import to be imported as lowercase (use lowercase in the 'import as...' name).", + "explanation": "" + }, + "N813": { + "original_message": "camelcase imported as lowercase", + "title": "Import that should have been imported as camelCase has been imported as lowercase.", + "solution": "Edit the import to be imported as camelCase (use camelCase in the 'import as ...' name).", + "explanation": "" + }, + "N814": { + "original_message": "camelcase imported as constant", + "title": "Import that should have been imported as camelCase has been imported as a constant.", + "solution": "Edit the import to be imported as camelCase (use camelCase in the 'import as ...' name).", + "explanation": "" + }, + "N815": { + "original_message": "mixedCase variable in class scope", + "title": "Mixed case variable used in the class scope", + "solution": "Edit the variable name so that it doesn't use mixedCase.", + "explanation": "" + }, + "N816": { + "original_message": "mixedCase variable in global scope", + "title": "Mixed case variable used in the global scope", + "solution": "Edit the variable name so that it doesn't use mixedCase.", + "explanation": "" + }, + "N817": { + "original_message": "camelcase imported as acronym", + "title": "Import that should have been imported as camelCase has been imported as an acronym.", + "solution": "Edit the import to be imported as camelCase (use camelCase in the 'import as...' name).", + "explanation": "" + }, + "Q000": { + "original_message": "Remove bad quotes", + "title": "Use single quotes (') instead of double quotes (\").", + "solution": "Replace any double quotes with single quotes.", + "explanation": "" + }, + "Q001": { + "original_message": "Remove bad quotes from multiline string", + "title": "Use single quotes (') instead of double quotes (\") in multiline strings.", + "solution": "Replace any double quotes in your multiline string with single quotes.", + "explanation": "" + }, + "Q002": { + "original_message": "Remove bad quotes from docstring", + "title": "Use single quotes (') instead of double quotes (\") in docstrings.", + "solution": "Replace any double quotes in your docstring with single quotes.", + "explanation": "" + }, + "Q003": { + "original_message": "Change outer quotes to avoid escaping inner quotes", + "title": "Change outer quotes to avoid escaping inner quotes.", + "solution": "Make either the outer quotes single (') and the inner quotes double (\"), or the outer quotes double and the inner quotes single.", + "explanation": "" + }, } diff --git a/codewof/templates/style/component/result.html b/codewof/templates/style/component/result.html index eb2b88d3f..0d73cea99 100644 --- a/codewof/templates/style/component/result.html +++ b/codewof/templates/style/component/result.html @@ -11,14 +11,14 @@

{{ issue_count }} style issue{{ issue_count|pluralize }} - Issue code: {{ issue.error_code }} - +

{% endblock content_container %} From bde12151c78c13b56d6017b77d65158203c1d840 Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Wed, 10 Jun 2020 15:22:34 +1200 Subject: [PATCH 27/48] Render code examples in CodeMirror blocks --- codewof/static/js/style_checkers/python3.js | 23 +++++++++++++++++++++ codewof/static/scss/style_checker.scss | 12 ++++++----- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/codewof/static/js/style_checkers/python3.js b/codewof/static/js/style_checkers/python3.js index 028433c03..22d414155 100644 --- a/codewof/static/js/style_checkers/python3.js +++ b/codewof/static/js/style_checkers/python3.js @@ -110,6 +110,29 @@ function display_style_checker_results(data, textStatus, jqXHR) { $('#check_btn').hide(); $('#reset-btn').show(); $('#copy-text-btn').show(); + // Render all code examples + $('.issue-card pre').each(function () { + var pre_block = this; + CodeMirror( + function (element) { + pre_block.parentNode.replaceChild(element, pre_block); + }, { + mode: { + name: 'python', + version: 3, + singleLineStringErrors: false + }, + value: pre_block.innerText.trim(), + readOnly: true, + cursorBlinkRate: -1, + lineNumbers: true, + textWrapping: false, + styleActiveLine: false, + autofocus: false, + indentUnit: 4, + viewportMargin: 0 + }); + }); } else { display_style_checker_error(); } diff --git a/codewof/static/scss/style_checker.scss b/codewof/static/scss/style_checker.scss index 158e39a72..3769152a5 100644 --- a/codewof/static/scss/style_checker.scss +++ b/codewof/static/scss/style_checker.scss @@ -12,16 +12,18 @@ $highlight_colour: #ffeb3b; .issue-card { user-select: none; - cursor: pointer; + cursor: pointer !important; + .CodeMirror { + height: auto; + } } #code-container { position: sticky; top: 0; -} - -.CodeMirror { - height: 500px; + .CodeMirror { + height: 500px; + } } .read-only { From 79e1d8985ad2d1c6e226d5070e570b8b9a9007c1 Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Wed, 10 Jun 2020 15:22:38 +1200 Subject: [PATCH 28/48] Add missing import --- codewof/style/style_checkers/python3.py | 1 + 1 file changed, 1 insertion(+) diff --git a/codewof/style/style_checkers/python3.py b/codewof/style/style_checkers/python3.py index 0ed25ae27..69907d812 100644 --- a/codewof/style/style_checkers/python3.py +++ b/codewof/style/style_checkers/python3.py @@ -5,6 +5,7 @@ from pathlib import Path from django.conf import settings from django.core.exceptions import ObjectDoesNotExist +from django.db.models import F from style.style_checkers import python3_data from style.utils import ( CHARACTER_DESCRIPTIONS, From c88f919f9715e622e97e120851cbcdbdfabe94c0 Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Wed, 10 Jun 2020 15:41:17 +1200 Subject: [PATCH 29/48] Improve padding inside issue cards --- codewof/static/scss/style_checker.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/codewof/static/scss/style_checker.scss b/codewof/static/scss/style_checker.scss index 3769152a5..204f35463 100644 --- a/codewof/static/scss/style_checker.scss +++ b/codewof/static/scss/style_checker.scss @@ -16,6 +16,9 @@ $highlight_colour: #ffeb3b; .CodeMirror { height: auto; } + .card-body > div:last-child > p:last-child { + margin-bottom: 0; + } } #code-container { From 8ee461645816ebd470d7e4c80c35f728e701fd8a Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Wed, 10 Jun 2020 15:46:12 +1200 Subject: [PATCH 30/48] Remove debugging statement --- codewof/style/style_checkers/python3.py | 1 - codewof/templates/base.html | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/codewof/style/style_checkers/python3.py b/codewof/style/style_checkers/python3.py index 69907d812..5ab7e781d 100644 --- a/codewof/style/style_checkers/python3.py +++ b/codewof/style/style_checkers/python3.py @@ -118,7 +118,6 @@ def process_line(line_text, is_example_code): 'title': error_message, 'line_number': line_number, } - print(issue_data) return issue_data diff --git a/codewof/templates/base.html b/codewof/templates/base.html index 96d660211..f819ca330 100644 --- a/codewof/templates/base.html +++ b/codewof/templates/base.html @@ -53,8 +53,9 @@ {% if request %} {% activeurl %}
From 489ca3da67293b8ea4b8f7e2a432f7a4e8e49c48 Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Wed, 10 Jun 2020 23:29:35 +1200 Subject: [PATCH 33/48] Initial style improvements --- .../general/management/commands/sampledata.py | 2 +- codewof/programming/codewof_utils.py | 6 +- .../en/celsius-to-fahrenheit/solution.py | 2 +- .../en/fahrenheit-to-celsius/solution.py | 2 +- .../content/en/sum-multiples-3-5/solution.py | 2 +- .../content/en/triangle-pattern/solution.py | 2 +- .../backdate_points_and_achievements.py | 6 +- .../management/commands/load_achievements.py | 140 ++++----- codewof/static/scss/style_checker.scss | 3 + .../management/commands/load_style_errors.py | 13 +- codewof/style/style_checkers/python3.py | 32 +- codewof/style/style_checkers/python3_data.py | 290 ++++++++++-------- codewof/style/utils.py | 2 - codewof/style/views.py | 5 +- codewof/users/views.py | 6 +- setup.cfg | 8 +- 16 files changed, 291 insertions(+), 230 deletions(-) diff --git a/codewof/general/management/commands/sampledata.py b/codewof/general/management/commands/sampledata.py index 31877f7ba..b589bfbdf 100644 --- a/codewof/general/management/commands/sampledata.py +++ b/codewof/general/management/commands/sampledata.py @@ -44,7 +44,7 @@ def handle(self, *args, **options): management.call_command('load_user_types') print(LOG_HEADER.format('Create sample users')) - User = get_user_model() + User = get_user_model() # noqa N806 # Create admin account admin = User.objects.create_superuser( 'admin', diff --git a/codewof/programming/codewof_utils.py b/codewof/programming/codewof_utils.py index cc867d0bf..15a7bad5a 100644 --- a/codewof/programming/codewof_utils.py +++ b/codewof/programming/codewof_utils.py @@ -236,13 +236,13 @@ def backdate_user(profile): profile.save() -def backdate_points_and_achievements(n=-1, ignoreFlags=True): +def backdate_points_and_achievements(n=-1, ignore_flags=True): """Perform batch backdate of all points and achievements for n profiles in the system.""" backdate_achievements_times = [] backdate_points_times = [] time_before = time.perf_counter() profiles = Profile.objects.all() - if not ignoreFlags: + if not ignore_flags: profiles = profiles.filter(has_backdated=False) if (n > 0): profiles = profiles[:n] @@ -252,7 +252,7 @@ def backdate_points_and_achievements(n=-1, ignoreFlags=True): # The commented out part below seems to break travis somehow print("Backdating user: {}/{}".format(str(i + 1), str(num_profiles))) # , end="\r") profile = profiles[i] - if not profile.has_backdated or ignoreFlags: + if not profile.has_backdated or ignore_flags: attempts = all_attempts.filter(profile=profile) achievements_time_before = time.perf_counter() diff --git a/codewof/programming/content/en/celsius-to-fahrenheit/solution.py b/codewof/programming/content/en/celsius-to-fahrenheit/solution.py index 24cad2509..932d6563c 100644 --- a/codewof/programming/content/en/celsius-to-fahrenheit/solution.py +++ b/codewof/programming/content/en/celsius-to-fahrenheit/solution.py @@ -1,2 +1,2 @@ def celsius_to_fahrenheit(temperature): - return (temperature * (9/5) + 32) + return (temperature * (9 / 5) + 32) diff --git a/codewof/programming/content/en/fahrenheit-to-celsius/solution.py b/codewof/programming/content/en/fahrenheit-to-celsius/solution.py index be3cc28ab..422f1c74c 100644 --- a/codewof/programming/content/en/fahrenheit-to-celsius/solution.py +++ b/codewof/programming/content/en/fahrenheit-to-celsius/solution.py @@ -1,2 +1,2 @@ def fahrenheit_to_celsius(temperature): - return ((temperature - 32) * 5/9) + return ((temperature - 32) * 5 / 9) diff --git a/codewof/programming/content/en/sum-multiples-3-5/solution.py b/codewof/programming/content/en/sum-multiples-3-5/solution.py index 110de0b59..8b2812578 100644 --- a/codewof/programming/content/en/sum-multiples-3-5/solution.py +++ b/codewof/programming/content/en/sum-multiples-3-5/solution.py @@ -1,6 +1,6 @@ number = int(input("Enter a positive integer: ")) sum = 0 -for i in range(1, number+1): +for i in range(1, number + 1): if (i % 3 == 0) or (i % 5 == 0): sum += i diff --git a/codewof/programming/content/en/triangle-pattern/solution.py b/codewof/programming/content/en/triangle-pattern/solution.py index 52f104e7b..2ef0700d9 100644 --- a/codewof/programming/content/en/triangle-pattern/solution.py +++ b/codewof/programming/content/en/triangle-pattern/solution.py @@ -2,5 +2,5 @@ def triangle(x): if x <= 1: print("That isn't a triangle!") else: - for i in range(1, x+1): + for i in range(1, x + 1): print(i * '*') diff --git a/codewof/programming/management/commands/backdate_points_and_achievements.py b/codewof/programming/management/commands/backdate_points_and_achievements.py index 265f96df8..457b2d18d 100644 --- a/codewof/programming/management/commands/backdate_points_and_achievements.py +++ b/codewof/programming/management/commands/backdate_points_and_achievements.py @@ -25,8 +25,8 @@ def add_arguments(self, parser): def handle(self, *args, **options): """Automatically called when the backdate command is given.""" print("Backdating points and achievements\n") - ignoreFlags = options['ignore_flags'] + ignore_flags = options['ignore_flags'] number = int(options['profiles']) - if ignoreFlags and number > 0: + if ignore_flags and number > 0: raise ValueError("If ignoring backdate flags you must backdate all profiles.") - backdate_points_and_achievements(number, ignoreFlags) + backdate_points_and_achievements(number, ignore_flags) diff --git a/codewof/programming/management/commands/load_achievements.py b/codewof/programming/management/commands/load_achievements.py index 83ddcbd51..b36b98473 100644 --- a/codewof/programming/management/commands/load_achievements.py +++ b/codewof/programming/management/commands/load_achievements.py @@ -6,116 +6,116 @@ # TODO: Consider relocating to a yaml file like the questions ACHIEVEMENTS = [ { - 'id_name': 'create-account', + 'id_name': 'create-account', 'display_name': 'Created an account!', - 'description': 'Created your very own account', - 'icon_name': 'img/icons/achievements/icons8-achievement-create-account-48.png', - 'achievement_tier': 0, - 'parent': None + 'description': 'Created your very own account', + 'icon_name': 'img/icons/achievements/icons8-achievement-create-account-48.png', + 'achievement_tier': 0, + 'parent': None }, { - 'id_name': 'questions-solved-100', + 'id_name': 'questions-solved-100', 'display_name': 'Solved one hundred questions!', - 'description': 'Solved one hundred questions', - 'icon_name': 'img/icons/achievements/icons8-question-solved-gold-50.png', - 'achievement_tier': 4, - 'parent': None + 'description': 'Solved one hundred questions', + 'icon_name': 'img/icons/achievements/icons8-question-solved-gold-50.png', + 'achievement_tier': 4, + 'parent': None }, { - 'id_name': 'questions-solved-10', + 'id_name': 'questions-solved-10', 'display_name': 'Solved ten questions!', - 'description': 'Solved ten questions', - 'icon_name': 'img/icons/achievements/icons8-question-solved-silver-50.png', - 'achievement_tier': 3, - 'parent': 'questions-solved-100' + 'description': 'Solved ten questions', + 'icon_name': 'img/icons/achievements/icons8-question-solved-silver-50.png', + 'achievement_tier': 3, + 'parent': 'questions-solved-100' }, { - 'id_name': 'questions-solved-5', + 'id_name': 'questions-solved-5', 'display_name': 'Solved five questions!', - 'description': 'Solved five questions', - 'icon_name': 'img/icons/achievements/icons8-question-solved-bronze-50.png', - 'achievement_tier': 2, - 'parent': 'questions-solved-10' + 'description': 'Solved five questions', + 'icon_name': 'img/icons/achievements/icons8-question-solved-bronze-50.png', + 'achievement_tier': 2, + 'parent': 'questions-solved-10' }, { - 'id_name': 'questions-solved-1', + 'id_name': 'questions-solved-1', 'display_name': 'Solved one question!', - 'description': 'Solved your very first question', - 'icon_name': 'img/icons/achievements/icons8-question-solved-black-50.png', - 'achievement_tier': 1, - 'parent': 'questions-solved-5' + 'description': 'Solved your very first question', + 'icon_name': 'img/icons/achievements/icons8-question-solved-black-50.png', + 'achievement_tier': 1, + 'parent': 'questions-solved-5' }, { - 'id_name': 'attempts-made-100', + 'id_name': 'attempts-made-100', 'display_name': 'Made one hundred question attempts!', - 'description': 'Attempted one hundred questions', - 'icon_name': 'img/icons/achievements/icons8-attempt-made-gold-50.png', - 'achievement_tier': 4, - 'parent': None + 'description': 'Attempted one hundred questions', + 'icon_name': 'img/icons/achievements/icons8-attempt-made-gold-50.png', + 'achievement_tier': 4, + 'parent': None }, { - 'id_name': 'attempts-made-10', + 'id_name': 'attempts-made-10', 'display_name': 'Made ten question attempts!', - 'description': 'Attempted ten questions', - 'icon_name': 'img/icons/achievements/icons8-attempt-made-silver-50.png', - 'achievement_tier': 3, - 'parent': 'attempts-made-100' + 'description': 'Attempted ten questions', + 'icon_name': 'img/icons/achievements/icons8-attempt-made-silver-50.png', + 'achievement_tier': 3, + 'parent': 'attempts-made-100' }, { - 'id_name': 'attempts-made-5', + 'id_name': 'attempts-made-5', 'display_name': 'Made five question attempts!', - 'description': 'Attempted five questions', - 'icon_name': 'img/icons/achievements/icons8-attempt-made-bronze-50.png', - 'achievement_tier': 2, - 'parent': 'attempts-made-10' + 'description': 'Attempted five questions', + 'icon_name': 'img/icons/achievements/icons8-attempt-made-bronze-50.png', + 'achievement_tier': 2, + 'parent': 'attempts-made-10' }, { - 'id_name': 'attempts-made-1', + 'id_name': 'attempts-made-1', 'display_name': 'Made your first question attempt!', - 'description': 'Attempted one question', - 'icon_name': 'img/icons/achievements/icons8-attempt-made-black-50.png', - 'achievement_tier': 1, - 'parent': 'attempts-made-5' + 'description': 'Attempted one question', + 'icon_name': 'img/icons/achievements/icons8-attempt-made-black-50.png', + 'achievement_tier': 1, + 'parent': 'attempts-made-5' }, { - 'id_name': 'consecutive-days-28', + 'id_name': 'consecutive-days-28', 'display_name': 'Worked on coding every day for four weeks!', - 'description': 'Attempted at least one question every day for four weeks', - 'icon_name': 'img/icons/achievements/icons8-calendar-28-50.png', - 'achievement_tier': 5, - 'parent': None + 'description': 'Attempted at least one question every day for four weeks', + 'icon_name': 'img/icons/achievements/icons8-calendar-28-50.png', + 'achievement_tier': 5, + 'parent': None }, { - 'id_name': 'consecutive-days-21', + 'id_name': 'consecutive-days-21', 'display_name': 'Worked on coding every day for three weeks!', - 'description': 'Attempted at least one question every day for three weeks', - 'icon_name': 'img/icons/achievements/icons8-calendar-21-50.png', - 'achievement_tier': 4, - 'parent': 'consecutive-days-28' + 'description': 'Attempted at least one question every day for three weeks', + 'icon_name': 'img/icons/achievements/icons8-calendar-21-50.png', + 'achievement_tier': 4, + 'parent': 'consecutive-days-28' }, { - 'id_name': 'consecutive-days-14', + 'id_name': 'consecutive-days-14', 'display_name': 'Worked on coding every day for two weeks!', - 'description': 'Attempted at least one question every day for two weeks', - 'icon_name': 'img/icons/achievements/icons8-calendar-14-50.png', - 'achievement_tier': 3, - 'parent': 'consecutive-days-21' + 'description': 'Attempted at least one question every day for two weeks', + 'icon_name': 'img/icons/achievements/icons8-calendar-14-50.png', + 'achievement_tier': 3, + 'parent': 'consecutive-days-21' }, { - 'id_name': 'consecutive-days-7', + 'id_name': 'consecutive-days-7', 'display_name': 'Worked on coding every day for one week!', - 'description': 'Attempted at least one question every day for one week', - 'icon_name': 'img/icons/achievements/icons8-calendar-7-50.png', - 'achievement_tier': 2, - 'parent': 'consecutive-days-14' + 'description': 'Attempted at least one question every day for one week', + 'icon_name': 'img/icons/achievements/icons8-calendar-7-50.png', + 'achievement_tier': 2, + 'parent': 'consecutive-days-14' }, { - 'id_name': 'consecutive-days-2', + 'id_name': 'consecutive-days-2', 'display_name': 'Worked on coding for two days in a row!', - 'description': 'Attempted at least one question two days in a row', - 'icon_name': 'img/icons/achievements/icons8-calendar-2-50.png', - 'achievement_tier': 1, - 'parent': 'consecutive-days-7' + 'description': 'Attempted at least one question two days in a row', + 'icon_name': 'img/icons/achievements/icons8-calendar-2-50.png', + 'achievement_tier': 1, + 'parent': 'consecutive-days-7' }, ] diff --git a/codewof/static/scss/style_checker.scss b/codewof/static/scss/style_checker.scss index b739b91ab..db210ca0c 100644 --- a/codewof/static/scss/style_checker.scss +++ b/codewof/static/scss/style_checker.scss @@ -14,6 +14,9 @@ $highlight_colour: #ffeb3b; .CodeMirror { height: auto; } + p { + margin-bottom: 0.25rem; + } .card-body > div:last-child > p:last-child { margin-bottom: 0; } diff --git a/codewof/style/management/commands/load_style_errors.py b/codewof/style/management/commands/load_style_errors.py index e5bfbe77e..be4560c42 100644 --- a/codewof/style/management/commands/load_style_errors.py +++ b/codewof/style/management/commands/load_style_errors.py @@ -8,7 +8,6 @@ from style.models import Error from utils.errors.VertoConversionError import VertoConversionError - BASE_DATA_MODULE_PATH = 'style.style_checkers.{}_data' MARKDOWN_CONVERTER = Verto( extensions=[ @@ -16,12 +15,24 @@ ], ) + class Command(management.base.BaseCommand): """Required command class for the custom Django load_style_errors command.""" help = "Load progress outcomes to database." def convert_markdown(self, module_path, code, field, markdown): + """Render Markdown string to HTML. + + Args: + module_path (str): Path to Python 3 module. + code (str): Code of style error that text belongs too. + field (str): Field of style error that text belongs too. + markdown (str): Markdown text to convert. + + Returns: + HTML of converted text. + """ MARKDOWN_CONVERTER.clear_saved_data() try: result = MARKDOWN_CONVERTER.convert(markdown) diff --git a/codewof/style/style_checkers/python3.py b/codewof/style/style_checkers/python3.py index ac86c7ffd..fd7a172c2 100644 --- a/codewof/style/style_checkers/python3.py +++ b/codewof/style/style_checkers/python3.py @@ -1,3 +1,5 @@ +"""Style checking code for Python 3 code.""" + import re import os.path import uuid @@ -6,7 +8,6 @@ from django.conf import settings from django.core.exceptions import ObjectDoesNotExist from django.db.models import F -from style.style_checkers import python3_data from style.utils import ( CHARACTER_DESCRIPTIONS, get_language_info, @@ -14,7 +15,6 @@ ) from style.models import Error - LINE_RE = re.compile(r':(?P\d+):(?P\d+): (?P\w\d+) (?P.*)$') CHARACTER_RE = re.compile(r'\'(?P.*)\'') TEMP_FILE_ROOT = settings.STYLE_CHECKER_TEMP_FILES_ROOT @@ -25,6 +25,14 @@ def python3_style_check(code): + """Run the flake8 style check on provided code. + + Args: + code (str): String of user code. + + Returns: + List of dictionaries of style checker result data. + """ # Write file to HDD filename = str(uuid.uuid4()) + TEMP_FILE_EXT filepath = Path(os.path.join(TEMP_FILE_ROOT, filename)) @@ -75,15 +83,22 @@ def process_results(result_text, is_example_code): def process_line(line_text, is_example_code): """ + Process style error by matching database entry and incrementing count. Note: Could at extracting parts of this function to a generic utility function. + + Args: + line_text (str): Text of style checker result. + is_example_code (bool): True if program was provided example code. + + Returns: + Dictionary of information about style error. """ issue_data = dict() re_result = re.search(LINE_RE, line_text) if re_result: line_number = re_result.group('line') - char_number = re_result.group('character') error_code = re_result.group('error_code') error_message = re_result.group('error_message') @@ -122,10 +137,19 @@ def process_line(line_text, is_example_code): def render_text(template, error_message): + """Render title text from error message contents. + + Args: + template (str): Template for formatting text. + error_message (str): Original style error message. + + Returns: + Rendered title text. + """ re_result = re.search(CHARACTER_RE, error_message) character = re_result.group('character') character_description = CHARACTER_DESCRIPTIONS[character] - template_data = { + template_data = { 'character': character, 'character_description': character_description, 'article': get_article(character_description), diff --git a/codewof/style/style_checkers/python3_data.py b/codewof/style/style_checkers/python3_data.py index d7bc51264..57509d192 100644 --- a/codewof/style/style_checkers/python3_data.py +++ b/codewof/style/style_checkers/python3_data.py @@ -6,56 +6,56 @@ "E101": { "original_message": "indentation contains mixed spaces and tabs", "title_templated": False, - "title": "This line is indented using a mixture of spaces and tabs.", + "title": "Line is indented using a mixture of spaces and tabs.", "solution": "You should indent your code using only spaces.", "explanation": "Python expects the indentation method to be consistent line to line. Spaces are the preferred indentation method." }, "E111": { "original_message": "indentation is not a multiple of four", "title_templated": False, - "title": "This line has an indentation level that is not a multiple of four.", + "title": "Line has an indentation level that is not a multiple of four.", "solution": "Ensure that the first indentation level is 4 spaces, the second indentation level is 8 spaces and so on.", "explanation": "" }, "E112": { "original_message": "expected an indented block", "title_templated": False, - "title": "This line is not indented at the correct level.", + "title": "Line is not indented at the correct level.", "solution": "Add indentation to this line until it is indented at the correct level.", "explanation": "" }, "E113": { "original_message": "unexpected indentation", "title_templated": False, - "title": "This line is indented when it shouldn't be.", + "title": "Line is indented when it shouldn't be.", "solution": "Remove indentation from this line until it is indented at the correct level.", "explanation": "" }, "E114": { "original_message": "indentation is not a multiple of four (comment)", "title_templated": False, - "title": "This line has an indentation level that is not a multiple of four.", + "title": "Line has an indentation level that is not a multiple of four.", "solution": "Ensure that the first indentation level is 4 spaces, the second indentation level is 8 spaces and so on.", "explanation": "" }, "E115": { "original_message": "expected an indented block (comment)", "title_templated": False, - "title": "This line is not indented at the correct level.", + "title": "Line is not indented at the correct level.", "solution": "Add indentation to this line until it is indented at the correct level.", "explanation": "Comments should be indented relative to the code block they are in." }, "E116": { "original_message": "unexpected indentation (comment)", "title_templated": False, - "title": "This line is indented when it shouldn't be.", + "title": "Line is indented when it shouldn't be.", "solution": "Remove indentation from this line until it is indented at the correct level.", "explanation": "" }, "E117": { "original_message": "over-indented", "title_templated": False, - "title": "This line has too many indentation levels.", + "title": "Line has too many indentation levels.", "solution": "Remove indentation from this line until it is indented at the correct level.", "explanation": "" }, @@ -63,14 +63,14 @@ "E121": { "original_message": "continuation line under-indented for hanging indent", "title_templated": False, - "title": "This line is less indented than it should be.", + "title": "Line is less indented than it should be.", "solution": "Add indentation to this line until it is indented at the correct level.", "explanation": "" }, "E122": { "original_message": "continuation line missing indentation or outdented", "title_templated": False, - "title": "This line is not indented as far as it should be or is indented too far.", + "title": "Line is not indented as far as it should be or is indented too far.", "solution": "Add or remove indentation levels until it is indented at the correct level.", "explanation": "" }, @@ -78,21 +78,21 @@ "E123": { "original_message": "closing bracket does not match indentation of opening bracket’s line", "title_templated": False, - "title": "This line has a closing bracket that does not match the indentation level of the line that the opening bracket started on.", + "title": "Line has a closing bracket that does not match the indentation level of the line that the opening bracket started on.", "solution": "Add or remove indentation of the closing bracket so it matches the indentation of the line that the opening bracket is on.", "explanation": "" }, "E124": { "original_message": "closing bracket does not match visual indentation", "title_templated": False, - "title": "This line has a closing bracket that does not match the indentation of the opening bracket.", + "title": "Line has a closing bracket that does not match the indentation of the opening bracket.", "solution": "Add or remove indentation of the closing bracket so it matches the indentation of the opening bracket.", "explanation": "" }, "E125": { "original_message": "continuation line with same indent as next logical line", "title_templated": False, - "title": "This line has a continuation that should be indented one extra level so that it can be distinguished from the next logical line.", + "title": "Line has a continuation that should be indented one extra level so that it can be distinguished from the next logical line.", "solution": "Add an indentation level to the line continuation so that it is indented one more level than the next logical line.", "explanation": "Continuation lines should not be indented at the same level as the next logical line. Instead, they should be indented to one more level so as to distinguish them from the next line." }, @@ -100,35 +100,35 @@ "E126": { "original_message": "continuation line over-indented for hanging indent", "title_templated": False, - "title": "This line is indented more than it should be.", + "title": "Line is indented more than it should be.", "solution": "Remove indentation from this line until it is indented at the correct level.", "explanation": "" }, "E127": { "original_message": "continuation line over-indented for visual indent", "title_templated": False, - "title": "This line is indented more than it should be.", + "title": "Line is indented more than it should be.", "solution": "Remove indentation from this line until it is indented at the correct level.", "explanation": "" }, "E128": { "original_message": "continuation line under-indented for visual indent", "title_templated": False, - "title": "This line is indented less than it should be.", + "title": "Line is indented less than it should be.", "solution": "Add indentation to this line until it is indented at the correct level.", "explanation": "" }, "E129": { "original_message": "visually indented line with same indent as next logical line", "title_templated": False, - "title": "This line has the same indentation as the next logical line.", + "title": "Line has the same indentation as the next logical line.", "solution": "Add an indentation level to the visually indented line so that it is indented one more level than the next logical line.", "explanation": "A visually indented line that has the same indentation as the next logical line is hard to read." }, "E131": { "original_message": "continuation line unaligned for hanging indent", "title_templated": False, - "title": "This line is not aligned correctly for a hanging indent.", + "title": "Line is not aligned correctly for a hanging indent.", "solution": "Add or remove indentation so that the lines are aligned with each other.", "explanation": "" }, @@ -143,223 +143,245 @@ "E201": { "original_message": "whitespace after '{character}'", "title_templated": True, - "title": "This line contains {article} {character_description} that has a space after it.", + "title": "Line contains {article} {character_description} that has a space after it.", "solution": "Remove any spaces that appear after the `{character}` character.", "explanation": "" }, "E202": { "original_message": "whitespace before '{character}'", "title_templated": True, - "title": "This line contains {article} {character_description} that has a space before it.", + "title": "Line contains {article} {character_description} that has a space before it.", "solution": "Remove any spaces that appear before the `{character}` character.", "explanation": "" }, "E203": { "original_message": "whitespace before '{character}'", "title_templated": True, - "title": "This line contains {article} {character_description} that has a space before it.", + "title": "Line contains {article} {character_description} that has a space before it.", "solution": "Remove any spaces that appear before the `{character}` character.", "explanation": "" }, "E211": { "original_message": "whitespace before '{character}'", "title_templated": True, - "title": "This line contains {article} {character_description} that has a space before it.", + "title": "Line contains {article} {character_description} that has a space before it.", "solution": "Remove any spaces that appear before the `{character}` character.", "explanation": "" }, "E221": { - "title_templated": False, "original_message": "multiple spaces before operator", - "title": "This line has multiple spaces before an operator.", + "title_templated": False, + "title": "Line has multiple spaces before an operator.", "solution": "Remove any extra spaces that appear before the operator on this line.", "explanation": "" }, "E222": { - "title_templated": False, "original_message": "multiple spaces after operator", - "title": "This line has multiple spaces after an operator.", + "title_templated": False, + "title": "Line has multiple spaces after an operator.", "solution": "Remove any extra spaces that appear after the operator on this line.", "explanation": "" }, "E223": { - "title_templated": False, "original_message": "tab before operator", - "title": "This line contains a tab character before an operator.", + "title_templated": False, + "title": "Line contains a tab character before an operator.", "solution": "Remove any tab characters that appear before the operator on this line. Operators should only have one space before them.", "explanation": "" }, "E224": { - "title_templated": False, "original_message": "tab after operator", - "title": "This line contains a tab character after an operator.", + "title_templated": False, + "title": "Line contains a tab character after an operator.", "solution": "Remove any tab characters that appear after the operator on this line. Operators should only have one space after them.", "explanation": "" }, "E225": { - "title_templated": False, "original_message": "missing whitespace around operator", - "title": "This line is missing whitespace around an operator.", + "title_templated": False, + "title": "Line is missing whitespace around an operator.", "solution": "Ensure there is one space before and after all operators.", "explanation": "" }, # E226 ignored by default "E226": { - "title_templated": False, "original_message": "missing whitespace around arithmetic operator", - "title": "This line is missing whitespace around an arithmetic operator (+, -, / and *).", - "solution": "Ensure there is one space before and after all arithmetic operators (+, -, / and *).", + "title_templated": False, + "title": "Line is missing whitespace around an arithmetic operator (`+`, `-`, `/` and `*`).", + "solution": "Ensure there is one space before and after all arithmetic operators (`+`, `-`, `/` and `*`).", "explanation": "" }, "E227": { - "title_templated": False, "original_message": "missing whitespace around bitwise or shift operator", - "title": "This line is missing whitespace around a bitwise or shift operator (<<, >>, &, |, ^).", - "solution": "Ensure there is one space before and after all bitwise and shift operators (<<, >>, &, |, ^).", + "title_templated": False, + "title": "Line is missing whitespace around a bitwise or shift operator (`<<`, `>>`, `&`, `|`, `^`).", + "solution": "Ensure there is one space before and after all bitwise and shift operators (`<<`, `>>`, `&`, `|`, `^`).", "explanation": "" }, "E228": { - "title_templated": False, "original_message": "missing whitespace around modulo operator", - "title": "This line is missing whitespace around a modulo operator (`%`).", + "title_templated": False, + "title": "Line is missing whitespace around a modulo operator (`%`).", "solution": "Ensure there is one space before and after the modulo operator (`%`).", "explanation": "" }, - # TODO: Check for specific character "E231": { "original_message": "missing whitespace after ‘,’, ‘;’, or ‘:’", - "title": "This line is missing whitespace around one of the following characters: , ; and :.", - "solution": "Ensure there is one space before and after any of the following characters: , ; and :.", + "title_templated": False, + "title": "Line is missing whitespace around one of the following characters: `,` `;` and `:`.", + "solution": "Ensure there is one space before and after any of the following characters: `,` `;` and `:`.", "explanation": "" }, # E241 ignored by default "E241": { "original_message": "multiple spaces after ‘,’", - "title": "This line has multiple spaces after the ',' character.", - "solution": "Ensure there is one space before and after any ',' characters.", + "title_templated": False, + "title": "Line has multiple spaces after the `,` character.", + "solution": "Ensure there is one space before and after any `,` characters.", "explanation": "" }, # E242 ignored by default "E242": { "original_message": "tab after ‘,’", - "title": "This line contains a tab character after the ',' character.", - "solution": "Remove any tab characters and ensure there is one space before and after any ',' characters.", + "title_templated": False, + "title": "Line contains a tab character after the `,` character.", + "solution": "Remove any tab characters and ensure there is one space before and after any `,` characters.", "explanation": "" }, "E251": { - "original_message": "unexpected spaces around keyword / parameter equals", - "title": "This line contains spaces before or after the = in a function definition.", - "solution": "Remove any spaces that appear either before or after the = character in your function definition.", - "explanation": "" + "original_message": "unexpected spaces around keyword / parameter equals", + "title_templated": False, + "title": "Line contains spaces before or after the `=` in a function definition.", + "solution": "Remove any spaces that appear either before or after the `=` character in your function definition.", + "explanation": "" }, "E261": { - "original_message": "at least two spaces before inline comment", - "title": "This line contains an inline comment that does not have 2 spaces before it.", - "solution": "Ensure that your inline comment has 2 spaces before the '#' character.", - "explanation": "" + "original_message": "at least two spaces before inline comment", + "title_templated": False, + "title": "Line contains an inline comment that does not have 2 spaces before it.", + "solution": "Ensure that your inline comment has 2 spaces before the `#` character.", + "explanation": "" }, "E262": { - "original_message": "inline comment should start with ‘# ‘", - "title": "Comments should start with a '#' character and have one space between the '#' character and the comment itself.", - "solution": "Ensure that your inline comment starts with a '#' character followed by a space and then the comment itself.", - "explanation": "https://lintlyci.github.io/Flake8Rules/rules/E262.html this rule seems to be more about the space after the pound sign though????" + "original_message": "inline comment should start with ‘# ‘", + "title_templated": False, + "title": "Comments have a space between the `#` character and the comment message.", + "solution": "Ensure that your inline comment has a space between the `#` character and the comment message.", + "explanation": "" }, "E265": { - "original_message": "block comment should start with ‘# ‘", - "title": "Comments should start with a '#' character and have one space between the '#' character and the comment itself.", - "solution": "Ensure that your block comment starts with a '#' character followed by a space and then the comment itself.", - "explanation": "" + "original_message": "block comment should start with ‘# ‘", + "title_templated": False, + "title": "Comments should start with a `#` character and have one space between the `#` character and the comment itself.", + "solution": "Ensure that your block comment has a space between the `#` character and the comment message.", + "explanation": "" }, "E266": { - "original_message": "too many leading ‘#’ for block comment", - "title": "Comments should only start with a single '#' character.", - "solution": "Ensure your comment only starts with one '#' character.", - "explanation": "" + "original_message": "too many leading ‘#’ for block comment", + "title_templated": False, + "title": "Comments should only start with a single `#` character.", + "solution": "Ensure your comment only starts with one `#` character.", + "explanation": "" }, "E271": { - "original_message": "multiple spaces after keyword", - "title": "This line contains more than one space after a keyword.", - "solution": "Ensure there is only one space after any keywords.", - "explanation": "" + "original_message": "multiple spaces after keyword", + "title_templated": False, + "title": "Line contains more than one space after a keyword.", + "solution": "Ensure there is only one space after any keywords.", + "explanation": "" }, "E272": { - "original_message": "multiple spaces before keyword", - "title": "This line contains more than one space before a keyword.", - "solution": "Ensure there is only one space before any keywords.", - "explanation": "" + "original_message": "multiple spaces before keyword", + "title_templated": False, + "title": "Line contains more than one space before a keyword.", + "solution": "Ensure there is only one space before any keywords.", + "explanation": "" }, "E273": { - "original_message": "tab after keyword", - "title": "This line contains a tab character after a keyword.", - "solution": "Ensure there is only one space after any keywords.", - "explanation": "" + "original_message": "tab after keyword", + "title_templated": False, + "title": "Line contains a tab character after a keyword.", + "solution": "Ensure there is only one space after any keywords.", + "explanation": "" }, "E274": { - "original_message": "tab before keyword", - "title": "This line contains a tab character before a keyword.", - "solution": "Ensure there is only one space before any keywords.", - "explanation": "" + "original_message": "tab before keyword", + "title_templated": False, + "title": "Line contains a tab character before a keyword.", + "solution": "Ensure there is only one space before any keywords.", + "explanation": "" }, "E275": { - "original_message": "missing whitespace after keyword", - "title": "This line is missing a space after a keyword.", - "solution": "Ensure there is one space after any keywords.", - "explanation": "" + "original_message": "missing whitespace after keyword", + "title_templated": False, + "title": "Line is missing a space after a keyword.", + "solution": "Ensure there is one space after any keywords.", + "explanation": "" }, "E301": { - "original_message": "expected 1 blank line, found 0", - "title": "This line is missing a blank line between the methods of a class.", - "solution": "Add a blank line in between your class methods.", - "explanation": "" + "original_message": "expected 1 blank line, found 0", + "title_templated": False, + "title": "Line is missing a blank line between the methods of a class.", + "solution": "Add a blank line in between your class methods.", + "explanation": "" }, "E302": { - "original_message": "expected 2 blank lines, found 0", - "title": "Two blank lines are expected between functions and classes.", - "solution": "Ensure there are two blank lines between functions and classes.", - "explanation": "" + "original_message": "expected 2 blank lines, found 0", + "title_templated": False, + "title": "Two blank lines are expected between functions and classes.", + "solution": "Ensure there are two blank lines between functions and classes.", + "explanation": "" }, "E303": { - "original_message": "too many blank lines (3)", - "title": "There are too many blank lines.", - "solution": "Ensure there are two blank lines between functions and classes and one blank line between methods of a class.", - "explanation": "" + "original_message": "too many blank lines (3)", + "title_templated": False, + "title": "Too many blank lines.", + "solution": "Ensure there are only two blank lines between functions and classes and one blank line between methods of a class.", + "explanation": "" }, "E304": { - "original_message": "blank lines found after function decorator", - "title": "This line contains a blank line after a function decorator.", - "solution": "Ensure that there are no blank lines between a function decorator and the function it is decorating.", - "explanation": "" + "original_message": "blank lines found after function decorator", + "title_templated": False, + "title": "Line contains a blank line after a function decorator.", + "solution": "Ensure that there are no blank lines between a function decorator and the function it is decorating.", + "explanation": "" }, "E305": { - "original_message": "expected 2 blank lines after end of function or class", - "title": "Functions and classes should have two blank lines after them.", - "solution": "Ensure that functions and classes should have two blank lines after them.", - "explanation": "" + "original_message": "expected 2 blank lines after end of function or class", + "title_templated": False, + "title": "Functions and classes should have two blank lines after them.", + "solution": "Ensure that functions and classes should have two blank lines after them.", + "explanation": "" }, "E306": { - "original_message": "expected 1 blank line before a nested definition", - "title": "Nested definitions should have one blank line before them.", - "solution": "Ensure there is a blank line above your nested definition.", - "explanation": "" + "original_message": "expected 1 blank line before a nested definition", + "title_templated": False, + "title": "Nested definitions should have one blank line before them.", + "solution": "Ensure there is a blank line above your nested definition.", + "explanation": "" }, "E401": { - "original_message": "multiple imports on one line", - "title": "This line contains imports from different modules on the same line.", - "solution": "Ensure import statements from different modules occur on their own line.", - "explanation": "" + "original_message": "multiple imports on one line", + "title_templated": False, + "title": "Line contains imports from different modules on the same line.", + "solution": "Ensure import statements from different modules occur on their own line.", + "explanation": "" }, "E402": { - "original_message": "module level import not at top of file", - "title": "Module imports should be at the top of the file and there should be no statements in between module level imports.", - "solution": "Ensure all import statements are at the top of the file and there are no statements in between imports.", - "explanation": "" + "original_message": "module level import not at top of file", + "title_templated": False, + "title": "Module imports should be at the top of the file and there should be no statements in between module level imports.", + "solution": "Ensure all import statements are at the top of the file and there are no statements in between imports.", + "explanation": "" }, "E501": { - "original_message": "line too long (82 > 79 characters)", - "title": "This line is longer than 79 characters.", - "solution": "You can insert a backslash character (\\) to wrap the text onto the next line.", - "explanation": "Limiting the line width makes it possible to have several files open side-by-side, and works well when using code review tools that present the two versions in adjacent columns." + "original_message": "line too long (82 > 79 characters)", + "title_templated": False, + "title": "Line is longer than 79 characters.", + "solution": "You should rewrite your long line of code by breaking it down across multiple lines.", + "explanation": "By making sure your lines of code are not too complicated means it's easier to understand by other people. Also by limiting the line width makes it possible to have several files open side-by-side, and works well when using code review tools that present the two versions in adjacent columns." }, + # TODO: Continue from this point onwards with checking text and adding templating boolean "E502": { "original_message": "the backslash is redundant between brackets", "title": "There is no need for a backslash (\\) between brackets.", @@ -368,26 +390,26 @@ }, "E701": { "original_message": "multiple statements on one line (colon)", - "title": "This line contains multiple statements.", + "title": "Line contains multiple statements.", "solution": "Make sure that each statement is on its own line.", "explanation": "This improves readability." }, "E702": { "original_message": "multiple statements on one line (semicolon)", - "title": "This line contains multiple statements.", + "title": "Line contains multiple statements.", "solution": "Make sure that each statement is on its own line.", "explanation": "This improves readability." }, "E703": { "original_message": "statement ends with a semicolon", - "title": "This line ends in a semicolon (;).", + "title": "Line ends in a semicolon (;).", "solution": "Remove the semicolon from the end of the line.", "explanation": "" }, # E704 ignored by default "E704": { "original_message": "multiple statements on one line (def)", - "title": "This line contains multiple statements.", + "title": "Line contains multiple statements.", "solution": "Make sure multiple statements of a function definition are on their own separate lines.", "explanation": "" }, @@ -429,37 +451,37 @@ }, "E731": { "original_message": "do not assign a lambda expression, use a def", - "title": "This line assigns a lambda expression instead of defining it as a function using `def`.", + "title": "Line assigns a lambda expression instead of defining it as a function using `def`.", "solution": "", "explanation": "The primary reason for this is debugging. Lambdas show as `` in tracebacks, where functions will display the function’s name." }, "E741": { "original_message": "do not use variables named ‘l’, ‘O’, or ‘I’", - "title": "This line uses one of the variables named ‘l’, ‘O’, or ‘I’", + "title": "Line uses one of the variables named ‘l’, ‘O’, or ‘I’", "solution": "Change the names of these variables to something more descriptive.", "explanation": "Variables named I, O, and l can be very hard to read. This is because the letter I and the letter l are easily confused, and the letter O and the number 0 can be easily confused." }, "E742": { "original_message": "do not define classes named ‘l’, ‘O’, or ‘I’", - "title": "This line contains a class named ‘l’, ‘O’, or ‘I’", + "title": "Line contains a class named ‘l’, ‘O’, or ‘I’", "solution": "Change the names of these classes to something more descriptive.", "explanation": "Classes named I, O, and l can be very hard to read. This is because the letter I and the letter l are easily confused, and the letter O and the number 0 can be easily confused." }, "E743": { "original_message": "do not define functions named ‘l’, ‘O’, or ‘I’", - "title": "This line contains a function named ‘l’, ‘O’, or ‘I’", + "title": "Line contains a function named ‘l’, ‘O’, or ‘I’", "solution": "Change the names of these functions to something more descriptive.", "explanation": "Functions named I, O, and l can be very hard to read. This is because the letter I and the letter l are easily confused, and the letter O and the number 0 can be easily confused." }, "W191": { "original_message": "indentation contains tabs", - "title": "This line contains tabs when only spaces are expected.", + "title": "Line contains tabs when only spaces are expected.", "solution": "Replace any tabs in your indentation with spaces.", "explanation": "" }, "W291": { "original_message": "trailing whitespace", - "title": "This line contains whitespace after the final character.", + "title": "Line contains whitespace after the final character.", "solution": "Remove any extra whitespace at the end of each line.", "explanation": "" }, @@ -500,7 +522,7 @@ # W505 ignored by default "W505": { "original_message": "doc line too long (82 > 79 characters)", - "title": "This line is longer than 79 characters.", + "title": "Line is longer than 79 characters.", "solution": "You can insert a backslash character (\\) to wrap the text onto the next line.", "explanation": "" }, diff --git a/codewof/style/utils.py b/codewof/style/utils.py index f9b319ce3..a1de9c12d 100644 --- a/codewof/style/utils.py +++ b/codewof/style/utils.py @@ -1,9 +1,7 @@ """Utilities for the style checker application.""" from django.conf import settings -from django.db.models import F from django.template.loader import render_to_string -from style.models import Error CHARACTER_DESCRIPTIONS = { diff --git a/codewof/style/views.py b/codewof/style/views.py index be6e84962..b12a1626d 100644 --- a/codewof/style/views.py +++ b/codewof/style/views.py @@ -2,10 +2,7 @@ import json from django.conf import settings -from django.urls import reverse_lazy -from django.http import Http404, JsonResponse, HttpResponse -from django.template.loader import get_template -from django.template import TemplateDoesNotExist +from django.http import JsonResponse from django.views.generic import ( TemplateView, ListView, diff --git a/codewof/users/views.py b/codewof/users/views.py index 76afa17c6..543991659 100644 --- a/codewof/users/views.py +++ b/codewof/users/views.py @@ -87,9 +87,9 @@ def get_context_data(self, **kwargs): # TODO: Also filter by questions added before today questions = questions.filter( - Q(attempt__isnull=True) | - (Q(attempt__passed_tests=False) & Q(attempt__datetime__date__lte=today)) | - (Q(attempt__passed_tests=True) & Q(attempt__datetime__date=today)) + Q(attempt__isnull=True) + | (Q(attempt__passed_tests=False) & Q(attempt__datetime__date__lte=today)) + | (Q(attempt__passed_tests=True) & Q(attempt__datetime__date=today)) ).order_by('pk').distinct('pk').select_subclasses() questions = list(questions) diff --git a/setup.cfg b/setup.cfg index 1b7fa0765..67655d77c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -11,14 +11,20 @@ exclude = */temp, */manage.py, codewof/programming/content/en/*/initial.py, + */tests/*, show-source = True statistics = True count = True +ignore = Q000, Q001, Q002, W503 +per-file-ignores = + style/style_checkers/python3_data.py:E501 + programming/content/*/*.py:D100,D103 [pydocstyle] # Ignore following rules to allow Google Python Style docstrings add_ignore = D407,D413 -match_dir = (?!migrations|node_modules|files|build|staticfiles|content).* +match = (?!test_).*\.py +match_dir = (?!migrations|node_modules|files|build|staticfiles|content|temp).* [mypy] python_version = 3.6 From ecd8f48659c6cdd908740ee3763fb29044a205ec Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Thu, 11 Jun 2020 10:57:01 +1200 Subject: [PATCH 34/48] Style fixes --- .../general/management/commands/sampledata.py | 3 + codewof/style/style_checkers/python3_data.py | 930 +++++++++++------- dev | 4 + infrastructure/prod-deploy/update-content.sh | 1 + setup.cfg | 1 - 5 files changed, 563 insertions(+), 376 deletions(-) diff --git a/codewof/general/management/commands/sampledata.py b/codewof/general/management/commands/sampledata.py index b589bfbdf..b022715c5 100644 --- a/codewof/general/management/commands/sampledata.py +++ b/codewof/general/management/commands/sampledata.py @@ -88,6 +88,9 @@ def handle(self, *args, **options): management.call_command('load_achievements') print('Achievements loaded.\n') + management.call_command('load_style_errors') + print('Style errors loaded.\n') + # Research StudyFactory.create_batch(size=5) StudyGroupFactory.create_batch(size=15) diff --git a/codewof/style/style_checkers/python3_data.py b/codewof/style/style_checkers/python3_data.py index 57509d192..416b2a681 100644 --- a/codewof/style/style_checkers/python3_data.py +++ b/codewof/style/style_checkers/python3_data.py @@ -1,6 +1,9 @@ -# The following fields are rendered as HTML. -# - solution -# - explanation +"""Data for Python 3 style errors. + +The following fields are rendered as HTML. + - solution + - explanation +""" DATA = { "E101": { @@ -381,211 +384,282 @@ "solution": "You should rewrite your long line of code by breaking it down across multiple lines.", "explanation": "By making sure your lines of code are not too complicated means it's easier to understand by other people. Also by limiting the line width makes it possible to have several files open side-by-side, and works well when using code review tools that present the two versions in adjacent columns." }, - # TODO: Continue from this point onwards with checking text and adding templating boolean "E502": { - "original_message": "the backslash is redundant between brackets", - "title": "There is no need for a backslash (\\) between brackets.", - "solution": "Remove any backslashes between brackets.", - "explanation": "" + "original_message": "the backslash is redundant between brackets", + "title_templated": False, + "title": "There is no need for a backslash (`\\`) between brackets.", + "solution": "Remove any backslashes between brackets.", + "explanation": "" }, "E701": { - "original_message": "multiple statements on one line (colon)", - "title": "Line contains multiple statements.", - "solution": "Make sure that each statement is on its own line.", - "explanation": "This improves readability." + "original_message": "multiple statements on one line (colon)", + "title_templated": False, + "title": "Line contains multiple statements.", + "solution": "Make sure that each statement is on its own line.", + "explanation": "This improves readability." }, "E702": { - "original_message": "multiple statements on one line (semicolon)", - "title": "Line contains multiple statements.", - "solution": "Make sure that each statement is on its own line.", - "explanation": "This improves readability." + "original_message": "multiple statements on one line (semicolon)", + "title_templated": False, + "title": "Line contains multiple statements.", + "solution": "Make sure that each statement is on its own line.", + "explanation": "This improves readability of your code." }, "E703": { - "original_message": "statement ends with a semicolon", - "title": "Line ends in a semicolon (;).", - "solution": "Remove the semicolon from the end of the line.", - "explanation": "" + "original_message": "statement ends with a semicolon", + "title_templated": False, + "title": "Line ends in a semicolon (`;`).", + "solution": "Remove the semicolon from the end of the line, these are not used in Python 3.", + "explanation": "" }, - # E704 ignored by default + # E704 ignored by default "E704": { - "original_message": "multiple statements on one line (def)", - "title": "Line contains multiple statements.", - "solution": "Make sure multiple statements of a function definition are on their own separate lines.", - "explanation": "" + "original_message": "multiple statements on one line (def)", + "title_templated": False, + "title": "Line contains multiple statements.", + "solution": "Make sure multiple statements of a function definition are on their own separate lines.", + "explanation": "" }, "E711": { - "original_message": "comparison to None should be ‘if cond is None:’", - "title": "Comparisons to objects such as True, False and None should use 'is' or 'is not' instead of '==' and '!='.", - "solution": "Replace != with 'is not' and '==' with 'is'.", - "explanation": "" + "original_message": "comparison to None should be ‘if cond is None:’", + "title_templated": False, + "title": "Comparisons to objects such as `True`, `False`, and `None` should use `is` or `is not` instead of `==` and `!=`.", + "solution": "Replace `!=` with `is not` and `==` with `is`.", + "explanation": "This makes your code easier to read, as it's using words rather than symbols." }, "E712": { - "original_message": "comparison to True should be ‘if cond is True:’ or ‘if cond:’", - "title": "Comparisons to objects such as True, False and None should use 'is' or 'is not' instead of '==' and '!='.", - "solution": "Replace != with 'is not' and '==' with 'is'.", - "explanation": "" + "original_message": "comparison to True should be ‘if cond is True:’ or ‘if cond:’", + "title_templated": False, + "title": "Comparisons to objects such as `True`, `False`, and `None` should use `is` or `is not` instead of `==` and `!=`.", + "solution": "Replace `!=` with `is not` and `==` with `is`.", + "explanation": "This makes your code easier to read, as it's using words rather than symbols." }, "E713": { - "original_message": "test for membership should be ‘not in’", - "title": "When testing whether or not something is in an object use the form `x not in the_object` instead of `not x in the_object`.", - "solution": "Use the form `not x in the_object` instead of `x not in the_object`.", - "explanation": "This improves readability." + "original_message": "test for membership should be ‘not in’", + "title_templated": False, + "title": "When testing whether or not something is in an object use the form `x not in the_object` instead of `not x in the_object`.", + "solution": "Use the form `not x in the_object` instead of `x not in the_object`.", + "explanation": "This improves readability of your code as it reads more naturally." }, "E714": { - "original_message": "test for object identity should be ‘is not’", - "title": "When testing for object identity use the form `x is not None` rather than `not x is None`.", - "solution": "Use the form `x is not None` rather than `not x is None`.", - "explanation": "This improves readability." + "original_message": "test for object identity should be ‘is not’", + "title_templated": False, + "title": "When testing for object identity use the form `x is not None` rather than `not x is None`.", + "solution": "Use the form `x is not None` rather than `not x is None`.", + "explanation": "This improves readability of your code as it reads more naturally." }, "E721": { - "original_message": "do not compare types, use ‘isinstance()’", - "title": "You should compare an objects type by using `isinstance()` instead of `==`. This is because `isinstance` can handle subclasses as well.", - "solution": "Use `if isinstance(user, User)` instead of `if type(user) == User`.", - "explanation": "" + "original_message": "do not compare types, use ‘isinstance()’", + "title_templated": False, + "title": "You should compare an objects type by using `isinstance()` instead of `==`. This is because `isinstance` can handle subclasses as well.", + "solution": "Use `if isinstance(dog, Animal)` instead of `if type(dog) == Animal`.", + "explanation": "" }, "E722": { - "original_message": "do not use bare except, specify exception instead", - "title": "", - "solution": "", - "explanation": "" + "original_message": "do not use bare except, specify exception instead", + "title_templated": False, + "title": "Except block is calling all exceptions, instead it should catch a specific exception.", + # TODO: Add a simple multi-except example + "solution": "Add the specific exception the block is expected to catch, you may need to use multiple `except` blocks if you were catching multiple exceptions.", + "explanation": "This helps other to know exactly what the `except` is expected to catch." }, "E731": { - "original_message": "do not assign a lambda expression, use a def", - "title": "Line assigns a lambda expression instead of defining it as a function using `def`.", - "solution": "", - "explanation": "The primary reason for this is debugging. Lambdas show as `` in tracebacks, where functions will display the function’s name." + "original_message": "do not assign a lambda expression, use a def", + "title_templated": False, + "title": "Line assigns a lambda expression instead of defining it as a function using `def`.", + "solution": "Define the line as a function using `def`.", + "explanation": "The primary reason for this is debugging. Lambdas show as `` in tracebacks, where functions will display the function’s name." }, "E741": { - "original_message": "do not use variables named ‘l’, ‘O’, or ‘I’", - "title": "Line uses one of the variables named ‘l’, ‘O’, or ‘I’", - "solution": "Change the names of these variables to something more descriptive.", - "explanation": "Variables named I, O, and l can be very hard to read. This is because the letter I and the letter l are easily confused, and the letter O and the number 0 can be easily confused." + "original_message": "do not use variables named `l`, `O`, or `I`", + "title_templated": False, + "title": "Line uses one of the variables named `l`, `O`, or `I`", + "solution": "Change the names of these variables to something more descriptive.", + "explanation": "Variables named `l`, `O`, or `I` can be very hard to read. This is because the letter `I` and the letter `l` are easily confused, and the letter `O` and the number `0` can be easily confused." }, "E742": { - "original_message": "do not define classes named ‘l’, ‘O’, or ‘I’", - "title": "Line contains a class named ‘l’, ‘O’, or ‘I’", - "solution": "Change the names of these classes to something more descriptive.", - "explanation": "Classes named I, O, and l can be very hard to read. This is because the letter I and the letter l are easily confused, and the letter O and the number 0 can be easily confused." + "original_message": "do not define classes named `l`, `O`, or `I`", + "title_templated": False, + "title": "Line contains a class named `l`, `O`, or `I`", + "solution": "Change the names of these classes to something more descriptive.", + "explanation": "Classes named `l`, `O`, or `I` can be very hard to read. This is because the letter `I` and the letter `l` are easily confused, and the letter `O` and the number `0` can be easily confused." }, "E743": { - "original_message": "do not define functions named ‘l’, ‘O’, or ‘I’", - "title": "Line contains a function named ‘l’, ‘O’, or ‘I’", - "solution": "Change the names of these functions to something more descriptive.", - "explanation": "Functions named I, O, and l can be very hard to read. This is because the letter I and the letter l are easily confused, and the letter O and the number 0 can be easily confused." + "original_message": "do not define functions named `l`, `O`, or `I`", + "title_templated": False, + "title": "Line contains a function named `l`, `O`, or `I`", + "solution": "Change the names of these functions to something more descriptive.", + "explanation": "Functions named `l`, `O`, or `I` can be very hard to read. This is because the letter `I` and the letter `l` are easily confused, and the letter `O` and the number `0` can be easily confused." }, + # TODO: Continue from this point onwards with checking text and adding templating boolean "W191": { - "original_message": "indentation contains tabs", - "title": "Line contains tabs when only spaces are expected.", - "solution": "Replace any tabs in your indentation with spaces.", - "explanation": "" + "original_message": "indentation contains tabs", + "title_templated": False, + "title": "Line contains tabs when only spaces are expected.", + "solution": "Replace any tabs in your indentation with spaces.", + "explanation": "Using a consistent character for whitespace makes it much easier for editors to read your file." }, "W291": { - "original_message": "trailing whitespace", - "title": "Line contains whitespace after the final character.", - "solution": "Remove any extra whitespace at the end of each line.", - "explanation": "" + "original_message": "trailing whitespace", + "title_templated": False, + "title": "Line contains whitespace after the final character.", + "solution": "Remove any extra whitespace at the end of each line.", + "explanation": "" }, "W292": { - "original_message": "no newline at end of file", - "title": "Files should end with a newline.", - "solution": "Add a newline to the end of your file.", - "explanation": "" + "original_message": "no newline at end of file", + "title_templated": False, + "title": "Files should end with a newline.", + "solution": "Add a newline to the end of your file.", + "explanation": "All text files should automatically end with a new line character, but some code editors can allow you to remove it." }, "W293": { - "original_message": "blank line contains whitespace", - "title": "Blank lines should not contain any tabs or spaces.", - "solution": "Remove any whitespace from blank lines.", - "explanation": "" + "original_message": "blank line contains whitespace", + "title_templated": False, + "title": "Blank lines should not contain any tabs or spaces.", + "solution": "Remove any whitespace from blank lines.", + "explanation": "" }, "W391": { - "original_message": "blank line at end of file", - "title": "There are either zero, two, or more than two blank lines at the end of your file.", - "solution": "Ensure there is only one blank line at the end of your file.", - "explanation": "" + "original_message": "blank line at end of file", + "title_templated": False, + "title": "There are either zero, two, or more than two blank lines at the end of your file.", + "solution": "Ensure there is only one blank line at the end of your file.", + "explanation": "" }, - # W503 ignored by default - # This seems contradicitng... https://lintlyci.github.io/Flake8Rules/rules/W503.html + # W503 ignored by default + # This seems contradicitng... https://lintlyci.github.io/Flake8Rules/rules/W503.html "W503": { - "original_message": "line break before binary operator", - "title": "", - "solution": "", - "explanation": "" + "original_message": "line break before binary operator", + "title_templated": False, + "title": "Line break is before a binary operator.", + "solution": "", + "explanation": "" }, - # W504 ignored by default - # same as above https://lintlyci.github.io/Flake8Rules/rules/W504.html + # W504 ignored by default + # same as above https://lintlyci.github.io/Flake8Rules/rules/W504.html "W504": { - "original_message": "line break after binary operator", - "title": "", - "solution": "", - "explanation": "" + "original_message": "line break after binary operator", + "title_templated": False, + "title": "Line break is after a binary operator.", + "solution": "", + "explanation": "" }, - # W505 ignored by default + # W505 ignored by default "W505": { - "original_message": "doc line too long (82 > 79 characters)", - "title": "Line is longer than 79 characters.", - "solution": "You can insert a backslash character (\\) to wrap the text onto the next line.", - "explanation": "" + "original_message": "doc line too long (82 > 79 characters)", + "title_templated": False, + "title": "Line is longer than 79 characters.", + "solution": "You should rewrite your long line of code by breaking it down across multiple lines.", + "explanation": "By making sure your lines of code are not too complicated means it's easier to understand by other people. Also by limiting the line width makes it possible to have several files open side-by-side, and works well when using code review tools that present the two versions in adjacent columns." }, "W601": { - "original_message": ".has_key() is deprecated, use ‘in’", - "title": ".has_key() was deprecated in Python 2. It is recommended to use the in operator instead.", - "solution": "", - "explanation": "" + "original_message": ".has_key() is deprecated, use ‘in’", + "title_templated": False, + "title": "`.has_key()` was deprecated in Python 2. It is recommended to use the `in` operator instead.", + "solution": """ +Use `in` instead of `.has_key()`. + +For example: + +``` +if 8054 in postcodes: +``` +""", + "explanation": "" }, "W602": { - "original_message": "deprecated form of raising exception", - "title": "The raise Exception, message form of raising exceptions is no longer supported. Use the new form.", - "solution": "Instead of the form raise ErrorType, 'Error message' use the form raise ErrorType('Error message')", - "explanation": "" + "original_message": "deprecated form of raising exception", + "title_templated": False, + "title": "Using `raise ExceptionType, message` is not supported.", + "solution": "Instead of using `raise ExceptionType, 'Error message'`, passing the text as a parameter to the exception, like `raise ExceptionType('Error message')`.", + "explanation": "" }, "W603": { - "original_message": "‘<>’ is deprecated, use ‘!=’", - "title": "<> has been removed in Python 3.", - "solution": "Replace any occurences of <> with !=.", - "explanation": "" + "original_message": "‘<>’ is deprecated, use ‘!=’", + "title_templated": False, + "title": "`<>` has been removed in Python 3.", + "solution": "Replace any occurences of `<>` with `!=`.", + "explanation": "The `!=` is the common programming symbols for stating not equal." }, "W604": { - "original_message": "backticks are deprecated, use ‘repr()’", - "title": "Backticks have been removed in Python 3.", - "solution": "Use the built-in function repr() instead.", - "explanation": "" + "original_message": "backticks are deprecated, use ‘repr()’", + "title_templated": False, + "title": "Backticks have been removed in Python 3.", + "solution": "Use the built-in function `repr()` instead.", + "explanation": "" }, "W605": { - "original_message": "invalid escape sequence ‘x’", - "title": "", - "solution": "", - "explanation": "" + "original_message": "invalid escape sequence ‘x’", + "title_templated": False, + "title": "Backslash is used to escape a character that cannot be escaped.", + "solution": "Either don't use the backslash, or check your string is correct.", + "explanation": "" }, "W606": { - "original_message": "‘async’ and ‘await’ are reserved keywords starting with Python 3.7", - "title": "", - "solution": "", - "explanation": "" + "original_message": "`async` and `await` are reserved keywords starting with Python 3.7", + "title_templated": False, + "title": "`async` and `await` are reserved names", + "solution": "Do not name variables or functions as `async` or `await`.", + "explanation": "" }, "D100": { - "original_message": "Missing docstring in public module", - "title": "Modules should have docstrings.", - "solution": "Add a docstring to your module.", - "explanation": "A docstring is a comment at the top of your module that briefly explains the purpose of the module." + "original_message": "Missing docstring in public module", + "title_templated": False, + "title": "Module (the term for the Python file) should have a docstring.", + "solution": "Add a docstring to your module.", + "explanation": """ +A docstring is a special comment at the top of your module that briefly explains the purpose of the module. It should have 3 sets of quotes to start and finish the comment. + +For example: + +``` +\"\"\"This file calculates required dietary requirements for kiwis. +``` +""" }, "D101": { - "original_message": "Missing docstring in public class", - "title": "Classes should have docstrings.", - "solution": "Add a docstring to your class.", - "explanation": "" + "original_message": "Missing docstring in public class", + "title_templated": False, + "title": "Class should have a docstring.", + "solution": "Add a docstring to your class.", + "explanation": """ +A docstring is a special comment at the top of your class that briefly explains the purpose of the class. It should have 3 sets of quotes to start and finish the comment. + +For example: + +``` +class Kiwi(): + \"\"\"Represents a kiwi bird from New Zealand.\"\"\" +``` +""" }, "D102": { - "original_message": "Missing docstring in public method", - "title": "Methods should have docstrings.", - "solution": "Add a docstring to your method.", - "explanation": "" + "original_message": "Missing docstring in public method", + "title_templated": False, + "title": "Methods should have docstrings.", + "solution": "Add a docstring to your method.", + "explanation": """ +A docstring is a special comment at the top of your method that briefly explains the purpose of the method. It should have 3 sets of quotes to start and finish the comment. + +For example: + +``` +class Kiwi(): + \"\"\"Represents a kiwi bird from New Zealand.\"\"\" + + def eat_plants(amount): + \"\"\"Calculates calories from the given amount of plant food and eats it.\"\"\" +``` +""" }, "D103": { "original_message": "Missing docstring in public function", + "title_templated": False, "title": "This function should have a docstring.", "solution": "Add a docstring to your function.", "explanation": """ -A docstring is a comment at the top of your function that briefly explains the purpose of the module. +A docstring is a special comment at the top of your module that briefly explains the purpose of the function. It should have 3 sets of quotes to start and finish the comment. For example: @@ -596,357 +670,463 @@ def get_waypoint_latlng(number): """ }, "D104": { - "original_message": "Missing docstring in public package", - "title": "Packages should have docstrings.", - "solution": "Add a docstring to your package.", - "explanation": "" + "original_message": "Missing docstring in public package", + "title_templated": False, + "title": "Packages should have docstrings.", + "solution": "Add a docstring to your package.", + "explanation": "" }, "D105": { - "original_message": "Missing docstring in magic method", - "title": "Magic methods should have docstrings.", - "solution": "Add a docstring to your magic method.", - "explanation": "" + "original_message": "Missing docstring in magic method", + "title_templated": False, + "title": "Magic methods should have docstrings.", + "solution": "Add a docstring to your magic method.", + "explanation": "" }, "D106": { - "original_message": "Missing docstring in public nested class", - "title": "Public nested classes should have docstrings.", - "solution": "Add a docstring to your public nested class.", - "explanation": "" + "original_message": "Missing docstring in public nested class", + "title_templated": False, + "title": "Public nested classes should have docstrings.", + "solution": "Add a docstring to your public nested class.", + "explanation": "" }, "D107": { - "original_message": "Missing docstring in __init__", - "title": "The __init__ method should have a docstring.", - "solution": "Add a docstring to your __init__ method.", - "explanation": "" + "original_message": "Missing docstring in __init__", + "title_templated": False, + "title": "The `__init__` method should have a docstring.", + "solution": "Add a docstring to your `__init__` method.", + "explanation": "This helps understand how objects of your class are created." }, "D200": { - "original_message": "One-line docstring should fit on one line with quotes", - "title": "Docstrings that are one line long should fit on one line with quotes.", - "solution": "Put your docstring on one line with quotes.", - "explanation": "" + "original_message": "One-line docstring should fit on one line with quotes", + "title_templated": False, + "title": "Docstrings that are one line long should fit on one line with quotes.", + "solution": "Put your docstring on one line with quotes.", + "explanation": "" }, "D201": { - "original_message": "No blank lines allowed before function docstring", - "title": "Function docstrings should not have blank lines before them.", - "solution": "Remove any blank lines before your function docstring.", - "explanation": "" + "original_message": "No blank lines allowed before function docstring", + "title_templated": False, + "title": "Function docstrings should not have blank lines before them.", + "solution": "Remove any blank lines before your function docstring.", + "explanation": "" }, "D202": { - "original_message": "No blank lines allowed after function docstring", - "title": "Function docstrings should not have blank lines after them.", - "solution": "Remove any blank lines after your function docstring.", - "explanation": "" + "original_message": "No blank lines allowed after function docstring", + "title_templated": False, + "title": "Function docstrings should not have blank lines after them.", + "solution": "Remove any blank lines after your function docstring.", + "explanation": "" }, "D203": { - "original_message": "1 blank line required before class docstring", - "title": "Class docstrings should have 1 blank line before them.", - "solution": "Insert 1 blank line before your class docstring.", - "explanation": "" + "original_message": "1 blank line required before class docstring", + "title_templated": False, + "title": "Class docstrings should have 1 blank line before them.", + "solution": "Insert 1 blank line before your class docstring.", + "explanation": "" }, "D204": { - "original_message": "1 blank line required after class docstring", - "title": "Class docstrings should have 1 blank line after them.", - "solution": "Insert 1 blank line after your class docstring.", - "explanation": "" + "original_message": "1 blank line required after class docstring", + "title_templated": False, + "title": "Class docstrings should have 1 blank line after them.", + "solution": "Insert 1 blank line after your class docstring.", + "explanation": "This improves the readability of your code." }, "D205": { - "original_message": "1 blank line required between summary line and description", - "title": "There should be 1 blank line between the summary line and the description.", - "solution": "Insert 1 blank line between the summary line and the description.", - "explanation": "" + "original_message": "1 blank line required between summary line and description", + "title_templated": False, + "title": "There should be 1 blank line between the summary line and the description.", + "solution": "Insert 1 blank line between the summary line and the description.", + "explanation": """ +This makes larger docstrings easier to read. + +For example: + +``` +def get_stock_level(book): + \"\"\"Return the stock level for the given book. + + This calculates the stock level across multiple stores in the city, + excluding books reserved for customers. + + Args: + book (str): Name of the book. + + Returns: + String of publication date in the format of 'DD/MM/YYYY'. + \"\"\" +``` +""" }, "D206": { - "original_message": "Docstring should be indented with spaces, not tabs", - "title": "Docstrings should be indented using spaces, not tabs.", - "solution": "Make sure your docstrings are indented using spaces instead of tabs.", - "explanation": "" + "original_message": "Docstring should be indented with spaces, not tabs", + "title_templated": False, + "title": "Docstrings should be indented using spaces, not tabs.", + "solution": "Make sure your docstrings are indented using spaces instead of tabs.", + "explanation": "" }, "D207": { - "original_message": "Docstring is under-indented", - "title": "Docstring is under-indented.", - "solution": "Add indentation levels to your docstring until it is at the correct indentation level.", - "explanation": "" + "original_message": "Docstring is under-indented", + "title_templated": False, + "title": "Docstring is under-indented.", + "solution": "Add indentation levels to your docstring until it is at the correct indentation level.", + "explanation": "" }, "D208": { - "original_message": "Docstring is over-indented", - "title": "Docstring is over-indented.", - "solution": "Remove indentation levels from your docstring until it is at the correct indentation level.", - "explanation": "" + "original_message": "Docstring is over-indented", + "title_templated": False, + "title": "Docstring is over-indented.", + "solution": "Remove indentation levels from your docstring until it is at the correct indentation level.", + "explanation": "" }, "D209": { - "original_message": "Multi-line docstring closing quotes should be on a separate line", - "title": "Docstrings that are longer than one line should have closing quotes on a separate line.", - "solution": "Put the closing quotes of your docstring on a separate line.", - "explanation": "" + "original_message": "Multi-line docstring closing quotes should be on a separate line", + "title_templated": False, + "title": "Docstrings that are longer than one line should have closing quotes on a separate line.", + "solution": "Put the closing quotes of your docstring on a separate line.", + "explanation": """ +This makes larger docstrings easier to read. + +For example: + +``` +def get_stock_level(book): + \"\"\"Return the stock level for the given book. + + This calculates the stock level across multiple stores in the city, + excluding books reserved for customers. + + Args: + book (str): Name of the book. + + Returns: + String of publication date in the format of 'DD/MM/YYYY'. + \"\"\" +``` +""" }, "D210": { - "original_message": "No whitespaces allowed surrounding docstring text", - "title": "Text in docstrings should not be surrounded by whitespace.", - "solution": "Remove any whitespace from the start and end of your docstring.", - "explanation": "" + "original_message": "No whitespaces allowed surrounding docstring text", + "title_templated": False, + "title": "Text in docstrings should not be surrounded by whitespace.", + "solution": "Remove any whitespace from the start and end of your docstring.", + "explanation": "" }, "D211": { - "original_message": "No blank lines allowed before class docstring", - "title": "Class docstrings should not have blank lines before them.", - "solution": "Remove any blank lines before your class docstring.", - "explanation": "" + "original_message": "No blank lines allowed before class docstring", + "title_templated": False, + "title": "Class docstrings should not have blank lines before them.", + "solution": "Remove any blank lines before your class docstring.", + "explanation": "" }, "D212": { - "original_message": "Multi-line docstring summary should start at the first line", - "title": "Docstrings that are more than one line long should start at the first line.", - "solution": "Ensure your docstring starts on the first line with quotes.", - "explanation": "" + "original_message": "Multi-line docstring summary should start at the first line", + "title_templated": False, + "title": "Docstrings that are more than one line long should start at the first line.", + "solution": """ +Ensure your docstring starts on the first line with quotes. + +For example: + +``` +def get_stock_level(book): + \"\"\"Return the stock level for the given book. +``` +""", + "explanation": "" }, "D213": { - "original_message": "Multi-line docstring summary should start at the second line", - "title": "Docstrings that are more than one line long should start at the second line.", - "solution": "Ensure your docstring starts on the second line, which is the first line without quotes.", - "explanation": "" + "original_message": "Multi-line docstring summary should start at the second line", + "title_templated": False, + "title": "Docstrings that are more than one line long should start at the second line.", + "solution": "Ensure your docstring starts on the second line, which is the first line without quotes.", + "explanation": "" }, "D214": { - "original_message": "Section is over-indented", - "title": "Section is indented by too many levels.", - "solution": "Remove indentation levels from this section until it is at the correct indentation level.", - "explanation": "" + "original_message": "Section is over-indented", + "title_templated": False, + "title": "Section is indented by too many levels.", + "solution": "Remove indentation levels from this section until it is at the correct indentation level.", + "explanation": "" }, "D215": { - "original_message": "Section underline is over-indented", - "title": "Section underline is indented by too many levels.", - "solution": "Remove indentation levels from this section underline until it is at the correct indentation level.", - "explanation": "" + "original_message": "Section underline is over-indented", + "title_templated": False, + "title": "Section underline is indented by too many levels.", + "solution": "Remove indentation levels from this section underline until it is at the correct indentation level.", + "explanation": "" }, "D300": { - "original_message": "Use “”“triple double quotes”“”", - "title": "Use “”“triple double quotes”“” around docstrings.", - "solution": "Use “”“triple double quotes”“” around your docstring.", - "explanation": "" + "original_message": "Use \"\"\"triple double quotes\"\"\"", + "title_templated": False, + "title": "Use \"\"\"triple double quotes\"\"\" around docstrings.", + "solution": "Use \"\"\" triple double quotes around your docstring.", + "explanation": "" }, "D301": { - "original_message": "Use r”“” if any backslashes in a docstring", - "title": "Use r”“” if there are any backslashes in a docstring.", - "solution": "Use r”“” at the beginning of your docstring if it contains any backslashes.", - "explanation": "" + "original_message": "Use r\"\"\" if any backslashes in a docstring", + "title_templated": False, + "title": "Use r\"\"\" if there are any backslashes in a docstring.", + "solution": "Use r\"\"\" at the beginning of your docstring if it contains any backslashes.", + "explanation": "" }, "D302": { - "original_message": "Use u”“” for Unicode docstrings", - "title": "Use u”“” for docstrings that contain Unicode.", - "solution": "Use u”“” at the beginning of your docstring if it contains any Unicode.", - "explanation": "" + "original_message": "Use u\"\"\" for Unicode docstrings", + "title_templated": False, + "title": "Use u\"\"\" for docstrings that contain Unicode.", + "solution": "Use u\"\"\" at the beginning of your docstring if it contains any Unicode.", + "explanation": "" }, "D400": { - "original_message": "First line should end with a period", - "title": "The first line in docstrings should end with a period.", - "solution": "Add a period to the end of the first line in your docstring.", - "explanation": "" + "original_message": "First line should end with a period", + "title_templated": False, + "title": "The first line in docstrings should end with a period.", + "solution": "Add a period to the end of the first line in your docstring. It should be a short summary of your code, if it's too long you may need to break it apart into multiple sentences.", + "explanation": "" }, "D401": { - "original_message": "First line should be in imperative mood", - "title": "The first line in docstrings should read like a command.", - "solution": "Ensure the first line in your docstring reads like a command, not a description. For example 'Do this' instead of 'Does this', 'Return this' instead of 'Returns this'.", - "explanation": "" + "original_message": "First line should be in imperative mood", + "title_templated": False, + "title": "The first line in docstrings should read like a command.", + "solution": "Ensure the first line in your docstring reads like a command, not a description. For example 'Do this' instead of 'Does this', 'Return this' instead of 'Returns this'.", + "explanation": "" }, "D402": { - "original_message": "First line should not be the function’s “signature”", - "title": "The first line in docstrings should not be the function’s “signature”.", - "solution": "Move the function’s “signature” to a different line or remove it completely.", - "explanation": "" + "original_message": "First line should not be the function’s signature", + "title_templated": False, + "title": "The first line in docstrings should not be the function’s signature.", + "solution": "Move the function’s signature to a different line or remove it completely.", + "explanation": "" }, "D403": { - "original_message": "First line should not be the function’s “signature”", - "title": "The first line in docstrings should not be the function’s “signature”.", - "solution": "Move the function’s “signature” to a different line or remove it completely.", - "explanation": "" + "original_message": "First line should not be the function’s 'signature'", + "title_templated": False, + "title": "The first line in docstrings should not be the function’s 'signature'.", + "solution": "Move the function’s 'signature' to a different line or remove it completely.", + "explanation": "" }, "D404": { - "original_message": "First word of the docstring should not be 'This'", - "title": "First word of the docstring should not be 'This'.", - "solution": "Rephrase the docstring so that the first word is not 'This'.", - "explanation": "" + "original_message": "First word of the docstring should not be 'This'", + "title_templated": False, + "title": "First word of the docstring should not be 'This'.", + "solution": "Rephrase the docstring so that the first word is not 'This'.", + "explanation": "" }, "D405": { - "original_message": "Section name should be properly capitalized", - "title": "Section name should be properly capitalised.", - "solution": "Capitalise the section name.", - "explanation": "" + "original_message": "Section name should be properly capitalized", + "title_templated": False, + "title": "Section name should be properly capitalised.", + "solution": "Capitalise the section name.", + "explanation": "" }, "D406": { - "original_message": "Section name should end with a newline", - "title": "Section names should end with a newline.", - "solution": "Add a newline after the section name.", - "explanation": "" + "original_message": "Section name should end with a newline", + "title_templated": False, + "title": "Section names should end with a newline.", + "solution": "Add a newline after the section name.", + "explanation": "" }, "D407": { - "original_message": "Missing dashed underline after section", - "title": "Section names should have a dashed line underneath them.", - "solution": "Add a dashed line underneath the section name.", - "explanation": "" + "original_message": "Missing dashed underline after section", + "title_templated": False, + "title": "Section names should have a dashed line underneath them.", + "solution": "Add a dashed line underneath the section name.", + "explanation": "" }, "D408": { - "original_message": "Section underline should be in the line following the section’s name", - "title": "Dashed line should be on the line following the section's name.", - "solution": "Put the dashed underline on the line immediately following the section name.", - "explanation": "" + "original_message": "Section underline should be in the line following the section’s name", + "title_templated": False, + "title": "Dashed line should be on the line following the section's name.", + "solution": "Put the dashed underline on the line immediately following the section name.", + "explanation": "" }, "D409": { - "original_message": "Section underline should match the length of its name", - "title": "Dashed section underline should match the length of the section's name.", - "solution": "Add or remove dashes from the dashed underline until it matches the length of the section name.", - "explanation": "" + "original_message": "Section underline should match the length of its name", + "title_templated": False, + "title": "Dashed section underline should match the length of the section's name.", + "solution": "Add or remove dashes from the dashed underline until it matches the length of the section name.", + "explanation": "" }, "D410": { - "original_message": "Missing blank line after section", - "title": "Section should have a blank line after it.", - "solution": "Add a blank line after the section.", - "explanation": "" + "original_message": "Missing blank line after section", + "title_templated": False, + "title": "Section should have a blank line after it.", + "solution": "Add a blank line after the section.", + "explanation": "" }, "D411": { - "original_message": "Missing blank line before section", - "title": "Section should have a blank line before it.", - "solution": "Add a blank line before the section.", - "explanation": "" + "original_message": "Missing blank line before section", + "title_templated": False, + "title": "Section should have a blank line before it.", + "solution": "Add a blank line before the section.", + "explanation": "" }, "D412": { - "original_message": "No blank lines allowed between a section header and its content", - "title": "There should be no blank lines between a section header and its content.", - "solution": "Remove any blank lines that are between the section header and its content.", - "explanation": "" + "original_message": "No blank lines allowed between a section header and its content", + "title_templated": False, + "title": "There should be no blank lines between a section header and its content.", + "solution": "Remove any blank lines that are between the section header and its content.", + "explanation": "" }, "D413": { - "original_message": "Missing blank line after last section", - "title": "The last section in a docstring should have a blank line after it.", - "solution": "Add a blank line after the last section in your docstring.", - "explanation": "" + "original_message": "Missing blank line after last section", + "title_templated": False, + "title": "The last section in a docstring should have a blank line after it.", + "solution": "Add a blank line after the last section in your docstring.", + "explanation": "" }, "D414": { - "original_message": "Section has no content", - "title": "Sections in docstrings must have content.", - "solution": "Add content to the section in your docstring.", - "explanation": "" + "original_message": "Section has no content", + "title_templated": False, + "title": "Sections in docstrings must have content.", + "solution": "Add content to the section in your docstring.", + "explanation": "" }, "D415": { - "original_message": "First line should end with a period, question mark, or exclamation point", - "title": "The first line in your docstring should end with a period, question mark, or exclamation point.", - "solution": "Add a period, question mark, or exclamation point to the end of the first line in your docstring.", - "explanation": "" + "original_message": "First line should end with a period, question mark, or exclamation point", + "title_templated": False, + "title": "The first line in your docstring should end with a period, question mark, or exclamation point.", + "solution": "Add a period, question mark, or exclamation point to the end of the first line in your docstring.", + "explanation": "" }, "D416": { - "original_message": "Section name should end with a colon", - "title": "Section names should end with a colon.", - "solution": "Add a colon to the end of your section name.", - "explanation": "" + "original_message": "Section name should end with a colon", + "title_templated": False, + "title": "Section names should end with a colon.", + "solution": "Add a colon to the end of your section name.", + "explanation": "" }, "D417": { - "original_message": "Missing argument descriptions in the docstring", - "title": "Docstrings should include argument descriptions.", - "solution": "Add argument descriptions to your docstring.", - "explanation": "" + "original_message": "Missing argument descriptions in the docstring", + "title_templated": False, + "title": "Docstrings should include argument descriptions.", + "solution": "Add argument descriptions to your docstring.", + "explanation": "" }, "N801": { - "original_message": "class names should use CapWords convention", - "title": "Class names should use the CapWords convention.", - "solution": "Edit your class names to follow the CapWords convention.", - "explanation": "" + "original_message": "class names should use CapWords convention", + "title_templated": False, + "title": "Class names should use the CapWords convention.", + "solution": "Edit your class names to follow the CapWords convention, where each word is capitalised with no spaces.", + "explanation": "" }, "N802": { - "original_message": "function name should be lowercase", - "title": "Function names should be lowercase.", - "solution": "Edit your function names to be lowercase.", - "explanation": "" + "original_message": "function name should be lowercase", + "title_templated": False, + "title": "Function names should be lowercase.", + "solution": "Edit your function names to be lowercase, with underscores for spaces.", + "explanation": "" }, "N803": { - "original_message": "argument name should be lowercase", - "title": "Argument names should be lowercase.", - "solution": "Edit your argument names to be lowercase.", - "explanation": "" + "original_message": "argument name should be lowercase", + "title_templated": False, + "title": "Argument names should be lowercase.", + "solution": "Edit your function names to be lowercase, with underscores for spaces.", + "explanation": "" }, "N804": { - "original_message": "first argument of a classmethod should be named 'cls'", - "title": "The first argument of a classmethod should be named 'cls'.", - "solution": "Edit the first argument in your classmethod to be named 'cls'.", - "explanation": "" + "original_message": "first argument of a classmethod should be named `cls`", + "title_templated": False, + "title": "The first argument of a classmethod should be named `cls`.", + "solution": "Edit the first argument in your classmethod to be named `cls`.", + "explanation": "This is the common term that Python programmers use for a classmethod." }, "N805": { - "original_message": "first argument of a method should be named 'self'", - "title": "The first argument of a method should be named 'self'.", - "solution": "Edit the first argument in your method to be named 'self'.", - "explanation": "" + "original_message": "first argument of a method should be named `self`", + "title_templated": False, + "title": "The first argument of a method should be named `self`.", + "solution": "Edit the first argument in your method to be named `self`.", + "explanation": "This is the common term that Python programmers use for the object of a class's method." }, "N806": { - "original_message": "variable in function should be lowercase", - "title": "Variables in functions should be lowercase.", - "solution": "Edit the variable in your function to be lowercase.", - "explanation": "" + "original_message": "variable in function should be lowercase", + "title_templated": False, + "title": "Variables in functions should be lowercase.", + "solution": "Edit the variable in your function to be lowercase.", + "explanation": "" }, "N807": { - "original_message": "function name should not start and end with '__'", - "title": "Function names should not start and end with '__'", - "solution": "Edit the function name so that it does not start and end with '__'", - "explanation": "" + "original_message": "function name should not start and end with `__`", + "title_templated": False, + "title": "Function names should not start and end with `__` (double underscore).", + "solution": "Edit the function name so that it does not start and end with `__`", + "explanation": "" }, "N811": { - "original_message": "constant imported as non constant", - "title": "Import that should have been imported as a constant has been imported as a non constant.", - "solution": "Edit the import to be imported as a constant (use all capital letters in the 'import as...' name).", - "explanation": "" + "original_message": "constant imported as non constant", + "title_templated": False, + "title": "Import that should have been imported as a constant has been imported as a non constant.", + "solution": "Edit the import to be imported as a constant (use all capital letters in the 'import as...' name).", + "explanation": "" }, "N812": { - "original_message": "lowercase imported as non lowercase", - "title": "Import that should have been imported as lowercase has been imported as non lowercase", - "solution": "Edit the import to be imported as lowercase (use lowercase in the 'import as...' name).", - "explanation": "" + "original_message": "lowercase imported as non lowercase", + "title_templated": False, + "title": "Import that should have been imported as lowercase has been imported as non lowercase", + "solution": "Edit the import to be imported as lowercase (use lowercase in the `import as` name).", + "explanation": "" }, "N813": { - "original_message": "camelcase imported as lowercase", - "title": "Import that should have been imported as camelCase has been imported as lowercase.", - "solution": "Edit the import to be imported as camelCase (use camelCase in the 'import as ...' name).", - "explanation": "" + "original_message": "camelcase imported as lowercase", + "title_templated": False, + "title": "Import that should have been imported as camelCase has been imported as lowercase.", + "solution": "Edit the import to be imported as camelCase (use camelCase in the 'import as ...' name).", + "explanation": "" }, "N814": { - "original_message": "camelcase imported as constant", - "title": "Import that should have been imported as camelCase has been imported as a constant.", - "solution": "Edit the import to be imported as camelCase (use camelCase in the 'import as ...' name).", - "explanation": "" + "original_message": "camelcase imported as constant", + "title_templated": False, + "title": "Import that should have been imported as camelCase has been imported as a constant.", + "solution": "Edit the import to be imported as camelCase (use camelCase in the 'import as ...' name).", + "explanation": "" }, "N815": { - "original_message": "mixedCase variable in class scope", - "title": "Mixed case variable used in the class scope", - "solution": "Edit the variable name so that it doesn't use mixedCase.", - "explanation": "" + "original_message": "mixedCase variable in class scope", + "title_templated": False, + "title": "Mixed case variable used in the class scope", + "solution": "Edit the variable name so that it doesn't use mixedCase.", + "explanation": "" }, "N816": { - "original_message": "mixedCase variable in global scope", - "title": "Mixed case variable used in the global scope", - "solution": "Edit the variable name so that it doesn't use mixedCase.", - "explanation": "" + "original_message": "mixedCase variable in global scope", + "title_templated": False, + "title": "Mixed case variable used in the global scope", + "solution": "Edit the variable name so that it doesn't use mixedCase.", + "explanation": "" }, "N817": { - "original_message": "camelcase imported as acronym", - "title": "Import that should have been imported as camelCase has been imported as an acronym.", - "solution": "Edit the import to be imported as camelCase (use camelCase in the 'import as...' name).", - "explanation": "" + "original_message": "camelcase imported as acronym", + "title_templated": False, + "title": "Import that should have been imported as camelCase has been imported as an acronym.", + "solution": "Edit the import to be imported as camelCase (use camelCase in the 'import as...' name).", + "explanation": "" }, "Q000": { - "original_message": "Remove bad quotes", - "title": "Use single quotes (') instead of double quotes (\").", - "solution": "Replace any double quotes with single quotes.", - "explanation": "" + "original_message": "Remove bad quotes", + "title_templated": False, + "title": "Use single quotes (') instead of double quotes (\").", + "solution": "Replace any double quotes with single quotes.", + "explanation": "" }, "Q001": { - "original_message": "Remove bad quotes from multiline string", - "title": "Use single quotes (') instead of double quotes (\") in multiline strings.", - "solution": "Replace any double quotes in your multiline string with single quotes.", - "explanation": "" + "original_message": "Remove bad quotes from multiline string", + "title_templated": False, + "title": "Use single quotes (') instead of double quotes (\") in multiline strings.", + "solution": "Replace any double quotes in your multiline string with single quotes.", + "explanation": "" }, "Q002": { - "original_message": "Remove bad quotes from docstring", - "title": "Use single quotes (') instead of double quotes (\") in docstrings.", - "solution": "Replace any double quotes in your docstring with single quotes.", - "explanation": "" + "original_message": "Remove bad quotes from docstring", + "title_templated": False, + "title": "Use single quotes (') instead of double quotes (\") in docstrings.", + "solution": "Replace any double quotes in your docstring with single quotes.", + "explanation": "" }, "Q003": { - "original_message": "Change outer quotes to avoid escaping inner quotes", - "title": "Change outer quotes to avoid escaping inner quotes.", - "solution": "Make either the outer quotes single (') and the inner quotes double (\"), or the outer quotes double and the inner quotes single.", - "explanation": "" + "original_message": "Change outer quotes to avoid escaping inner quotes", + "title_templated": False, + "title": "Change outer quotes to avoid escaping inner quotes.", + "solution": "Make either the outer quotes single (`'`) and the inner quotes double (`\"`), or the outer quotes double and the inner quotes single.", + "explanation": "" }, } diff --git a/dev b/dev index c77599d6b..9353ad765 100755 --- a/dev +++ b/dev @@ -78,6 +78,10 @@ cmd_update() { echo "" cmd_migrate + echo "" + cmd_load_questions + cmd_load_style_errors + echo "" cmd_collect_static echo "" diff --git a/infrastructure/prod-deploy/update-content.sh b/infrastructure/prod-deploy/update-content.sh index f40d603cd..e5b1fe912 100755 --- a/infrastructure/prod-deploy/update-content.sh +++ b/infrastructure/prod-deploy/update-content.sh @@ -16,5 +16,6 @@ source ./codewof/load-prod-envs.sh ./dev migrate docker-compose exec django /docker_venv/bin/python3 ./manage.py load_user_types ./dev load_questions +./dev load_style_errors ./dev load_achievements ./dev raise_backdate_flags diff --git a/setup.cfg b/setup.cfg index 67655d77c..3db0fedec 100644 --- a/setup.cfg +++ b/setup.cfg @@ -23,7 +23,6 @@ per-file-ignores = [pydocstyle] # Ignore following rules to allow Google Python Style docstrings add_ignore = D407,D413 -match = (?!test_).*\.py match_dir = (?!migrations|node_modules|files|build|staticfiles|content|temp).* [mypy] From 4fd5e21f85698f318e825b31ff2e18b276c8e8bf Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Thu, 11 Jun 2020 14:39:43 +1200 Subject: [PATCH 35/48] Add test cases --- codewof/style/views.py | 17 ++- codewof/templates/style/component/result.txt | 2 +- codewof/tests/style/__init__.py | 1 + codewof/tests/style/models/__init__.py | 1 + codewof/tests/style/models/test_error.py | 13 ++ codewof/tests/style/urls/test_home.py | 13 ++ codewof/tests/style/urls/test_language.py | 16 +++ .../style/urls/test_language_statistics.py | 16 +++ codewof/tests/style/utils.py | 1 + codewof/tests/style/views/__init__.py | 1 + codewof/tests/style/views/test_home_view.py | 86 ++++++++++++ .../views/test_language_statistics_view.py | 123 ++++++++++++++++++ .../tests/style/views/test_language_view.py | 83 ++++++++++++ 13 files changed, 368 insertions(+), 5 deletions(-) create mode 100644 codewof/tests/style/__init__.py create mode 100644 codewof/tests/style/models/__init__.py create mode 100644 codewof/tests/style/models/test_error.py create mode 100644 codewof/tests/style/urls/test_home.py create mode 100644 codewof/tests/style/urls/test_language.py create mode 100644 codewof/tests/style/urls/test_language_statistics.py create mode 100644 codewof/tests/style/utils.py create mode 100644 codewof/tests/style/views/__init__.py create mode 100644 codewof/tests/style/views/test_home_view.py create mode 100644 codewof/tests/style/views/test_language_statistics_view.py create mode 100644 codewof/tests/style/views/test_language_view.py diff --git a/codewof/style/views.py b/codewof/style/views.py index b12a1626d..c5eccad3d 100644 --- a/codewof/style/views.py +++ b/codewof/style/views.py @@ -2,7 +2,7 @@ import json from django.conf import settings -from django.http import JsonResponse +from django.http import JsonResponse, Http404 from django.views.generic import ( TemplateView, ListView, @@ -39,7 +39,11 @@ def get_context_data(self, **kwargs): """Get additional context data for template.""" context = super().get_context_data(**kwargs) language_slug = self.kwargs.get('language', '') - context['language'] = get_language_info(language_slug) + language_info = get_language_info(language_slug) + # If language not found + if not language_info: + raise Http404 + context['language'] = language_info context['language_header'] = 'style/language-components/{}-header.html'.format(language_slug) context['language_subheader'] = 'style/language-components/{}-subheader.html'.format(language_slug) context['language_js'] = 'js/style_checkers/{}.js'.format(language_slug) @@ -56,10 +60,15 @@ def get_context_data(self, **kwargs): """Get additional context data for template.""" context = super().get_context_data(**kwargs) language_slug = self.kwargs.get('language', '') - context['language'] = get_language_info(language_slug) + language_info = get_language_info(language_slug) + # If language not found + if not language_info: + raise Http404 + context['language'] = language_info context['language_header'] = 'style/language-components/{}-header.html'.format(language_slug) context['issues'] = Error.objects.filter(language=language_slug).order_by('-count', 'code') - context['max_count'] = context['issues'][0].count + if context['issues']: + context['max_count'] = context['issues'][0].count context['characters'] = list(CHARACTER_DESCRIPTIONS.keys()) return context diff --git a/codewof/templates/style/component/result.txt b/codewof/templates/style/component/result.txt index 6a02c452e..93c90b861 100644 --- a/codewof/templates/style/component/result.txt +++ b/codewof/templates/style/component/result.txt @@ -14,7 +14,7 @@ No style issues found! {% endspaceless %} {% for issue in issues %}----------------------------------------------------------------------------- ISSUE {{ forloop.counter }} -Line {{ issue.line_number }} - Issue code: {{ issue.error_code }} +Line {{ issue.line_number }} - Issue code: {{ issue.code }} {{ issue.title|striptags|safe }} {{ issue.solution|striptags|safe }} {% if issue.explanation %}{{ issue.explanation|striptags|safe }}{% endif %}{% endfor %}============================================================================== diff --git a/codewof/tests/style/__init__.py b/codewof/tests/style/__init__.py new file mode 100644 index 000000000..0e31e6c1d --- /dev/null +++ b/codewof/tests/style/__init__.py @@ -0,0 +1 @@ +"""Module for tests of the style application.""" diff --git a/codewof/tests/style/models/__init__.py b/codewof/tests/style/models/__init__.py new file mode 100644 index 000000000..abd2fcfbc --- /dev/null +++ b/codewof/tests/style/models/__init__.py @@ -0,0 +1 @@ +"""Module for tests of the models in the style application.""" diff --git a/codewof/tests/style/models/test_error.py b/codewof/tests/style/models/test_error.py new file mode 100644 index 000000000..25552c41a --- /dev/null +++ b/codewof/tests/style/models/test_error.py @@ -0,0 +1,13 @@ +from django.test import TestCase +from style.models import Error + + +class TopicModelTest(TestCase): + + def test_error_str(self): + error = Error( + language='lang', + code='code123', + ) + error.save() + self.assertEqual(error.__str__(), "lang - code123") diff --git a/codewof/tests/style/urls/test_home.py b/codewof/tests/style/urls/test_home.py new file mode 100644 index 000000000..2be9f53be --- /dev/null +++ b/codewof/tests/style/urls/test_home.py @@ -0,0 +1,13 @@ +from django.test import TestCase +from django.urls import reverse + + +class HomeURLTest(TestCase): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + + def test_home_url(self): + url = reverse("style:home") + self.assertEqual(url, "/style/") diff --git a/codewof/tests/style/urls/test_language.py b/codewof/tests/style/urls/test_language.py new file mode 100644 index 000000000..ffb6d50d6 --- /dev/null +++ b/codewof/tests/style/urls/test_language.py @@ -0,0 +1,16 @@ +from django.test import TestCase +from django.urls import reverse + + +class LanguageURLTest(TestCase): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + + def test_language_url(self): + kwargs = { + "language": "lang" + } + url = reverse("style:language", kwargs=kwargs) + self.assertEqual(url, "/style/lang/") diff --git a/codewof/tests/style/urls/test_language_statistics.py b/codewof/tests/style/urls/test_language_statistics.py new file mode 100644 index 000000000..58f7cef78 --- /dev/null +++ b/codewof/tests/style/urls/test_language_statistics.py @@ -0,0 +1,16 @@ +from django.test import TestCase +from django.urls import reverse + + +class LanguageStatisticsURLTest(TestCase): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + + def test_language_url(self): + kwargs = { + "language": "lang" + } + url = reverse("style:language_statistics", kwargs=kwargs) + self.assertEqual(url, "/style/lang/statistics/") diff --git a/codewof/tests/style/utils.py b/codewof/tests/style/utils.py new file mode 100644 index 000000000..a6271d9bb --- /dev/null +++ b/codewof/tests/style/utils.py @@ -0,0 +1 @@ +EXAMPLE_CODE_1 = """print("Hello world")""" diff --git a/codewof/tests/style/views/__init__.py b/codewof/tests/style/views/__init__.py new file mode 100644 index 000000000..d6666c404 --- /dev/null +++ b/codewof/tests/style/views/__init__.py @@ -0,0 +1 @@ +"""Module for tests of the views in the style application.""" diff --git a/codewof/tests/style/views/test_home_view.py b/codewof/tests/style/views/test_home_view.py new file mode 100644 index 000000000..d2d6f67f9 --- /dev/null +++ b/codewof/tests/style/views/test_home_view.py @@ -0,0 +1,86 @@ +from http import HTTPStatus +from django.test import TestCase, override_settings +from django.urls import reverse + + +class HomeViewTest(TestCase): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = 'en' + + @override_settings(STYLE_CHECKER_LANGUAGES={}) + def test_home_with_no_languages(self): + url = reverse('style:home') + response = self.client.get(url) + self.assertEqual(HTTPStatus.OK, response.status_code) + self.assertEqual( + response.context['languages'], + dict() + ) + + @override_settings(STYLE_CHECKER_LANGUAGES={ + 'python3': { + 'name': 'Python 3', + 'slug': 'python3', + 'svg-icon': 'devicon-python.svg', + 'checker-config': '', + 'example_code': '', + }, + }) + def test_index_with_one_language(self): + url = reverse('style:home') + response = self.client.get(url) + self.assertEqual(HTTPStatus.OK, response.status_code) + self.assertEqual( + response.context['languages'], + { + 'python3': { + 'name': 'Python 3', + 'slug': 'python3', + 'svg-icon': 'devicon-python.svg', + 'checker-config': '', + 'example_code': '', + }, + } + ) + + @override_settings(STYLE_CHECKER_LANGUAGES={ + 'python3': { + 'name': 'Python 3', + 'slug': 'python3', + 'svg-icon': 'devicon-python.svg', + 'checker-config': '', + 'example_code': '', + }, + 'ruby': { + 'name': 'Ruby', + 'slug': 'ruby', + 'svg-icon': 'devicon-ruby.svg', + 'checker-config': '', + 'example_code': '', + }, + }) + def test_index_with_multiple_languages(self): + url = reverse('style:home') + response = self.client.get(url) + self.assertEqual(HTTPStatus.OK, response.status_code) + self.assertEqual( + response.context['languages'], + { + 'python3': { + 'name': 'Python 3', + 'slug': 'python3', + 'svg-icon': 'devicon-python.svg', + 'checker-config': '', + 'example_code': '', + }, + 'ruby': { + 'name': 'Ruby', + 'slug': 'ruby', + 'svg-icon': 'devicon-ruby.svg', + 'checker-config': '', + 'example_code': '', + }, + } + ) diff --git a/codewof/tests/style/views/test_language_statistics_view.py b/codewof/tests/style/views/test_language_statistics_view.py new file mode 100644 index 000000000..7e2c83a67 --- /dev/null +++ b/codewof/tests/style/views/test_language_statistics_view.py @@ -0,0 +1,123 @@ +from http import HTTPStatus +from django.test import TestCase, override_settings +from django.urls import reverse +from style.models import Error + + +class LanguageStatisticsViewTest(TestCase): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = 'en' + + @override_settings(STYLE_CHECKER_LANGUAGES={ + 'python3': { + 'name': 'Python 3', + 'slug': 'python3', + 'svg-icon': 'devicon-python.svg', + 'checker-config': '', + 'example_code': '', + }, + }) + def test_language_statistics_view_with_valid_slug(self): + kwargs = { + 'language': 'python3', + } + url = reverse('style:language_statistics', kwargs=kwargs) + response = self.client.get(url) + self.assertEqual(HTTPStatus.OK, response.status_code) + + @override_settings(STYLE_CHECKER_LANGUAGES={ + 'python3': { + 'name': 'Python 3', + 'slug': 'python3', + 'svg-icon': 'devicon-python.svg', + 'checker-config': '', + 'example_code': '', + }, + }) + def test_language_statistics_view_with_invalid_slug(self): + kwargs = { + 'language': 'xyz', + } + url = reverse('style:language_statistics', kwargs=kwargs) + response = self.client.get(url) + self.assertEqual(HTTPStatus.NOT_FOUND, response.status_code) + + @override_settings(STYLE_CHECKER_LANGUAGES={ + 'python3': { + 'name': 'Python 3', + 'slug': 'python3', + 'svg-icon': 'devicon-python.svg', + 'checker-config': '', + 'example_code': '', + }, + }) + def test_language_statistics_view_basic_context(self): + kwargs = { + 'language': 'python3', + } + url = reverse('style:language_statistics', kwargs=kwargs) + response = self.client.get(url) + self.assertEqual(HTTPStatus.OK, response.status_code) + self.assertEqual( + response.context['language'], + { + 'name': 'Python 3', + 'slug': 'python3', + 'svg-icon': 'devicon-python.svg', + 'checker-config': '', + 'example_code': '', + } + ) + self.assertEqual( + response.context['language_header'], + 'style/language-components/python3-header.html' + ) + + @override_settings(STYLE_CHECKER_LANGUAGES={ + 'python3': { + 'name': 'Python 3', + 'slug': 'python3', + 'svg-icon': 'devicon-python.svg', + 'checker-config': '', + 'example_code': '', + }, + }) + def test_language_statistics_view_valid_issues_context(self): + error1 = Error( + language='python3', + code='error1', + count=10, + ) + error1.save() + error2 = Error( + language='python3', + code='error2', + count=20, + ) + error2.save() + error3 = Error( + language='python3', + code='error3', + count=30, + ) + error3.save() + kwargs = { + 'language': 'python3', + } + url = reverse('style:language_statistics', kwargs=kwargs) + response = self.client.get(url) + self.assertEqual(HTTPStatus.OK, response.status_code) + self.assertQuerysetEqual( + response.context['issues'], + [ + '', + '', + '', + ] + ) + self.assertEqual( + response.context['max_count'], + 30 + ) diff --git a/codewof/tests/style/views/test_language_view.py b/codewof/tests/style/views/test_language_view.py new file mode 100644 index 000000000..5b6e11758 --- /dev/null +++ b/codewof/tests/style/views/test_language_view.py @@ -0,0 +1,83 @@ +from http import HTTPStatus +from django.test import TestCase, override_settings +from django.urls import reverse + + +class LanguageViewTest(TestCase): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = 'en' + + @override_settings(STYLE_CHECKER_LANGUAGES={ + 'python3': { + 'name': 'Python 3', + 'slug': 'python3', + 'svg-icon': 'devicon-python.svg', + 'checker-config': '', + 'example_code': '', + }, + }) + def test_language_view_with_valid_slug(self): + kwargs = { + 'language': 'python3', + } + url = reverse('style:language', kwargs=kwargs) + response = self.client.get(url) + self.assertEqual(HTTPStatus.OK, response.status_code) + + @override_settings(STYLE_CHECKER_LANGUAGES={ + 'python3': { + 'name': 'Python 3', + 'slug': 'python3', + 'svg-icon': 'devicon-python.svg', + 'checker-config': '', + 'example_code': '', + }, + }) + def test_language_view_with_invalid_slug(self): + kwargs = { + 'language': 'xyz', + } + url = reverse('style:language', kwargs=kwargs) + response = self.client.get(url) + self.assertEqual(HTTPStatus.NOT_FOUND, response.status_code) + + @override_settings(STYLE_CHECKER_LANGUAGES={ + 'python3': { + 'name': 'Python 3', + 'slug': 'python3', + 'svg-icon': 'devicon-python.svg', + 'checker-config': '', + 'example_code': '', + }, + }) + def test_language_view_context(self): + kwargs = { + 'language': 'python3', + } + url = reverse('style:language', kwargs=kwargs) + response = self.client.get(url) + self.assertEqual(HTTPStatus.OK, response.status_code) + self.assertEqual( + response.context['language'], + { + 'name': 'Python 3', + 'slug': 'python3', + 'svg-icon': 'devicon-python.svg', + 'checker-config': '', + 'example_code': '', + } + ) + self.assertEqual( + response.context['language_header'], + 'style/language-components/python3-header.html' + ) + self.assertEqual( + response.context['language_subheader'], + 'style/language-components/python3-subheader.html' + ) + self.assertEqual( + response.context['language_js'], + 'js/style_checkers/python3.js' + ) From 834843ec53e2c6c0fbaad167bb6baf27ef6e7150 Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Thu, 11 Jun 2020 14:48:02 +1200 Subject: [PATCH 36/48] Style improvements --- codewof/tests/users/test_views.py | 11 +++++------ setup.cfg | 4 ++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/codewof/tests/users/test_views.py b/codewof/tests/users/test_views.py index 9c71ba9ca..73ead367e 100644 --- a/codewof/tests/users/test_views.py +++ b/codewof/tests/users/test_views.py @@ -66,12 +66,11 @@ def test_context_object(self): class TestUserUpdateView: - """ - TODO: - extracting view initialization code as class-scoped fixture - would be great if only pytest-django supported non-function-scoped - fixture db access -- this is a work-in-progress for now: - https://github.com/pytest-dev/pytest-django/pull/258 + """Extracting view initialization code as class-scoped fixture. + + Would be great if only pytest-django supported non-function-scoped + fixture db access -- this is a work-in-progress for now: + https://github.com/pytest-dev/pytest-django/pull/258 """ def test_get_success_url(self, user, request_factory): diff --git a/setup.cfg b/setup.cfg index 3db0fedec..13f692a0c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -11,7 +11,6 @@ exclude = */temp, */manage.py, codewof/programming/content/en/*/initial.py, - */tests/*, show-source = True statistics = True count = True @@ -19,11 +18,12 @@ ignore = Q000, Q001, Q002, W503 per-file-ignores = style/style_checkers/python3_data.py:E501 programming/content/*/*.py:D100,D103 + tests/*.py:D100,D101,D102,D103,D107 [pydocstyle] # Ignore following rules to allow Google Python Style docstrings add_ignore = D407,D413 -match_dir = (?!migrations|node_modules|files|build|staticfiles|content|temp).* +match_dir = (?!migrations|node_modules|files|build|staticfiles|content|temp|tests).* [mypy] python_version = 3.6 From 812708232c8ea6046fab750ed3f3ed509d3dfcdf Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Thu, 11 Jun 2020 15:15:23 +1200 Subject: [PATCH 37/48] Review changes --- codewof/config/settings/base.py | 1 + .../templatetags/simplify_error_template.py | 23 +++++++++++++++++++ .../templates/style/language-statistics.html | 6 ++--- 3 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 codewof/config/templatetags/simplify_error_template.py diff --git a/codewof/config/settings/base.py b/codewof/config/settings/base.py index 32a6a304b..8e9d398fe 100644 --- a/codewof/config/settings/base.py +++ b/codewof/config/settings/base.py @@ -251,6 +251,7 @@ ], 'libraries': { 'query_replace': 'config.templatetags.query_replace', + 'simplify_error_template': 'config.templatetags.simplify_error_template', }, }, }, diff --git a/codewof/config/templatetags/simplify_error_template.py b/codewof/config/templatetags/simplify_error_template.py new file mode 100644 index 000000000..94a4e1978 --- /dev/null +++ b/codewof/config/templatetags/simplify_error_template.py @@ -0,0 +1,23 @@ +"""Module for the custom simplify_error_template template tag.""" + +from django import template +from django.utils.safestring import mark_safe + +register = template.Library() + +SEARCH_TEXT = '{article} {character_description}' +REPLACE_TEXT = 'a {character}' + + +@register.simple_tag +def simplify_error_template(template): + """Simplify template for rendering to user. + + Args: + template (str): String of template. + + Returns: + Updated string. + """ + new_text = template.replace(SEARCH_TEXT, REPLACE_TEXT) + return mark_safe(new_text) diff --git a/codewof/templates/style/language-statistics.html b/codewof/templates/style/language-statistics.html index 47d7bb9e8..b7c6e442d 100644 --- a/codewof/templates/style/language-statistics.html +++ b/codewof/templates/style/language-statistics.html @@ -1,6 +1,6 @@ {% extends "base.html" %} -{% load static i18n django_bootstrap_breadcrumbs %} +{% load static i18n django_bootstrap_breadcrumbs simplify_error_template %} {% block title %}{{ language.name }} Style Checker for beginners{% endblock %} @@ -18,7 +18,7 @@

Statistics

The following table shows the occurence counts of style issues submitted to the {{ language.name }} style checker.

-

The text {article} {character_description} will mean the style issue involves one of the following characters: {{ characters|join:"" }}

+

The text {character} will mean the style issue involves one of the following characters: {{ characters|join:"" }}

{% endblock page_heading %} {% block content %} @@ -33,7 +33,7 @@

From 796e11a3c79824e65e8589bd6c65fb775c164eea Mon Sep 17 00:00:00 2001 From: Jack Morgan Date: Thu, 11 Jun 2020 22:46:55 +1200 Subject: [PATCH 38/48] Update codewof/templates/style/language.html Co-authored-by: Courtney Bracefield --- codewof/templates/style/language.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codewof/templates/style/language.html b/codewof/templates/style/language.html index eaa98225d..a487be2d3 100644 --- a/codewof/templates/style/language.html +++ b/codewof/templates/style/language.html @@ -52,7 +52,7 @@

Your code

We have encountered an error. Feel free to contact us if this error continues.
- From 397d85346c8365d5fba549a314f1f5141fd20209 Mon Sep 17 00:00:00 2001 From: Jack Morgan Date: Thu, 11 Jun 2020 22:47:49 +1200 Subject: [PATCH 39/48] Update codewof/style/style_checkers/python3.py Co-authored-by: Courtney Bracefield --- codewof/style/style_checkers/python3.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codewof/style/style_checkers/python3.py b/codewof/style/style_checkers/python3.py index fd7a172c2..e3303b6e9 100644 --- a/codewof/style/style_checkers/python3.py +++ b/codewof/style/style_checkers/python3.py @@ -68,7 +68,7 @@ def process_results(result_text, is_example_code): Args: result_text (str): Text output from style checker. - is_example_code (bool): True if provide code matches the example code. + is_example_code (bool): True if provided code matches the example code. Returns: List of dictionaries of result data. From be65860a99d15d9b70130fea2b983193b3f89ef3 Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Thu, 11 Jun 2020 22:53:12 +1200 Subject: [PATCH 40/48] Style updates --- codewof/style/style_checkers/flake8.ini | 2 +- codewof/style/style_checkers/python3_data.py | 21 +++++++++++++------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/codewof/style/style_checkers/flake8.ini b/codewof/style/style_checkers/flake8.ini index bcb894ca2..14ca0186d 100644 --- a/codewof/style/style_checkers/flake8.ini +++ b/codewof/style/style_checkers/flake8.ini @@ -2,7 +2,7 @@ show_source = False statistics = False count = False -ignore = Q000, Q001, Q002 +ignore = Q000, Q001, Q002, E121, E123, E126, E133, E226, E241, E242, E704, W503, W504 [darglint] strictness = long diff --git a/codewof/style/style_checkers/python3_data.py b/codewof/style/style_checkers/python3_data.py index 416b2a681..9866b10cc 100644 --- a/codewof/style/style_checkers/python3_data.py +++ b/codewof/style/style_checkers/python3_data.py @@ -331,8 +331,8 @@ "E302": { "original_message": "expected 2 blank lines, found 0", "title_templated": False, - "title": "Two blank lines are expected between functions and classes.", - "solution": "Ensure there are two blank lines between functions and classes.", + "title": "Two blank lines are expected before and after each function or class.", + "solution": "Ensure there are two blank lines before and after each function and class.", "explanation": "" }, "E303": { @@ -491,6 +491,13 @@ "solution": "Change the names of these functions to something more descriptive.", "explanation": "Functions named `l`, `O`, or `I` can be very hard to read. This is because the letter `I` and the letter `l` are easily confused, and the letter `O` and the number `0` can be easily confused." }, + "E999": { + "original_message": "Syntax error", + "title_templated": False, + "title": "Program failed to compile.", + "solution": "Make sure your code is working.", + "explanation": "" + }, # TODO: Continue from this point onwards with checking text and adding templating boolean "W191": { "original_message": "indentation contains tabs", @@ -894,15 +901,15 @@ def get_stock_level(book): "D402": { "original_message": "First line should not be the function’s signature", "title_templated": False, - "title": "The first line in docstrings should not be the function’s signature.", - "solution": "Move the function’s signature to a different line or remove it completely.", + "title": "The first line in docstrings should not be a copy of the function’s definition.", + "solution": "Rewrite the docstring to describe the purpose of the function.", "explanation": "" }, "D403": { - "original_message": "First line should not be the function’s 'signature'", + "original_message": "First word of the first line should be properly capitalized", "title_templated": False, - "title": "The first line in docstrings should not be the function’s 'signature'.", - "solution": "Move the function’s 'signature' to a different line or remove it completely.", + "title": "The first word in the first line should be capitalised.", + "solution": "Capitalise the first word.", "explanation": "" }, "D404": { From 5ac9eb06a0efe7b0fb2ee036be05d6a552ff7095 Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Thu, 11 Jun 2020 23:14:54 +1200 Subject: [PATCH 41/48] Add test cases --- codewof/tests/style/management/__init__.py | 1 + .../style/management/test_load_style_errors.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 codewof/tests/style/management/__init__.py create mode 100644 codewof/tests/style/management/test_load_style_errors.py diff --git a/codewof/tests/style/management/__init__.py b/codewof/tests/style/management/__init__.py new file mode 100644 index 000000000..bf6c524d1 --- /dev/null +++ b/codewof/tests/style/management/__init__.py @@ -0,0 +1 @@ +"""Module for tests of the management commands in the style application.""" diff --git a/codewof/tests/style/management/test_load_style_errors.py b/codewof/tests/style/management/test_load_style_errors.py new file mode 100644 index 000000000..8c8b31258 --- /dev/null +++ b/codewof/tests/style/management/test_load_style_errors.py @@ -0,0 +1,14 @@ +"""Module for the testing custom Django load_style_errors commands.""" + +from django.test import TestCase +from django.core import management + + +class LoadStyleErrorsCommandTest(TestCase): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = "en" + + def test_load_style_errors_valid(self, topic_loader, age_loader): + management.call_command("load_style_errors") From d53e10f393566f848a06a8eefdea6421d8b1cf23 Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Thu, 11 Jun 2020 23:21:57 +1200 Subject: [PATCH 42/48] Remove incorrect parameters --- codewof/tests/style/management/test_load_style_errors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codewof/tests/style/management/test_load_style_errors.py b/codewof/tests/style/management/test_load_style_errors.py index 8c8b31258..c897de6b3 100644 --- a/codewof/tests/style/management/test_load_style_errors.py +++ b/codewof/tests/style/management/test_load_style_errors.py @@ -10,5 +10,5 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.language = "en" - def test_load_style_errors_valid(self, topic_loader, age_loader): + def test_load_style_errors_valid(self): management.call_command("load_style_errors") From fcd237987ca41800998fbb1e75c2194099cf5e00 Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Thu, 11 Jun 2020 23:50:29 +1200 Subject: [PATCH 43/48] Improve tests --- codewof/tests/style/views/test_check_code.py | 29 ++++++++++ codewof/tests/utils/__init__.py | 1 + codewof/tests/utils/errors/__init__.py | 1 + .../test_CouldNotFindGlossaryTermError.py | 30 ++++++++++ .../errors/test_CouldNotFindImageError.py | 30 ++++++++++ .../test_CouldNotFindMarkdownFileError.py | 30 ++++++++++ .../errors/test_CouldNotFindYAMLFileError.py | 28 +++++++++ .../errors/test_EmptyMarkdownFileError.py | 27 +++++++++ .../utils/errors/test_EmptyYAMLFileError.py | 25 ++++++++ .../utils/errors/test_InvalidYAMLFileError.py | 29 ++++++++++ .../errors/test_InvalidYAMLValueError.py | 36 ++++++++++++ .../utils/errors/test_KeyNotFoundError.py | 31 ++++++++++ .../errors/test_MissingRequiredFieldError.py | 57 +++++++++++++++++++ .../errors/test_MissingRequiredModelError.py | 33 +++++++++++ .../test_NoHeadingFoundInMarkdownFileError.py | 25 ++++++++ .../utils/errors/test_VertoConversionError.py | 51 +++++++++++++++++ 16 files changed, 463 insertions(+) create mode 100644 codewof/tests/style/views/test_check_code.py create mode 100644 codewof/tests/utils/__init__.py create mode 100644 codewof/tests/utils/errors/__init__.py create mode 100644 codewof/tests/utils/errors/test_CouldNotFindGlossaryTermError.py create mode 100644 codewof/tests/utils/errors/test_CouldNotFindImageError.py create mode 100644 codewof/tests/utils/errors/test_CouldNotFindMarkdownFileError.py create mode 100644 codewof/tests/utils/errors/test_CouldNotFindYAMLFileError.py create mode 100644 codewof/tests/utils/errors/test_EmptyMarkdownFileError.py create mode 100644 codewof/tests/utils/errors/test_EmptyYAMLFileError.py create mode 100644 codewof/tests/utils/errors/test_InvalidYAMLFileError.py create mode 100644 codewof/tests/utils/errors/test_InvalidYAMLValueError.py create mode 100644 codewof/tests/utils/errors/test_KeyNotFoundError.py create mode 100644 codewof/tests/utils/errors/test_MissingRequiredFieldError.py create mode 100644 codewof/tests/utils/errors/test_MissingRequiredModelError.py create mode 100644 codewof/tests/utils/errors/test_NoHeadingFoundInMarkdownFileError.py create mode 100644 codewof/tests/utils/errors/test_VertoConversionError.py diff --git a/codewof/tests/style/views/test_check_code.py b/codewof/tests/style/views/test_check_code.py new file mode 100644 index 000000000..ea142a5e1 --- /dev/null +++ b/codewof/tests/style/views/test_check_code.py @@ -0,0 +1,29 @@ +from http import HTTPStatus +from django.test import TestCase, override_settings +from django.urls import reverse + + +class CheckCodeViewTest(TestCase): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.language = 'en' + + def test_check_code_valid(self): + pass + + def test_check_code_not_ajax(self): + pass + + def test_check_code_code_empty(self): + pass + + def test_check_code_code_too_long(self): + pass + + def test_check_code_code_invalid_language(self): + pass + + @override_settings(STYLE_CHECKER_LANGUAGES={}) + def test_check_code_with_no_languages(self): + pass diff --git a/codewof/tests/utils/__init__.py b/codewof/tests/utils/__init__.py new file mode 100644 index 000000000..f5cc3e8df --- /dev/null +++ b/codewof/tests/utils/__init__.py @@ -0,0 +1 @@ +"""Module for tests of the utils application.""" diff --git a/codewof/tests/utils/errors/__init__.py b/codewof/tests/utils/errors/__init__.py new file mode 100644 index 000000000..5f6c9d36f --- /dev/null +++ b/codewof/tests/utils/errors/__init__.py @@ -0,0 +1 @@ +"""Module for tests of custom errors.""" diff --git a/codewof/tests/utils/errors/test_CouldNotFindGlossaryTermError.py b/codewof/tests/utils/errors/test_CouldNotFindGlossaryTermError.py new file mode 100644 index 000000000..01f9251ee --- /dev/null +++ b/codewof/tests/utils/errors/test_CouldNotFindGlossaryTermError.py @@ -0,0 +1,30 @@ +"""Test class for CouldNotFindGlossaryTermError error.""" + +from django.test import SimpleTestCase +from utils.errors.CouldNotFindGlossaryTermError import CouldNotFindGlossaryTermError + + +class CouldNotFindGlossaryTermErrorTest(SimpleTestCase): + """Test class for CouldNotFindGlossaryTermError error. + + Note: Tests to check if these were raised appropriately + are located where this exception is used. + """ + + def test_attributes(self): + exception = CouldNotFindGlossaryTermError("term", "file path") + self.assertEqual(exception.term, "term") + self.assertEqual(exception.reference_file_path, "file path") + + def test_string(self): + exception = CouldNotFindGlossaryTermError("term", "file path") + expected_string = ( + "\n****************************ERROR****************************\n" + "File: file path\n\n" + "Could not find glossary term: term\n\n" + "Options:\n" + " - Is the glossary term key defined in the\n" + " application structure file?\n" + " - Is the term spelt correctly?\n" + ) + self.assertEqual(exception.__str__(), expected_string) diff --git a/codewof/tests/utils/errors/test_CouldNotFindImageError.py b/codewof/tests/utils/errors/test_CouldNotFindImageError.py new file mode 100644 index 000000000..f500b2009 --- /dev/null +++ b/codewof/tests/utils/errors/test_CouldNotFindImageError.py @@ -0,0 +1,30 @@ +"""Test class for CouldNotFindImageError error.""" + +from django.test import SimpleTestCase +from utils.errors.CouldNotFindImageError import CouldNotFindImageError + + +class CouldNotFindImageErrorTest(SimpleTestCase): + """Test class for CouldNotFindImageError error. + + Note: Tests to check if these were raised appropriately + are located where this exception is used. + """ + + def test_attributes(self): + exception = CouldNotFindImageError("image path", "reference file path") + self.assertEqual(exception.image_path, "image path") + self.assertEqual(exception.reference_file_path, "reference file path") + + def test_string(self): + exception = CouldNotFindImageError("image path", "reference file path") + expected_string = ( + "\n****************************ERROR****************************\n" + "File: image path\n" + "Referenced in: reference file path\n\n" + "Could not find image.\n\n" + " - Did you spell the name of the file correctly?\n" + " - Does the file exist?\n" + " - Is the file saved in the correct directory?\n" + ) + self.assertEqual(exception.__str__(), expected_string) diff --git a/codewof/tests/utils/errors/test_CouldNotFindMarkdownFileError.py b/codewof/tests/utils/errors/test_CouldNotFindMarkdownFileError.py new file mode 100644 index 000000000..1b29ead46 --- /dev/null +++ b/codewof/tests/utils/errors/test_CouldNotFindMarkdownFileError.py @@ -0,0 +1,30 @@ +"""Test class for CouldNotFindMarkdownFileError error.""" + +from django.test import SimpleTestCase +from utils.errors.CouldNotFindMarkdownFileError import CouldNotFindMarkdownFileError + + +class CouldNotFindMarkdownFileErrorTest(SimpleTestCase): + """Test class for CouldNotFindMarkdownFileError error. + + Note: Tests to check if these were raised appropriately + are located where this exception is used. + """ + + def test_attributes(self): + exception = CouldNotFindMarkdownFileError("md file path", "config file path") + self.assertEqual(exception.md_file_path, "md file path") + self.assertEqual(exception.config_file_path, "config file path") + + def test_string(self): + exception = CouldNotFindMarkdownFileError("md file path", "config file path") + expected_string = ( + "\n****************************ERROR****************************\n" + "File: md file path\n" + "Referenced in: config file path\n\n" + "Could not find Markdown file.\n\n" + " - Did you spell the name of the file correctly?\n" + " - Does the file exist?\n" + " - Is the file saved in the correct directory?\n" + ) + self.assertEqual(exception.__str__(), expected_string) diff --git a/codewof/tests/utils/errors/test_CouldNotFindYAMLFileError.py b/codewof/tests/utils/errors/test_CouldNotFindYAMLFileError.py new file mode 100644 index 000000000..0233ea709 --- /dev/null +++ b/codewof/tests/utils/errors/test_CouldNotFindYAMLFileError.py @@ -0,0 +1,28 @@ +"""Test class for CouldNotFindYAMLFileError error.""" + +from django.test import SimpleTestCase +from utils.errors.CouldNotFindYAMLFileError import CouldNotFindYAMLFileError + + +class CouldNotFindYAMLFileErrorTest(SimpleTestCase): + """Test class for CouldNotFindYAMLFileError error. + + Note: Tests to check if these were raised appropriately + are located where this exception is used. + """ + + def test_attributes(self): + exception = CouldNotFindYAMLFileError("yaml file path") + self.assertEqual(exception.yaml_file_path, "yaml file path") + + def test_string(self): + exception = CouldNotFindYAMLFileError("yaml file path") + expected_string = ( + "\n****************************ERROR****************************\n" + "File: yaml file path\n\n" + "Could not find YAML file (.yaml).\n\n" + " - Did you spell the name of the file correctly?\n" + " - Does the file exist?\n" + " - Is the file saved in the correct directory?\n" + ) + self.assertEqual(exception.__str__(), expected_string) diff --git a/codewof/tests/utils/errors/test_EmptyMarkdownFileError.py b/codewof/tests/utils/errors/test_EmptyMarkdownFileError.py new file mode 100644 index 000000000..a18be4ead --- /dev/null +++ b/codewof/tests/utils/errors/test_EmptyMarkdownFileError.py @@ -0,0 +1,27 @@ +"""Test class for EmptyMarkdownFileError error.""" + +from django.test import SimpleTestCase +from utils.errors.EmptyMarkdownFileError import EmptyMarkdownFileError + + +class EmptyMarkdownFileErrorTest(SimpleTestCase): + """Test class for EmptyMarkdownFileError error. + + Note: Tests to check if these were raised appropriately + are located where this exception is used. + """ + + def test_attributes(self): + exception = EmptyMarkdownFileError("md file path") + self.assertEqual(exception.md_file_path, "md file path") + + def test_string(self): + exception = EmptyMarkdownFileError("md file path") + expected_string = ( + "\n****************************ERROR****************************\n" + "File: md file path\n\n" + "The file contains no content.\n\n" + "Note: A file containing a title and no other content is\n" + "also considered to be empty.\n" + ) + self.assertEqual(exception.__str__(), expected_string) diff --git a/codewof/tests/utils/errors/test_EmptyYAMLFileError.py b/codewof/tests/utils/errors/test_EmptyYAMLFileError.py new file mode 100644 index 000000000..8c89134b0 --- /dev/null +++ b/codewof/tests/utils/errors/test_EmptyYAMLFileError.py @@ -0,0 +1,25 @@ +"""Test class for EmptyYAMLFileError error.""" + +from django.test import SimpleTestCase +from utils.errors.EmptyYAMLFileError import EmptyYAMLFileError + + +class EmptyYAMLFileErrorTest(SimpleTestCase): + """Test class for EmptyYAMLFileError error. + + Note: Tests to check if these were raised appropriately + are located where this exception is used. + """ + + def test_attributes(self): + exception = EmptyYAMLFileError("yaml file path") + self.assertEqual(exception.yaml_file_path, "yaml file path") + + def test_string(self): + exception = EmptyYAMLFileError("yaml file path") + expected_string = ( + "\n****************************ERROR****************************\n" + "File: yaml file path\n\n" + "A YAML file cannot be empty.\n" + ) + self.assertEqual(exception.__str__(), expected_string) diff --git a/codewof/tests/utils/errors/test_InvalidYAMLFileError.py b/codewof/tests/utils/errors/test_InvalidYAMLFileError.py new file mode 100644 index 000000000..eb5637689 --- /dev/null +++ b/codewof/tests/utils/errors/test_InvalidYAMLFileError.py @@ -0,0 +1,29 @@ +"""Test class for InvalidYAMLFileError error.""" + +from django.test import SimpleTestCase +from utils.errors.InvalidYAMLFileError import InvalidYAMLFileError + + +class InvalidYAMLFileErrorTest(SimpleTestCase): + """Test class for InvalidYAMLFileError error. + + Note: Tests to check if these were raised appropriately + are located where this exception is used. + """ + + def test_attributes(self): + exception = InvalidYAMLFileError("yaml file path") + self.assertEqual(exception.yaml_file_path, "yaml file path") + + def test_string(self): + exception = InvalidYAMLFileError("yaml file path") + expected_string = ( + "\n****************************ERROR****************************\n" + "File: yaml file path\n\n" + "Invalid YAML file (.yaml).\n\n" + "Options:\n" + " - Does the file match the expected layout?\n" + " - Does the file contain at least one key:value pair?\n" + " - Is the syntax correct? (are you missing a colon somewhere?)\n" + ) + self.assertEqual(exception.__str__(), expected_string) diff --git a/codewof/tests/utils/errors/test_InvalidYAMLValueError.py b/codewof/tests/utils/errors/test_InvalidYAMLValueError.py new file mode 100644 index 000000000..88bdc6549 --- /dev/null +++ b/codewof/tests/utils/errors/test_InvalidYAMLValueError.py @@ -0,0 +1,36 @@ +"""Test class for InvalidYAMLValueError error.""" + +from django.test import SimpleTestCase +from utils.errors.InvalidYAMLValueError import InvalidYAMLValueError + + +class InvalidYAMLValueErrorTest(SimpleTestCase): + """Test class for InvalidYAMLValueError error. + + Note: Tests to check if these were raised appropriately + are located where this exception is used. + """ + + def test_attributes(self): + exception = InvalidYAMLValueError( + "yaml file path", + "key", + "expected" + ) + self.assertEqual(exception.yaml_file_path, "yaml file path") + self.assertEqual(exception.key, "key") + self.assertEqual(exception.expected, "expected") + + def test_string(self): + exception = InvalidYAMLValueError( + "yaml file path", + "key", + "expected" + ) + expected_string = ( + "\n****************************ERROR****************************\n" + "File: yaml file path\n\n" + "Invalid configuration file value for: key\n\n" + "Expected: expected\n" + ) + self.assertEqual(exception.__str__(), expected_string) diff --git a/codewof/tests/utils/errors/test_KeyNotFoundError.py b/codewof/tests/utils/errors/test_KeyNotFoundError.py new file mode 100644 index 000000000..cd968e959 --- /dev/null +++ b/codewof/tests/utils/errors/test_KeyNotFoundError.py @@ -0,0 +1,31 @@ +"""Test class for KeyNotFoundError error.""" + +from django.test import SimpleTestCase +from utils.errors.KeyNotFoundError import KeyNotFoundError + + +class KeyNotFoundErrorTest(SimpleTestCase): + """Test class for KeyNotFoundError error. + + Note: Tests to check if these were raised appropriately + are located where this exception is used. + """ + + def test_attributes(self): + exception = KeyNotFoundError("config file path", "key", "field") + self.assertEqual(exception.config_file_path, "config file path") + self.assertEqual(exception.key, "key") + self.assertEqual(exception.field, "field") + + def test_string(self): + exception = KeyNotFoundError("config file path", "key", "field") + expected_string = ( + "\n****************************ERROR****************************\n" + "File: config file path\n\n" + "Key: key\n" + '"key" did not match any field\n\n' + "Options:\n" + " - Did you spell the name of the key correctly?\n" + " - Does the key exist?\n" + ) + self.assertEqual(exception.__str__(), expected_string) diff --git a/codewof/tests/utils/errors/test_MissingRequiredFieldError.py b/codewof/tests/utils/errors/test_MissingRequiredFieldError.py new file mode 100644 index 000000000..ce73d6c6f --- /dev/null +++ b/codewof/tests/utils/errors/test_MissingRequiredFieldError.py @@ -0,0 +1,57 @@ +"""Test class for MissingRequiredFieldError error.""" + +from django.test import SimpleTestCase +from utils.errors.MissingRequiredFieldError import MissingRequiredFieldError + + +class MissingRequiredFieldErrorTest(SimpleTestCase): + """Test class for MissingRequiredFieldError error. + + Note: Tests to check if these were raised appropriately + are located where this exception is used. + """ + + def test_attributes(self): + exception = MissingRequiredFieldError( + "config file path", + ["field1", "field2"], + "model" + ) + self.assertEqual(exception.config_file_path, "config file path") + self.assertEqual(exception.required_fields, ["field1", "field2"]) + self.assertEqual(exception.model, "model") + + def test_string_single_field(self): + exception = MissingRequiredFieldError( + "config file path", + ["field1"], + "model" + ) + expected_string = ( + "\n****************************ERROR****************************\n" + "File: config file path\n\n" + "A model requires the following field:\n" + " - field1\n\n" + "For the missing field:\n" + " - Is the field name spelt correctly?\n" + " - Does the field have the correct value?\n" + ) + self.assertEqual(exception.__str__(), expected_string) + + def test_string_multiple_fields(self): + exception = MissingRequiredFieldError( + "config file path", + ["field1", "field2"], + "model" + ) + expected_string = ( + "\n****************************ERROR****************************\n" + "File: config file path\n\n" + "A model requires the following fields:\n" + " - field1\n" + " - field2\n\n" + "For the missing fields:\n" + " - Is the field name spelt correctly?\n" + " - Does the field have the correct value?\n" + ) + self.assertEqual(exception.__str__(), expected_string) diff --git a/codewof/tests/utils/errors/test_MissingRequiredModelError.py b/codewof/tests/utils/errors/test_MissingRequiredModelError.py new file mode 100644 index 000000000..8e5ba5180 --- /dev/null +++ b/codewof/tests/utils/errors/test_MissingRequiredModelError.py @@ -0,0 +1,33 @@ +"""Test class for MissingRequiredModelsError error.""" + +from django.test import SimpleTestCase +from utils.errors.MissingRequiredModelsError import MissingRequiredModelsError + + +class MissingRequiredModelsErrorTest(SimpleTestCase): + """Test class for MissingRequiredModelsError error. + + Note: Tests to check if these were raised appropriately + are located where this exception is used. + """ + + def test_attributes(self): + exception = MissingRequiredModelsError( + "config file path", + ["model1", "model2"] + ) + self.assertEqual(exception.config_file_path, "config file path") + self.assertEqual(exception.required_models, ["model1", "model2"]) + + def test_string(self): + exception = MissingRequiredModelsError( + "config file path", + ["model1", "model2"] + ) + expected_string = ( + "\n****************************ERROR****************************\n" + "File: config file path\n\n" + "The following models are missing from the file:\n" + "['model1', 'model2']\n" + ) + self.assertEqual(exception.__str__(), expected_string) diff --git a/codewof/tests/utils/errors/test_NoHeadingFoundInMarkdownFileError.py b/codewof/tests/utils/errors/test_NoHeadingFoundInMarkdownFileError.py new file mode 100644 index 000000000..de51abe4d --- /dev/null +++ b/codewof/tests/utils/errors/test_NoHeadingFoundInMarkdownFileError.py @@ -0,0 +1,25 @@ +"""Test class for NoHeadingFoundInMarkdownFileError error.""" + +from django.test import SimpleTestCase +from utils.errors.NoHeadingFoundInMarkdownFileError import NoHeadingFoundInMarkdownFileError + + +class NoHeadingFoundInMarkdownFileErrorTest(SimpleTestCase): + """Test class for NoHeadingFoundInMarkdownFileError error. + + Note: Tests to check if these were raised appropriately + are located where this exception is used. + """ + + def test_attributes(self): + exception = NoHeadingFoundInMarkdownFileError("md file path") + self.assertEqual(exception.md_file_path, "md file path") + + def test_string(self): + exception = NoHeadingFoundInMarkdownFileError("md file path") + expected_string = ( + "\n****************************ERROR****************************\n" + "File: md file path\n\n" + "The file does not contain a heading.\n" + ) + self.assertEqual(exception.__str__(), expected_string) diff --git a/codewof/tests/utils/errors/test_VertoConversionError.py b/codewof/tests/utils/errors/test_VertoConversionError.py new file mode 100644 index 000000000..d17637e12 --- /dev/null +++ b/codewof/tests/utils/errors/test_VertoConversionError.py @@ -0,0 +1,51 @@ +"""Test class for VertoConversionError error.""" + +from django.test import SimpleTestCase +from utils.errors.VertoConversionError import VertoConversionError +from unittest.mock import Mock + + +class VertoConversionErrorTest(SimpleTestCase): + """Test class for VertoConversionError error. + + Note: Tests to check if these were raised appropriately + are located where this exception is used. + """ + + def test_attributes(self): + verto_error = Mock() + verto_error.message = "Message." + verto_error.line_nums = (2, 3, 4) + verto_error.lines = ("", "{panel}", "Panel contents") + exception = VertoConversionError("md file path", verto_error) + self.assertEqual(exception.markdown_path, "md file path") + self.assertEqual(exception.verto_error, verto_error) + + def test_string_without_line_nums(self): + verto_error = Mock() + verto_error.message = "Message." + del verto_error.line_nums + del verto_error.lines + exception = VertoConversionError("md file path", verto_error) + expected_string = ( + "\n****************************ERROR****************************\n" + "File: md file path\n\n" + "Conversion failed with error: \"Message.\"\n" + ) + self.assertEqual(exception.__str__(), expected_string) + + def test_string_with_line_nums(self): + verto_error = Mock() + verto_error.message = "Message." + verto_error.line_nums = (2, 3, 4) + verto_error.lines = ("", "{panel}", "Panel contents") + exception = VertoConversionError("md file path", verto_error) + expected_string = ( + "\n****************************ERROR****************************\n" + "File: md file path\n\n" + "Conversion failed with error: \"Message.\"\n" + "2 \n" + "3 {panel}\n" + "4 Panel contents\n" + ) + self.assertEqual(exception.__str__(), expected_string) From 12ee21dedb2a2690c81c36dd185780daf3b6e932 Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Fri, 12 Jun 2020 10:31:10 +1200 Subject: [PATCH 44/48] Add tests for check_code --- codewof/style/views.py | 8 +- codewof/tests/style/views/test_check_code.py | 99 ++++++++++++++++++-- 2 files changed, 99 insertions(+), 8 deletions(-) diff --git a/codewof/style/views.py b/codewof/style/views.py index c5eccad3d..4f49535f3 100644 --- a/codewof/style/views.py +++ b/codewof/style/views.py @@ -12,6 +12,7 @@ from style.utils import ( render_results_as_html, render_results_as_text, + get_language_slugs, get_language_info, CHARACTER_DESCRIPTIONS, ) @@ -82,14 +83,17 @@ def check_code(request): Returns: JSON response with result. """ + # TODO: Provide message for failure result = { 'success': False, } if request.is_ajax(): request_json = json.loads(request.body.decode('utf-8')) user_code = request_json['user_code'] - if 0 < len(user_code) <= settings.STYLE_CHECKER_MAX_CHARACTER_COUNT: - language = request_json['language'] + language = request_json['language'] + is_valid_length = 0 < len(user_code) <= settings.STYLE_CHECKER_MAX_CHARACTER_COUNT + is_valid_language = language in get_language_slugs() + if is_valid_length and is_valid_language: if language == 'python3': result_data = python3_style_check(user_code) result['success'] = True diff --git a/codewof/tests/style/views/test_check_code.py b/codewof/tests/style/views/test_check_code.py index ea142a5e1..4186409ce 100644 --- a/codewof/tests/style/views/test_check_code.py +++ b/codewof/tests/style/views/test_check_code.py @@ -1,7 +1,10 @@ +import json from http import HTTPStatus from django.test import TestCase, override_settings from django.urls import reverse +SAMPLE_PROGRAM_1 = "print('Hello world!')" + class CheckCodeViewTest(TestCase): @@ -9,21 +12,105 @@ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.language = 'en' + @override_settings(STYLE_CHECKER_LANGUAGES={ + 'python3': { + 'name': 'Python 3', + 'slug': 'python3', + 'svg-icon': 'devicon-python.svg', + 'checker-config': '', + 'example_code': '', + }, + }) def test_check_code_valid(self): - pass + data = { + 'language': 'python3', + 'user_code': SAMPLE_PROGRAM_1, + } + url = reverse('style:check_code') + response = self.client.post( + url, + json.dumps(data), + 'json', + HTTP_X_REQUESTED_WITH='XMLHttpRequest', + ) + self.assertEqual(HTTPStatus.OK, response.status_code) + json_string = response.content + response_data = json.loads(json_string) + self.assertTrue(response_data['success']) def test_check_code_not_ajax(self): - pass + url = reverse('style:check_code') + response = self.client.get(url) + self.assertEqual(HTTPStatus.OK, response.status_code) + json_string = response.content + response_data = json.loads(json_string) + self.assertFalse(response_data['success']) def test_check_code_code_empty(self): - pass + data = { + 'language': 'python3', + 'user_code': '', + } + url = reverse('style:check_code') + response = self.client.post( + url, + json.dumps(data), + 'json', + HTTP_X_REQUESTED_WITH='XMLHttpRequest', + ) + self.assertEqual(HTTPStatus.OK, response.status_code) + json_string = response.content + response_data = json.loads(json_string) + self.assertFalse(response_data['success']) def test_check_code_code_too_long(self): - pass + data = { + 'language': 'python3', + 'user_code': '#' * 100000, + } + url = reverse('style:check_code') + response = self.client.post( + url, + json.dumps(data), + 'json', + HTTP_X_REQUESTED_WITH='XMLHttpRequest', + ) + self.assertEqual(HTTPStatus.OK, response.status_code) + json_string = response.content + response_data = json.loads(json_string) + self.assertFalse(response_data['success']) def test_check_code_code_invalid_language(self): - pass + data = { + 'language': 'not-a-language', + 'user_code': SAMPLE_PROGRAM_1, + } + url = reverse('style:check_code') + response = self.client.post( + url, + json.dumps(data), + 'json', + HTTP_X_REQUESTED_WITH='XMLHttpRequest', + ) + self.assertEqual(HTTPStatus.OK, response.status_code) + json_string = response.content + response_data = json.loads(json_string) + self.assertFalse(response_data['success']) @override_settings(STYLE_CHECKER_LANGUAGES={}) def test_check_code_with_no_languages(self): - pass + data = { + 'language': 'python3', + 'user_code': SAMPLE_PROGRAM_1, + } + url = reverse('style:check_code') + response = self.client.post( + url, + json.dumps(data), + 'json', + HTTP_X_REQUESTED_WITH='XMLHttpRequest', + ) + self.assertEqual(HTTPStatus.OK, response.status_code) + json_string = response.content + response_data = json.loads(json_string) + self.assertFalse(response_data['success']) From c769bf67b5a243e5ed0373a4026ffa43b4244c57 Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Fri, 12 Jun 2020 10:51:34 +1200 Subject: [PATCH 45/48] Delete unused file --- codewof/tests/style/utils.py | 1 - 1 file changed, 1 deletion(-) delete mode 100644 codewof/tests/style/utils.py diff --git a/codewof/tests/style/utils.py b/codewof/tests/style/utils.py deleted file mode 100644 index a6271d9bb..000000000 --- a/codewof/tests/style/utils.py +++ /dev/null @@ -1 +0,0 @@ -EXAMPLE_CODE_1 = """print("Hello world")""" From f6968f06bdddda64e72044df5aa8417e1a2f95fa Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Fri, 12 Jun 2020 11:40:05 +1200 Subject: [PATCH 46/48] Update version number and CHANGELOG --- CHANGELOG.md | 10 ++++++++++ codewof/config/__init__.py | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9ae6891f..857f79098 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 3.0.0 + +Add style checker for beginners. + +- Style checker for beginners is a freely accessible style checker. + - Currently only Python 3 is supported. + - Code is anonymously stored on the website for analysis and then instantly deleted. + - Count of style issues triggered by submitted code are stored, but the code itself is not permanently stored. + - Statistics are issue occurence counts is publically visible. + ## 2.0.0 Adds gamification elements (points and achievements) to the website, including for all previous submissions for each user. diff --git a/codewof/config/__init__.py b/codewof/config/__init__.py index bcfe02ac3..d7e1daa08 100644 --- a/codewof/config/__init__.py +++ b/codewof/config/__init__.py @@ -1,6 +1,6 @@ """Configuration for Django system.""" -__version__ = "2.0.0" +__version__ = "3.0.0" __version_info__ = tuple( [ int(num) if num.isdigit() else num From 9760de5609c17674a5fba74299cf871b13728603 Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Fri, 12 Jun 2020 11:42:08 +1200 Subject: [PATCH 47/48] Update sentence text. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 857f79098..f93b62754 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ Add style checker for beginners. - Currently only Python 3 is supported. - Code is anonymously stored on the website for analysis and then instantly deleted. - Count of style issues triggered by submitted code are stored, but the code itself is not permanently stored. - - Statistics are issue occurence counts is publically visible. + - Statistics of style issue occurence counts are publically visible. ## 2.0.0 From ad0cdf4103c28722475336c9082c0cd9cf269aa8 Mon Sep 17 00:00:00 2001 From: JackMorganNZ Date: Fri, 12 Jun 2020 11:53:37 +1200 Subject: [PATCH 48/48] List dependency changes in CHANGELOG --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f93b62754..12fe99a37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,12 @@ Add style checker for beginners. - Code is anonymously stored on the website for analysis and then instantly deleted. - Count of style issues triggered by submitted code are stored, but the code itself is not permanently stored. - Statistics of style issue occurence counts are publically visible. +- Dependency updates: + - Add django-bootstrap-breadcrumbs 0.9.2. + - Set flake8 to custom version that allows isolated configurations, to be updated to official release in next update. + - Add flake8-docstrings 1.5.0. + - Add flake8-quotes 2.1.1. + - Add pep8-naming 0.9.1. ## 2.0.0