diff --git a/jobs/app.py b/jobs/app.py index d582a33..39e68d9 100644 --- a/jobs/app.py +++ b/jobs/app.py @@ -1,6 +1,7 @@ import sqlite3 +import datetime -from flask import g, Flask, render_template +from flask import g, Flask, render_template, redirect, url_for app = Flask(__name__) @@ -35,4 +36,27 @@ def jobs(): @app.route('/job/') def job(job_id): job = query_db('SELECT job.id, job.title, job.description, job.salary, employer.id as employer_id, employer.name as employer_name FROM job JOIN employer ON employer.id = job.employer_id WHERE job.id = ?', [job_id], True) - return render_template('job.html', job=job) \ No newline at end of file + return render_template('job.html', job=job) + +@app.route('/employer/') +def employer(employer_id): + employer = query_db('SELECT * FROM employer WHERE id=?', [employer_id], True) + jobs = query_db('SELECT job.id, job.title, job.description, job.salary FROM job JOIN employer ON employer.id = job.employer_id WHERE employer.id = ?', [employer_id]) + reviews = query_db('SELECT review, rating, title, date, status FROM review JOIN employer ON employer.id = review.employer_id WHERE employer.id = ?', [employer_id]) + return render_template('employer.html', employer=employer, jobs=jobs, reviews=reviews) + +@app.route('/employer//review', methods=('GET', 'POST')) +def review(employer_id): + if request.method == 'POST': + review = request.form['review'] + rating = request.form['rating'] + title = request.form['title'] + status = request.form['status'] + date = datetime.datetime.now().strftime("%m/%d/%Y") + + db = get_db() + db.excute('INSERT INTO review (review, rating, title, date, status, employer_id) VALUES (?, ?, ?, ?, ?, ?)', (review, rating, title, date, status, employer_id)) + db.commit() + return redirect(url_for('employer', employer_id=employer_id)) + + return render_template('review.html', employer_id=employer_id) \ No newline at end of file diff --git a/jobs/templates/_macros.html b/jobs/templates/_macros.html index a969e1b..63c49aa 100644 --- a/jobs/templates/_macros.html +++ b/jobs/templates/_macros.html @@ -16,7 +16,11 @@ {% endmacro %} {% macro show_jobs(jobs) %} - {% for job in jobs %} - {{ show_job(job) }} +
+ {% for job in jobs %} +
+ {{ show_job(job) }} +
{% endfor %} +
{% endmacro %} \ No newline at end of file diff --git a/jobs/templates/employer.html b/jobs/templates/employer.html new file mode 100644 index 0000000..b36b904 --- /dev/null +++ b/jobs/templates/employer.html @@ -0,0 +1,40 @@ +{% extends "layout.html" %} + +{% block content %} +
+

{{ employer['name'] }}

+
+

{{ employer['description'] }}

+
+
+

Jobs

+ {{ show_jobs(jobs) }} + +

Reviews

+ {% for review in reviews %} +
+
+
+ {% for _ in range(1, review['rating']): %} + + {% endfor %} +
+
+
+

+ {{ review['title'] }} + {{ review['status'] }} + {{ review['date'] }} + {{ review['review'] }} +

+
+
+
+
+ {% endfor %} +
+ +
+{% endblock %} \ No newline at end of file diff --git a/jobs/templates/job.html b/jobs/templates/job.html index 43af1a3..9c5bd02 100644 --- a/jobs/templates/job.html +++ b/jobs/templates/job.html @@ -1,3 +1,5 @@ {% extends "layout.html" %} -{{ show_job(job) }} \ No newline at end of file +{% block content %} + {{ show_job(job) }} +{% endblock %} \ No newline at end of file diff --git a/jobs/templates/review.html b/jobs/templates/review.html new file mode 100644 index 0000000..febb312 --- /dev/null +++ b/jobs/templates/review.html @@ -0,0 +1,52 @@ +{% extends "layout.html" %} + +{% block content %} +

New Review

