" + "
Older Posts → " + \
27 | # "
"
28 | #return str_concat
29 |
--------------------------------------------------------------------------------
/blog_admin/templates/dashboard/edit_post.html:
--------------------------------------------------------------------------------
1 | {% extends "dashboard/dashboard.html" %}
2 | {% block content %}
3 |
4 |
5 |
24 |
25 | {% endblock %}
--------------------------------------------------------------------------------
/.env_example:
--------------------------------------------------------------------------------
1 | # dev, test, prod are the options
2 | APP_CONFIG=prod
3 | FLASK_BLOG_PORT=9080
4 | FLASK_ADMIN_PORT=5001
5 | FLASK_HOST=0.0.0.0
6 | # change according to the env
7 | DB_USER=root
8 | # change according to the env
9 | DB_PASSWORD=root123
10 | # change according to the env
11 | #DB_DATABASE_NAME=blog
12 | DB_DRIVER=mysql
13 | # use while on docker
14 | DB_HOST=mysql_db
15 | #Local computer running DB
16 | #DB_HOST=192.168.12.99
17 | #DB_HOST=localhost
18 | DB_NAME=blog
19 | #DB_NAME=test_blog
20 | DB_PORT=3306
21 | #ReCaptcha Config
22 | RECAPTCHA_SITE_KEY=6LfHcysUAAAAALzasd123yxcyxcasase123b
23 | RECAPTCHA_SITE_SECRET=6LfHcysUAAA123cxcr5151asacas1hVuO
24 |
25 | #SMTP Configs
26 | MAIL_USERNAME=example@example.com
27 | MAIL_PASSWORD=1234
28 |
29 | # Caching app config
30 | CACHE_TYPE=simple
31 | CACHE_DEFAULT_TIMEOUT=300
32 | SECRET_KEY=a1123123129a17ea21d12312aysxasdasda12326adfebc12312aycyx932e22312dea
33 | UPLOAD_FOLDER=uploads
34 |
35 | # The setting for admin user/password
36 | ADMIN_USERNAME=admin
37 | PASSWORD=1234
38 | # The first name, accessible for posting
39 | F_NAME=Name
40 | EMAIL=example@example.com
41 | post_init_limit=10
42 |
43 | #Blog content details
44 | blog_header=Name Blog
45 | blog_subheader=Blog on my daily life
46 | social_git=https://example.com
47 | social_linkedin=https://example.com
48 | social_stack=https://example.com
--------------------------------------------------------------------------------
/blog_admin/templates/dashboard/add_post.html:
--------------------------------------------------------------------------------
1 | {% extends "dashboard/dashboard.html" %}
2 | {% block content %}
3 |
4 |
5 |
6 |
7 | Title
8 |
9 |
10 |
11 | Content
12 |
13 |
14 |
15 | Author
16 |
17 |
18 |
19 | Categories
20 |
21 |
22 |
23 | Post It
24 |
25 |
26 |
27 |
28 | {% endblock %}
--------------------------------------------------------------------------------
/blog/static/scss/_global.scss:
--------------------------------------------------------------------------------
1 | // Global styling for this template
2 | body {
3 | font-size: 20px;
4 | color: $gray-900;
5 | @include serif-font;
6 | }
7 |
8 | p {
9 | line-height: 1.5;
10 | margin: 30px 0;
11 | a {
12 | text-decoration: underline;
13 | }
14 | }
15 |
16 | h1,
17 | h2,
18 | h3,
19 | h4,
20 | h5,
21 | h6 {
22 | font-weight: 800;
23 | @include sans-serif-font;
24 | }
25 |
26 | a {
27 | color: $gray-900;
28 | @include transition-all;
29 | &:focus,
30 | &:hover {
31 | color: $primary;
32 | }
33 | }
34 |
35 | blockquote {
36 | font-style: italic;
37 | color: $gray-600;
38 | }
39 |
40 | .section-heading {
41 | font-size: 36px;
42 | font-weight: 700;
43 | margin-top: 60px;
44 | }
45 |
46 | .caption {
47 | font-size: 14px;
48 | font-style: italic;
49 | display: block;
50 | margin: 0;
51 | padding: 10px;
52 | text-align: center;
53 | border-bottom-right-radius: 5px;
54 | border-bottom-left-radius: 5px;
55 | }
56 |
57 | ::-moz-selection {
58 | color: $white;
59 | background: $primary;
60 | text-shadow: none;
61 | }
62 |
63 | ::selection {
64 | color: $white;
65 | background: $primary;
66 | text-shadow: none;
67 | }
68 |
69 | img::selection {
70 | color: $white;
71 | background: transparent;
72 | }
73 |
74 | img::-moz-selection {
75 | color: $white;
76 | background: transparent;
77 | }
78 |
--------------------------------------------------------------------------------
/common/services/tags_service.py:
--------------------------------------------------------------------------------
1 | from common import cache
2 | from common.models import tags_model, posts_model
3 |
4 |
5 | def get_post_tags(tag_name):
6 | if cache.get(tag_name):
7 | print("Retreiving the posts from cache for the tag", tag_name)
8 | posts = cache.get(tag_name)
9 | else:
10 | print("Not yet cached retreiving from db for the tag", tag_name)
11 | tags_db_list = tags_model.Tags.query.filter_by(tag=tag_name).all()
12 | if len(tags_db_list) > 0:
13 | posts = []
14 | for db_tag in tags_db_list:
15 | print(db_tag.posts.title)
16 | print(db_tag.posts.posted_date)
17 | posts.append(db_tag.posts)
18 | cache.set(tag_name, posts, timeout=50)
19 | else:
20 | return False
21 |
22 | return posts
23 |
24 | def get_tags_count():
25 | db_tags_all = tags_model.Tags.query.all()
26 | tags_count = dict()
27 | if db_tags_all:
28 | for db_tag in db_tags_all:
29 | if db_tag.tag not in tags_count:
30 | tags_count[db_tag.tag] = 1
31 | else:
32 | tags_count[db_tag.tag] = tags_count[db_tag.tag] + 1
33 |
34 | # Sort by value of the dictionary and return
35 | tags_count = dict(sorted(tags_count.items(), key=lambda x: x[1], reverse=True))
36 | return tags_count
37 |
--------------------------------------------------------------------------------
/blog_admin/posts/posts_controller.py:
--------------------------------------------------------------------------------
1 | from flask import Blueprint, render_template
2 | from flask_login import login_required, current_user
3 | from common.services import posts_service, comments_service
4 |
5 | posts_bp = Blueprint("posts", __name__)
6 |
7 | # Count the comments
8 | #count_comments = comments_service.CommentService.get_comment_count(is_admin=True)
9 |
10 |
11 | @posts_bp.route("/overview.html")
12 | @login_required
13 | def dash_overview():
14 | return render_template("dashboard/overview.html")
15 |
16 |
17 | @posts_bp.route("/posts.html")
18 | @login_required
19 | def dash_posts():
20 | #load all posts
21 | posts = posts_service.PostService.get_all_posts(order_by=True, is_admin=True)
22 | return render_template("dashboard/posts.html", posts=posts)
23 |
24 |
25 | @posts_bp.route("/add_post.html")
26 | @login_required
27 | def add_post():
28 | return render_template("dashboard/add_post.html")
29 |
30 | @posts_bp.route("/edit_post.html/
")
31 | @login_required
32 | def edit_post(post_title):
33 | post_data = posts_service.PostService.get_post_by_title(post_title, is_admin=True)
34 | if len(post_data) > 0:
35 | data_resp = {"post_data": post_data[0], "tags": posts_service.PostService.serialize_tags(post_data[1])}
36 | else:
37 | data_resp = {"post_data": False, "tags": False}
38 |
39 | return render_template("dashboard/edit_post.html", data_resp=data_resp)
40 |
--------------------------------------------------------------------------------
/instance/config.py:
--------------------------------------------------------------------------------
1 | # Adding secret infos here
2 | from os import environ
3 |
4 | class Config(object):
5 | DEBUG = True
6 | SQLALCHEMY_ECHO = False
7 | SQLALCHEMY_TRACK_MODIFICATIONS = True
8 | CSRF_ENABLED = True
9 | CACHE_TYPE = environ.get("CACHE_TYPE")
10 | CACHE_DEFAULT_TIMEOUT = environ.get("CACHE_DEFAULT_TIMEOUT")
11 | SECRET_KEY = environ.get("SECRET_KEY")
12 | DB_DRIVER = environ.get("DB_DRIVER")
13 | DB_HOST = environ.get("DB_HOST")
14 | DB_PORT = environ.get("DB_PORT")
15 | DB_USER = environ.get("DB_USER")
16 | DB_PASSWORD = environ.get("DB_PASSWORD")
17 | DATABASE_NAME = environ.get("DB_NAME")
18 | MAIL_SERVER = "smtp.gmail.com"
19 | MAIL_PORT = 465
20 | MAIL_USE_SSL = True
21 | MAIL_USERNAME = environ.get("MAIL_USERNAME")
22 | MAIL_PASSWORD = environ.get("MAIL_PASSWORD")
23 | MAIL_DEBUG = False
24 | db_uri = DB_DRIVER + "://" + DB_USER + ":" + DB_PASSWORD + "@" + DB_HOST + ":" + DB_PORT + "/" + DATABASE_NAME
25 |
26 |
27 | class DevelopmentConfig(Config):
28 | SQLALCHEMY_DATABASE_URI = Config.db_uri
29 |
30 |
31 | class TestingConfig(Config):
32 | SQLALCHEMY_DATABASE_URI = Config.db_uri
33 | SQLALCHEMY_ECHO = True
34 |
35 |
36 | class ProductionConfig(Config):
37 | SQLALCHEMY_DATABASE_URI = Config.db_uri
38 | SQLALCHEMY_ECHO = True
39 |
40 |
41 | app_config = {
42 | "dev": DevelopmentConfig,
43 | "test": TestingConfig,
44 | "prod": ProductionConfig
45 | }
--------------------------------------------------------------------------------
/blog/static/scss/_contact.scss:
--------------------------------------------------------------------------------
1 | // Styling for the contact page
2 | .floating-label-form-group {
3 | font-size: 14px;
4 | position: relative;
5 | margin-bottom: 0;
6 | padding-bottom: 0.5em;
7 | border-bottom: 1px solid $gray-300;
8 | input,
9 | textarea {
10 | font-size: 1.5em;
11 | position: relative;
12 | z-index: 1;
13 | padding: 0;
14 | resize: none;
15 | border: none;
16 | border-radius: 0;
17 | background: none;
18 | box-shadow: none !important;
19 | @include serif-font;
20 | &::-webkit-input-placeholder {
21 | color: $gray-600;
22 | @include serif-font;
23 | }
24 | }
25 | label {
26 | font-size: 0.85em;
27 | line-height: 1.764705882em;
28 | position: relative;
29 | z-index: 0;
30 | top: 2em;
31 | display: block;
32 | margin: 0;
33 | -webkit-transition: top 0.3s ease, opacity 0.3s ease;
34 | -moz-transition: top 0.3s ease, opacity 0.3s ease;
35 | -ms-transition: top 0.3s ease, opacity 0.3s ease;
36 | transition: top 0.3s ease, opacity 0.3s ease;
37 | opacity: 0;
38 | }
39 | .help-block {
40 | margin: 15px 0;
41 | }
42 | }
43 |
44 | .floating-label-form-group-with-value {
45 | label {
46 | top: 0;
47 | opacity: 1;
48 | }
49 | }
50 |
51 | .floating-label-form-group-with-focus {
52 | label {
53 | color: $primary;
54 | }
55 | }
56 | form .form-group:first-child .floating-label-form-group {
57 | border-top: 1px solid $gray-300;
58 | }
59 |
--------------------------------------------------------------------------------
/.env_template:
--------------------------------------------------------------------------------
1 | # dev, test, prod are the options
2 | APP_CONFIG=dev
3 | FLASK_BLOG_PORT=9090
4 | FLASK_ADMIN_PORT=5005
5 | FLASK_HOST=0.0.0.0
6 | # change according to the env
7 | DB_USER=root
8 | # change according to the env
9 | DB_PASSWORD=strongpassword
10 | # change according to the env
11 | DB_DRIVER=mysql
12 | # use while on docker. Cannot change the container name
13 | DB_HOST=mysql_db
14 | #Local computer running DB (If running db separetly)
15 | #DB_HOST=localhost
16 | # Database name of your wish
17 | DB_NAME=yourblogdb
18 | # Change to test db when working with testing
19 | #DB_NAME=test_blog
20 | # Default mysql port specification
21 | DB_PORT=3306
22 |
23 | #ReCaptcha Config
24 | RECAPTCHA_SITE_KEY=YOURRECAPTCHA_SITE_KEY
25 | RECAPTCHA_SITE_SECRET=YOURRECAPTCHA_SECRET
26 |
27 | #SMTP Configs (Email and password of the blog email generated)
28 | MAIL_USERNAME=admin@xyz.com
29 | MAIL_PASSWORD=verystrongpassword
30 |
31 | # Caching app config. (Leave it default for basic caching)
32 | CACHE_TYPE=simple
33 | CACHE_DEFAULT_TIMEOUT=300
34 |
35 | # Upload of image folder
36 | SECRET_KEY=yoursecret
37 | UPLOAD_FOLDER=uploads
38 |
39 | # The setting for admin user/password. This will be anyway changed
40 | ADMIN_USERNAME=youradminusername
41 | PASSWORD=useradminpassword
42 | # The first name, accessible for posting
43 | F_NAME=AuthorFirstName
44 | EMAIL=AuthorEmailId
45 |
46 | #Default post limit leave it to 10
47 | post_init_limit=10
48 |
49 | #Blog content details
50 | blog_header=CHANGETHEBLOGTITLE
51 | blog_subheader=CHANGETHEBLOGSUBTITLE
52 | social_git=LINK1
53 | social_linkedin=LINK2
54 | social_stack=LINK3
55 |
--------------------------------------------------------------------------------
/common/services/utility.py:
--------------------------------------------------------------------------------
1 | from os import environ
2 | from common.models import comments_model
3 | from flask_mail import Message
4 | from common import mail
5 |
6 | def check_reply(comment, post_obj):
7 | refer_names = list()
8 | words_list = comment.split(" ")
9 | for word in words_list:
10 | if len(word) >= 3 and "@" in word[0] and ":" in word[len(word) - 1]:
11 | refer_names.append(word.replace("@", "").replace(":",""))
12 | if len(refer_names) > 0:
13 | for name in refer_names:
14 | com_db_obj = comments_model.Comments.query.filter(comments_model.Comments.author_name.like("%" + name + "%"), comments_model.Comments.post_id==post_obj.p_id).first()
15 | if com_db_obj.author_name:
16 | return (com_db_obj.author_name, com_db_obj.author_email), True
17 | return None, False
18 |
19 |
20 | def send_email(author_comment, author_name, c_name_tuple, post_db_obj):
21 | try:
22 | msg = Message('New message from Blog',
23 | sender = environ.get("MAIL_USERNAME"),
24 | recipients = [c_name_tuple[1]])
25 | msg_body = " Hello," + c_name_tuple[0] + "There has been a reply to your comment to " + post_db_obj.title + ". "
26 | msg_body += ""
27 | msg_body += "
" + "
Comment: " + author_comment + "
" + "
Author: " + author_name + "
" + "
"
28 | msg.html = msg_body
29 | mail.send(msg)
30 | return True
31 | except (UnicodeError, TypeError, AttributeError):
32 | return False
33 |
--------------------------------------------------------------------------------
/blog/static/js/clean-blog.js:
--------------------------------------------------------------------------------
1 | (function($) {
2 | "use strict"; // Start of use strict
3 |
4 | // Floating label headings for the contact form
5 | $("body").on("input propertychange", ".floating-label-form-group", function(e) {
6 | $(this).toggleClass("floating-label-form-group-with-value", !!$(e.target).val());
7 | }).on("focus", ".floating-label-form-group", function() {
8 | $(this).addClass("floating-label-form-group-with-focus");
9 | }).on("blur", ".floating-label-form-group", function() {
10 | $(this).removeClass("floating-label-form-group-with-focus");
11 | });
12 |
13 | // Show the navbar when the page is scrolled up
14 | var MQL = 992;
15 |
16 | //primary navigation slide-in effect
17 | if ($(window).width() > MQL) {
18 | var headerHeight = $('#mainNav').height();
19 | $(window).on('scroll', {
20 | previousTop: 0
21 | },
22 | function() {
23 | var currentTop = $(window).scrollTop();
24 | //check if user is scrolling up
25 | if (currentTop < this.previousTop) {
26 | //if scrolling up...
27 | if (currentTop > 0 && $('#mainNav').hasClass('is-fixed')) {
28 | $('#mainNav').addClass('is-visible');
29 | } else {
30 | $('#mainNav').removeClass('is-visible is-fixed');
31 | }
32 | } else if (currentTop > this.previousTop) {
33 | //if scrolling down...
34 | $('#mainNav').removeClass('is-visible');
35 | if (currentTop > headerHeight && !$('#mainNav').hasClass('is-fixed')) $('#mainNav').addClass('is-fixed');
36 | }
37 | this.previousTop = currentTop;
38 | });
39 | }
40 |
41 | })(jQuery); // End of use strict
42 |
--------------------------------------------------------------------------------
/blog/static/scss/_mixins.scss:
--------------------------------------------------------------------------------
1 | // Mixins
2 | // Bootstrap Button Variant
3 | @mixin button-variant($color, $background, $border) {
4 | color: $color;
5 | border-color: $border;
6 | background-color: $background;
7 | &.focus,
8 | &:focus {
9 | color: $color;
10 | border-color: darken($border, 25%);
11 | background-color: darken($background, 10%);
12 | }
13 | &:hover {
14 | color: $color;
15 | border-color: darken($border, 12%);
16 | background-color: darken($background, 10%);
17 | }
18 | &.active,
19 | &:active,
20 | .open > &.dropdown-toggle {
21 | color: $color;
22 | border-color: darken($border, 12%);
23 | background-color: darken($background, 10%);
24 | &.focus,
25 | &:focus,
26 | &:hover {
27 | color: $color;
28 | border-color: darken($border, 25%);
29 | background-color: darken($background, 17%);
30 | }
31 | }
32 | &.active,
33 | &:active,
34 | .open > &.dropdown-toggle {
35 | background-image: none;
36 | }
37 | &.disabled,
38 | &[disabled],
39 | fieldset[disabled] & {
40 | &.focus,
41 | &:focus,
42 | &:hover {
43 | border-color: $border;
44 | background-color: $background;
45 | }
46 | }
47 | .badge {
48 | color: $background;
49 | background-color: $color;
50 | }
51 | }
52 | @mixin transition-all() {
53 | -webkit-transition: all 0.2s;
54 | -moz-transition: all 0.2s;
55 | transition: all 0.2s;
56 | }
57 | @mixin background-cover() {
58 | -webkit-background-size: cover;
59 | -moz-background-size: cover;
60 | -o-background-size: cover;
61 | background-size: cover;
62 | }
63 | @mixin serif-font() {
64 | font-family: 'Lora', 'Times New Roman', serif;
65 | }
66 | @mixin sans-serif-font() {
67 | font-family: 'Open Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
68 | }
69 |
--------------------------------------------------------------------------------
/blog/static/scss/_masthead.scss:
--------------------------------------------------------------------------------
1 | // Styling for the masthead
2 | header.masthead {
3 | // TIP: Background images are set within the HTML using inline CSS!
4 | margin-bottom: 50px;
5 | background: no-repeat center center;
6 | background-color: $gray-600;
7 | background-attachment: scroll;
8 | position: relative;
9 | @include background-cover;
10 | .overlay {
11 | position: absolute;
12 | top: 0;
13 | left: 0;
14 | height: 100%;
15 | width: 100%;
16 | background-color: $gray-900;
17 | opacity: 0.5;
18 | }
19 | .page-heading,
20 | .post-heading,
21 | .site-heading {
22 | padding: 200px 0 150px;
23 | color: white;
24 | @media only screen and (min-width: 768px) {
25 | padding: 200px 0;
26 | }
27 | }
28 | .page-heading,
29 | .site-heading {
30 | text-align: center;
31 | h1 {
32 | font-size: 50px;
33 | margin-top: 0;
34 | }
35 | .subheading {
36 | font-size: 24px;
37 | font-weight: 300;
38 | line-height: 1.1;
39 | display: block;
40 | margin: 10px 0 0;
41 | @include sans-serif-font;
42 | }
43 | @media only screen and (min-width: 768px) {
44 | h1 {
45 | font-size: 80px;
46 | }
47 | }
48 | }
49 | .post-heading {
50 | h1 {
51 | font-size: 35px;
52 | }
53 | .meta,
54 | .subheading {
55 | line-height: 1.1;
56 | display: block;
57 | }
58 | .subheading {
59 | font-size: 24px;
60 | font-weight: 600;
61 | margin: 10px 0 30px;
62 | @include sans-serif-font;
63 | }
64 | .meta {
65 | font-size: 20px;
66 | font-weight: 300;
67 | font-style: italic;
68 | @include serif-font;
69 | a {
70 | color: $white;
71 | }
72 | }
73 | @media only screen and (min-width: 768px) {
74 | h1 {
75 | font-size: 55px;
76 | }
77 | .subheading {
78 | font-size: 30px;
79 | }
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/blog/templates/blog/index.html:
--------------------------------------------------------------------------------
1 | {% extends "blog/base.html" %}
2 | {% block content %}
3 |
4 |
5 |
6 |
7 |
8 |
9 |
{{resp.blog_header}}
10 | {{resp.blog_subheader}}
11 |
12 |
13 |
14 |
15 |
16 |
27 |
28 |
29 |
30 |
31 |
32 | {% if posts_data %}
33 | {% for post in posts_data %}
34 |
35 |
36 |
37 | {{ post.title }}
38 |
39 |
40 |
Posted by
41 | {{ post.author }}
42 | on {{post.posted_date.strftime('%B %d, %Y')}}
43 |
44 |
45 |
46 | {% endfor %}
47 | {% else %}
48 |
No posts to show yet.
49 | {% endif %}
50 |
51 |
52 |
53 | {% if post_len|int >= prev_limit|int %}
54 |
55 | Older Posts →
56 |
57 |
58 | {% endif %}
59 |
60 |
61 |
62 | {% endblock %}
--------------------------------------------------------------------------------
/blog_admin/static/js/comments_app.js:
--------------------------------------------------------------------------------
1 | function approve_comment(comment_status, comment_ref_id) {
2 | var data = {
3 | "comment_status": comment_status,
4 | "comment_ref_id": comment_ref_id
5 | };
6 |
7 | var csrftoken = $('meta[name=csrf-token]').attr('content');
8 | $.ajax({
9 | url: "/api/comment",
10 | cache: false,
11 | contentType: "application/json",
12 | processData: false,
13 | data: JSON.stringify(data),
14 | type: "PUT",
15 | beforeSend: function (xhr, settings) {
16 | if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
17 | xhr.setRequestHeader("X-CSRFToken", csrftoken)
18 | }
19 |
20 | if (comment_status === "" || comment_ref_id === "") {
21 | $('' + "Empty payload, aborting request!!" + " ").prependTo(".commentlog");
22 | $(".toast").toast("show");
23 | this.abort();
24 | }
25 |
26 | },
27 | success: function (response) {
28 | if (response.resp){
29 | $('' + response.message + ' ').prependTo(".commentlog");
30 | $(".toast").toast("show");
31 | $("#" + comment_ref_id).remove();
32 | temp_val = $("#comment-badge").text();
33 | new_val = Integer.parseInt(temp_val) - 1;
34 | if (new_val > 0){
35 | $("#comment-badge").text(new_val);
36 | }
37 | else{
38 | $("a.notification").remove();
39 | }
40 |
41 | }
42 | else{
43 | $('' + response.message + ' ').prependTo(".commentlog");
44 | $(".toast").toast("show");
45 | }
46 |
47 | },
48 | error: function (response) {
49 | console.log(data);
50 | $('' + response.message + ' ').prependTo(".commentlog");
51 | $(".toast").toast("show");
52 | }
53 | });
54 |
55 | }
56 |
57 | //Export functions to be imported in main wrapper
58 | export { approve_comment };
--------------------------------------------------------------------------------
/tests/blog/unit/test_blog_post_service.py:
--------------------------------------------------------------------------------
1 | from os import environ
2 | import datetime
3 | import pytest
4 | from flask import url_for
5 | from common.models import posts_model
6 | from dotenv import load_dotenv, find_dotenv
7 | from blog import create_app
8 | from blog.posts.service import posts_blog_service
9 |
10 |
11 | @pytest.fixture(scope='session', autouse=True)
12 | def load_env():
13 | load_dotenv(find_dotenv())
14 |
15 | @pytest.fixture(scope="module")
16 | def test_client():
17 | app_config = environ.get("APP_CONFIG")
18 | test_app = create_app(app_config)
19 | with test_app.test_client() as testing_client:
20 | yield testing_client
21 |
22 | @pytest.fixture(scope="module")
23 | def test_context():
24 | app_config = environ.get("APP_CONFIG")
25 | test_app = create_app(app_config)
26 | with test_app.app_context() as testing_context:
27 | yield testing_context
28 |
29 | def test_serialize(test_client):
30 | post_db_obj = posts_model.Posts(content="test content",
31 | posted_date = datetime.datetime.now(),
32 | title = "test title",
33 | author = "test author"
34 | )
35 | serialized_obj = posts_blog_service.serialize(post_db_obj)
36 | test_stub = {
37 | "content": "test content",
38 | "posted_date": datetime.datetime.now().strftime('%B %d, %Y'),
39 | "title": "test title",
40 | "author": "test author"
41 | }
42 |
43 | assert test_stub == serialized_obj
44 |
45 | def test_get_posts_html_resp(test_context):
46 | test_stub_items = list()
47 | test_stub = {
48 | "content": "test content",
49 | "posted_date": datetime.datetime.now().strftime('%B %d, %Y'),
50 | "title": "test title",
51 | "author": "test author"
52 | }
53 | test_stub_items.append(test_stub)
54 | print(test_stub)
55 | test_html_str = "" + "" + test_stub_items[0]["title"] + \
56 | " " + "Posted by " + "" + \
57 | test_stub_items[0]["author"] + " on " + test_stub_items[0]["posted_date"] + "