+
+
+ +
+ +
+
+
+ +
+
+ +
+
+
+
+ +
+ +
+
+
+ +
+
+ +
+
+
+
+
+ +
+
+ Cancel +
+
+
+{% endblock %} \ No newline at end of file diff --git a/tasks.md b/tasks.md index 6898703..0ad3e52 100644 --- a/tasks.md +++ b/tasks.md @@ -248,7 +248,7 @@ In `

` tag add the following: @pytest.mark.show_jobs_macro_for_loop Still in `_macros.html` and in the body of the `show_jobs` macro add the following HTML: -- Add a `

` with two classes `columns` and `is_multiline`. +- Add a `
` with two classes `columns` and `is-multiline`. - In this `
` add a `for in` loop that loops through all jobs. **Note: Use the `{% %}` template syntax, don’t forget about ending the `for` loop.** ## 4.8 - Show Jobs Macro For Loop Body @@ -303,7 +303,7 @@ At this point you can see all jobs on the homepage: In the file use an `extends` template tag to inherit `layout.html`. -After the `extends` tag add a call to the `show_job` macro passing in `job`. **Note: Use the `{{}}` for the macro call.** +After the `extends` tag add a template `block` called `content`. In the block call the `show_job` macro passing in `job`. **Note: Use the `{{}}` for the macro call.** ## 5.2 - Job Route Function @@ -363,7 +363,7 @@ To the top of the file inherit from the `layout.html` template by using an `exte ## 6.4 - Employer Template Reviews -@pytest.mark.employer_template_reviews Open `employer.html` and find the review `

`, remove the comment surrounding the empty `{% %}` template tag. To this tag add a `for in` loop to loop through all `reviews`. Add the `endfor` directive to the second empty `{% %}` template tag, don't forget to the remove the comment. +@pytest.mark.employer_template_reviews Still in `employer.html` find the review `

`, remove the comment surrounding the empty `{% %}` template tag. To this tag add a `for in` loop to loop through all `reviews`. Add the `endfor` directive to the second empty `{% %}` template tag, don't forget to the remove the comment. ## 6.5 - Employer Template Review Stars @@ -432,7 +432,15 @@ At this point you can see an individual employer: # Module 07 - Employer Reviews -## 7.1 - Review Route +## 7.1 - Review Template + +@pytest.mark.review_template To display a review form, create a new file called `review.html` in the templates folder. Open `templates.html`, find the appropriate block of HTML and copy and paste it to `review.html`. + +Inherit from the `layout.html` template by using an `extends` template tag. + +Find the cancel anchor tag. Add an `href` attribute with a value of `{{ url_for('employer', employer_id=employer_id) }}`. + +## 7.2 - Review Route @pytest.mark.app_review_route In `app.py` below the `employer` function create a new function called `review`. Add `employer_id` to the parameter list. @@ -440,23 +448,22 @@ Add a route decorator with a URL pattern of `/employer//review`. Al In the body of the function return the `render_template` function passing in the `review.html` template and a keyword argument of `employer_id=employer_id`. -## 7.2 - POST Request Check +## 7.3 - POST Request Check -@pytest.mark.app_review_post_request_check In the body of the `review` above the render_template function call, create an `if` statement that checks if `request.method` is `'POST'`. +@pytest.mark.app_review_post_request_check In the body of the `review` above the `render_template` function call, create an `if` statement that checks if `request.method` is equal to `'POST'`. - In the `if` statement create four variables `review`, `rating`, `title`, and `status`. Set them equal to their respective `request.form` values i.e. `request.form['review']`. - Create a `date` variable assign it todays date formatted like '08/10/2018'. **Hint: Use `now()` and `strftime("%m/%d/%Y")`. If you use `now()` add an `import datetime` statement to the top of `app.py`.** -## 7.3 - Insert Review +## 7.4 - Insert Review -@pytest.mark.app_review_insert_review Still in the `review` function below the variables in the `if` statement connect to the database, insert the form values, and commit the changes. Follow these steps: +@pytest.mark.app_review_insert_review Still in the `review` function below the variables in the `if` statement, connect to the database, insert the form values, and commit the changes. Follow these steps: - Assign a `db` variable to a call to `get_db()` -- `execute` the following SQL statement: `'INSERT INTO review (review, rating, title, date, status, employer_id) VALUES (?, ?, ?, ?, ?, ?)'` passing the values: `(review, rating, title, date, status, employer_id)` +- `execute` the following SQL statement on `db`: `'INSERT INTO review (review, rating, title, date, status, employer_id) VALUES (?, ?, ?, ?, ?, ?)'` passing the values: `(review, rating, title, date, status, employer_id)` - `commit` the changes to the database. - Return a redirect taking the user back to the employer page. **Hint: use `redirect()` and `url_for()` (pass a keyword argument of `employer_id=employer_id`) both of which need to be imported from flask.** -## 7.4 - Review Form Cancel - -@pytest.mark.review_form_cancel Open `review.html` and find the cancel anchor tag. Add an `href` attribute with a value of `{{ url_for('employer', employer_id=employer_id) }}`. +## 7.5 - Employer Review Button +@pytest.mark.employer_review_button Open the `employer.html` template and find the anchor tag to create a review. Add an `href` attribute with a value of `{{ url_for('review', employer_id=employer['id']) }}`. diff --git a/tests/test_module4.py b/tests/test_module4.py index 9153521..f00f1c4 100644 --- a/tests/test_module4.py +++ b/tests/test_module4.py @@ -25,15 +25,15 @@ def test_show_job_macro_html_module4(): def test_show_job_macro_header_module4(): assert template_exists('_macros'), 'The `_macros.html` template does not exist in the `templates` folder.' assert 'job:job_id:job:id' in template_functions('_macros', 'url_for'), 'Looks like the job title link `href` is incorrect.' - assert 'job:title' in template_macro_variables('_macros', 'show_job'), 'Looks like the job title link does not have content.' + assert 'job:title' in template_variables('_macros'), 'Looks like the job title link does not have content.' @pytest.mark.show_job_macro_body def test_show_job_macro_body_module4(): assert template_exists('_macros'), 'The `_macros.html` template does not exist in the `templates` folder.' assert 'employer:employer_id:job:employer_id' in template_functions('_macros', 'url_for'), 'Looks like the job title link `href` is incorrect.' - assert 'job:employer_name' in template_macro_variables('_macros', 'show_job'), 'Are you showing the employer name?' - assert 'job:salary' in template_macro_variables('_macros', 'show_job'), 'Are you showing the job salary?' - assert 'job:description' in template_macro_variables('_macros', 'show_job'), 'Are you showing the job description?' + assert 'job:employer_name' in template_variables('_macros'), 'Are you showing the employer name?' + assert 'job:salary' in template_variables('_macros'), 'Are you showing the job salary?' + assert 'job:description' in template_variables('_macros'), 'Are you showing the job description?' @pytest.mark.show_jobs_macro_definition def test_show_jobs_macro_definition_module4(): @@ -43,11 +43,17 @@ def test_show_jobs_macro_definition_module4(): @pytest.mark.show_jobs_macro_for_loop def test_show_jobs_macro_for_loop_module4(): assert template_exists('_macros'), 'The `_macros.html` template does not exist in the `templates` folder.' + html = template_macro_soup('_macros', 'show_jobs') + div = html.select('div.columns.is-multiline') + assert len(div) == 1, 'Has a `
` with classes of `columns` and `is-multiline` been added to the `show_jobs` macro?' assert 'job:jobs' in show_jobs_for(), 'Does the `show_jobs` macro contain a `for` loop?' @pytest.mark.show_jobs_macro_for_loop_body def test_show_jobs_macro_for_loop_body_module4(): assert template_exists('_macros'), 'The `_macros.html` template does not exist in the `templates` folder.' + html = template_macro_soup('_macros', 'show_jobs') + div = html.select('div.column.is-half') + assert len(div) == 1, 'Has a `
` with classes of `column` and `is-half` been added to the `show_jobs` macro `for` loop body?' assert 'show_job:job' in show_jobs_for(), 'Does the `show_jobs` macro call `show_job`?' @pytest.mark.import_macros diff --git a/tests/test_module5.py b/tests/test_module5.py index c43b06d..5e1b5c0 100644 --- a/tests/test_module5.py +++ b/tests/test_module5.py @@ -8,7 +8,9 @@ def test_app_job_template_module5(): assert template_exists('job'), 'The `job.html` template does not exist in the `templates` folder.' assert 'layout.html' in template_extends('job'), 'The `job.html` template does not extend `layout.html`.' + assert 'content' in template_block('job'), 'Have you added a template `block` called `content`?' assert 'show_job:job' in template_functions('job', 'show_job'), 'Have you call the `show_job` macro in the `job.html` file?' + assert False @pytest.mark.app_job_route def test_app_job_route_module5(): diff --git a/tests/test_module6.py b/tests/test_module6.py index 781db9d..249c664 100644 --- a/tests/test_module6.py +++ b/tests/test_module6.py @@ -2,43 +2,72 @@ import sys from jobs import app +from .utils import * @pytest.mark.employer_template def test_employer_template_module6(): - pass + assert template_exists('employer'), 'The `employer.html` template does not exist in the `templates` folder.' + el = template_data('employer').select('.box .media .media-content .content') + assert len(el) == 1, 'Has the `HTML` from `templates.html` been copied to the `employer.html` template?' + assert 'layout.html' in template_extends('employer'), 'The `employer.html` template does not extend `layout.html`.' @pytest.mark.employer_template_details def test_employer_template_details_module6(): - pass + assert template_exists('employer'), 'The `employer.html` template does not exist in the `templates` folder.' + assert 'employer:name' in template_variables('employer'), "Looks like the `employer['name']` is not present in the template." + assert 'employer:description' in template_variables('employer'), "Looks like the `employer['description']` is not present in the template." @pytest.mark.employer_template_all_jobs def test_employer_template_all_jobs_module6(): - pass + assert template_exists('employer'), 'The `employer.html` template does not exist in the `templates` folder.' + assert 'show_jobs:jobs' in template_functions('employer', 'show_jobs'), 'Have you called the `show_jobs` macro in the `employer.html` file?' @pytest.mark.employer_template_reviews def test_employer_template_reviews_module6(): - pass + assert template_exists('employer'), 'The `employer.html` template does not exist in the `templates` folder.' + assert 'review:reviews' in employer_for(), 'Have you created a `for` loop that cycles through `reviews`?' @pytest.mark.employer_template_review_stars def test_employer_template_review_stars_module6(): - pass + assert template_exists('employer'), 'The `employer.html` template does not exist in the `templates` folder.' + assert '_:range:1:review:rating' in employer_for(), 'Have you created a `for` loop that cycles through `reviews`?' + el = template_data('employer').select('.fa.fa_star.checked') + assert len(el) == 1, 'Has the star `` been added to the `employer.html` template?' @pytest.mark.employer_template_review_details def test_employer_template_review_details_module6(): - pass + assert template_exists('employer'), 'The `employer.html` template does not exist in the `templates` folder.' + assert 'review:title' in template_variables('employer'), "Looks like the `review['title']` is not present in the template." + assert 'review:status' in template_variables('employer'), "Looks like the `review['status']` is not present in the template." + assert 'review:date' in template_variables('employer'), "Looks like the `review['date']` is not present in the template." + assert 'review:review' in template_variables('employer'), "Looks like the `review['review']` is not present in the template." @pytest.mark.app_employer_route def test_app_employer_route_module6(): - pass + assert 'employer' in dir(app), 'Have you created the `employer` function?' + assert 'route:/employer/' in get_functions(app.employer) + result = [item for item in get_functions(app.employer) if item.startswith('render_template:employer.html')] + assert len(result) == 1, 'Have you called the `render_template` function.' @pytest.mark.app_employer_route_employers def test_app_employer_route_employers_module6(): - pass + assert 'employer' in dir(app), 'Have you created the `employer` function?' + assert 'employer_id' in inspect.getfullargspec(app.employer).args, 'Have you added the correct parameters to the `employer` function parameter list?' + query_db = 'query_db:SELECT * FROM employer WHERE id=?:employer_id:True' + assert query_db in get_functions(app.employer), '`query_db` has not been called or has the wrong parameters.' + result = [item for item in get_functions(app.employer) if item.startswith('render_template:employer.html:employer:employer')] + assert len(result) == 1, 'Have you added `employer` to the `render_template` call.' @pytest.mark.app_employer_route_jobs def test_app_employer_route_jobs_module6(): - pass + query_db = 'query_db:SELECT job.id, job.title, job.description, job.salary FROM job JOIN employer ON employer.id = job.employer_id WHERE employer.id = ?:employer_id' + assert query_db in get_functions(app.employer), '`query_db` has not been called or has the wrong parameters.' + result = [item for item in get_functions(app.employer) if item.startswith('render_template:employer.html:employer:employer:jobs:jobs')] + assert len(result) == 1, 'Have you added `jobs` to the `render_template` call.' @pytest.mark.app_employer_route_reviews def test_app_employer_route_reviews_module6(): - pass + query_db = 'query_db:SELECT review, rating, title, date, status FROM review JOIN employer ON employer.id = review.employer_id WHERE employer.id = ?:employer_id' + assert query_db in get_functions(app.employer), '`query_db` has not been called or has the wrong parameters.' + result = [item for item in get_functions(app.employer) if item.startswith('render_template:employer.html:employer:employer:jobs:jobs:reviews:reviews')] + assert len(result) == 1, 'Have you added `reviews` to the `render_template` call.' diff --git a/tests/test_module7.py b/tests/test_module7.py index bd83904..d12cfa0 100644 --- a/tests/test_module7.py +++ b/tests/test_module7.py @@ -1,20 +1,89 @@ import pytest import sys +import json from jobs import app +from .utils import * + +@pytest.mark.review_form_cancel +def test_review_template_module7(): + assert template_exists('review'), 'The `review.html` template does not exist in the `templates` folder.' + el = template_data('review').select('.field.is-grouped .control .button.is-text') + assert len(el) == 1, 'Has the `HTML` from `templates.html` been copied to the `review.html` template?' + assert 'layout.html' in template_extends('review'), 'The `review.html` template does not extend `layout.html`.' + assert 'employer:employer_id:employer_id' in template_functions('review', 'url_for'), 'Have you called the `url_for` function in the `review.html` file?' @pytest.mark.app_review_route def test_app_review_route_module7(): - pass + assert 'review' in dir(app), 'Have you created the `review` function?' + assert 'employer_id' in inspect.getfullargspec(app.review).args, 'Have you added the correct parameters to the `review` function parameter list?' + assert "route:/employer//review:methods:[{'s': 'GET'}, {'s': 'POST'}]" or "route:/employer//review:methods:[{'s': 'POST'}, {'s': 'GET'}]" in get_functions(app.review), 'Do you have a route decorator with the correct URL pattern and methods?' + result = [item for item in get_functions(app.review) if item.startswith('render_template:review.html:employer_id:employer_id')] + assert len(result) == 1, 'Have you called the `render_template` function with the correct arguments.' @pytest.mark.app_review_post_request_check def test_app_review_post_request_check_module7(): - pass + assert 'review' in dir(app), 'Have you created the `review` function?' + assert 'datetime' in dir(app), '`datetime` has not been imported.' + + if_statement = get_statements(app.review)[0] + body = if_statement['body'] + post = 'test/comparators/s' in if_statement and 'POST' == if_statement['test/comparators/s'] + method = 'test/left/attr' in if_statement and 'method' == if_statement['test/left/attr'] + request = 'test/left/value/id' in if_statement and 'request' == if_statement['test/left/value/id'] + eq = 'test/ops/node_type' in if_statement and 'Eq' == if_statement['test/ops/node_type'] + review = { + "targets/id": "review", + "value/slice/value/s": "review", + "value/value/attr": "form", + "value/value/value/id": "request" + } + rating = { + "targets/id": "rating", + "value/slice/value/s": "rating", + "value/value/attr": "form", + "value/value/value/id": "request" + } + title = { + "targets/id": "title", + "value/slice/value/s": "title", + "value/value/attr": "form", + "value/value/value/id": "request" + } + status = { + "targets/id": "status", + "value/slice/value/s": "status", + "value/value/attr": "form", + "value/value/value/id": "request" + } + date = { + "targets/id": "date", + "value/args/s": "%m/%d/%Y", + "value/func/attr": "strftime", + "value/func/value/func/attr": "now", + "value/func/value/func/value/attr": "datetime", + "value/func/value/func/value/value/id": "datetime" + } + assert post and method and request and eq, 'Do you have an `if` statement to test if the request method equals "POST?' + assert review in body, 'Have you created the `review` variable?' + assert rating in body, 'Have you created the `rating` variable?' + assert title in body, 'Have you created the `title` variable?' + assert status in body, 'Have you created the `status` variable?' + assert date in body, 'Have you created the `date` variable?' @pytest.mark.app_review_insert_review def test_app_review_insert_review_module7(): - pass + assert 'review' in dir(app), 'Have you created the `review` function?' + assert 'get_db' in get_functions(app.review), '`get_db` has not been called or has the wrong parameters.' + execute = "excute:INSERT INTO review (review, rating, title, date, status, employer_id) VALUES (?, ?, ?, ?, ?, ?):[{'id': 'review'}, {'id': 'rating'}, {'id': 'title'}, {'id': 'date'}, {'id': 'status'}, {'id': 'employer_id'}]" + assert execute in get_functions(app.review), '`db.execute` has not been called or has the wrong parameters.' + assert 'commit' in get_functions(app.review), '`db.commit` has not been called or has the wrong parameters.' + assert 'redirect' in dir(app), '`redirect` has not been imported from flask.' + assert 'url_for' in dir(app), '`url_for` has not been imported from flask.' + assert 'redirect:employer:url_for:employer_id:employer_id' in get_functions(app.review), 'In the `if` are you redirect back to the employer page?' -@pytest.mark.review_form_cancel -def test_review_form_cancel_module7(): - pass + +@pytest.mark.app_review_insert_review +def test_employer_review_button_module7(): + assert template_exists('employer'), 'The `employer.html` template does not exist in the `templates` folder.' + assert 'review:employer_id:employer:id' in template_functions('employer', 'url_for'), 'In the `if` are you redirect back to the employer page?' diff --git a/tests/utils.py b/tests/utils.py index 62ee55d..18398e4 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -56,6 +56,17 @@ def visit_Call(node): node_iter.visit(ast.parse(inspect.getsource(source))) return functions +def get_statements(source): + statements = [] + + def visit_If(node): + statements.append(build_dict(node)) + + node_iter = ast.NodeVisitor() + node_iter.visit_If = visit_If + node_iter.visit(ast.parse(inspect.getsource(source))) + return statements + def build_dict(node): result = {} if node.__class__.__name__ == 'Is' or node.__class__.__name__ == 'Eq': @@ -109,10 +120,12 @@ def template_functions(name, function_name): if isinstance(kwargs.value, nodes.Const): args_string += kwargs.value.value else: - args_string += kwargs.value.node.name - if isinstance(kwargs.value.arg, nodes.Const): - args_string += ':' + kwargs.value.arg.value - print(args_string) + if isinstance(kwargs.value, nodes.Name): + args_string += kwargs.value.name + else: + args_string += kwargs.value.node.name + if isinstance(kwargs.value.arg, nodes.Const): + args_string += ':' + kwargs.value.arg.value functions.append(args_string) return functions @@ -128,12 +141,31 @@ def show_jobs_for(): return values +def employer_for(): + values = [] + + for node in parsed_content('employer').find_all(nodes.For): + path = node.target.name + if isinstance(node.iter, nodes.Name): + path += ':' + node.iter.name + elif isinstance(node.iter, nodes.Call): + path += ':' + node.iter.node.name + ':' + str(node.iter.args[0].value) + ':' + str(node.iter.args[1].node.name) + ':' + str(node.iter.args[1].arg.value) + values.append(path) + + return values + def template_macros(name): macros = [] for macro in parsed_content(name).find_all(nodes.Macro): macros.append(macro.name + ':' + macro.args[0].name) return macros +def template_block(name): + blocks = [] + for block in parsed_content(name).find_all(nodes.Block): + blocks.append(block.name) + return blocks + def template_macro_soup(name, macro_name): for macro in parsed_content(name).find_all(nodes.Macro): if macro.name == macro_name: @@ -148,7 +180,7 @@ def template_data(name): html += node.data return source_soup(html) -def template_macro_variables(name, macro_name): +def template_variables(name): return [item.node.name + ':' + item.arg.value for item in parsed_content(name).find_all(nodes.Getitem)] def template_exists(name):