diff --git a/.gitignore b/.gitignore index 6592afb..0e199d4 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,10 @@ dev.db # Vagrant machines .vagrant/** + +*.sqlite + +# Virtual environment +venv/ +__pycache__/ +vscode/ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..84a192c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "python.pythonPath": "venv\\Scripts\\python.exe" +} \ No newline at end of file diff --git a/autoapp.py b/autoapp.py index e3a50c4..05727ac 100755 --- a/autoapp.py +++ b/autoapp.py @@ -1,10 +1,22 @@ # -*- coding: utf-8 -*- """Create an application instance.""" from flask.helpers import get_debug_flag - +from conduit.extensions import db +from flask_migrate import Migrate from conduit.app import create_app from conduit.settings import DevConfig, ProdConfig - CONFIG = DevConfig if get_debug_flag() else ProdConfig +from conduit.articles.views import create_category + +app = create_app(DevConfig) +app_context = app.app_context() +app_context.push() +db.create_all() +migrate = Migrate(app, db) + +<<<<<<< HEAD +======= app = create_app(CONFIG) + +>>>>>>> 609330574307173c44b32a96fb032c867b4e338f diff --git a/conduit/articles/models.py b/conduit/articles/models.py index 69b2267..7455178 100644 --- a/conduit/articles/models.py +++ b/conduit/articles/models.py @@ -17,6 +17,41 @@ db.Column("tag", db.Integer, db.ForeignKey("tags.id")), db.Column("article", db.Integer, db.ForeignKey("article.id"))) +category_assoc = db.Table("category_assoc", + db.Column("category", db.Integer, db.ForeignKey("categories.id")), + db.Column("article", db.Integer, db.ForeignKey("article.id"))) + + +category_tree =db.Table("category_tree", + db.Column("parent_id", db.Integer, db.ForeignKey("categories.id")), + db.Column("children_id", db.Integer, db.ForeignKey("categories.id"))) + + +class Category(Model): + __tablename__ = 'categories' + + id = db.Column(db.Integer, primary_key=True) + catname = db.Column(db.String(100)) + + parents = db.relationship('Category', + secondary = category_tree, + primaryjoin = (category_tree.c.parent_id==id), + secondaryjoin = (category_tree.c.children_id==id), + backref= db.backref('children_categories', lazy='dynamic'), lazy='dynamic' + ) + + def __init__(self, catname): + db.Model.__init__(self, catname=catname) + + def __repr__(self): + return self.catname + + def add_children(self, children): + if children not in self.parents: + self.parents.append(children) + return True + return False + class Tags(Model): __tablename__ = 'tags' @@ -64,6 +99,12 @@ class Article(SurrogatePK, Model): backref='favorites', lazy='dynamic') + categories = relationship( + 'Category', + secondary=category_assoc, + backref='articles', + lazy='dynamic') + tagList = relationship( 'Tags', secondary=tag_assoc, backref='articles') @@ -100,6 +141,18 @@ def remove_tag(self, tag): return True return False + def add_category(self, category): + if category not in self.categories: + self.categories.append(category) + return True + return False + + def remove_tag(self, category): + if category in self.categories: + self.categories.remove(category) + return True + return False + @property def favoritesCount(self): return len(self.favoriters.all()) diff --git a/conduit/articles/serializers.py b/conduit/articles/serializers.py index 99e3f16..fe71300 100644 --- a/conduit/articles/serializers.py +++ b/conduit/articles/serializers.py @@ -1,6 +1,8 @@ # coding: utf-8 -from marshmallow import Schema, fields, pre_load, post_dump +from marshmallow import Schema, fields, pre_load, post_dump, post_load +import json +from .models import Category from conduit.profile.serializers import ProfileSchema @@ -18,6 +20,7 @@ class ArticleSchema(Schema): updatedAt = fields.DateTime(dump_only=True) author = fields.Nested(ProfileSchema) article = fields.Nested('self', exclude=('article',), default=True, load_only=True) + categories = fields.List(fields.Str()) tagList = fields.List(fields.Str()) favoritesCount = fields.Int(dump_only=True) favorited = fields.Bool(dump_only=True) @@ -82,6 +85,34 @@ def make_comment(self, data, many): return {'comments': data} + +class CategorySchema(Schema): + catname = fields.Str() + id = fields.Int() + + + # for the envelope + category = fields.Nested('self', exclude=('category',), default=True, load_only=True) + + @pre_load + def make_category(self, data): + if not data: + return None + return data['category'] + + @post_dump(pass_many=True) + def dump_category(self, data, many): + key = 'categories' if many else 'category' + return { + key : data + } + + + class Meta: + strict = True + +category_schema = CategorySchema() +categories_schema = CategorySchema(many=True) article_schema = ArticleSchema() articles_schema = ArticleSchemas(many=True) comment_schema = CommentSchema() diff --git a/conduit/articles/views.py b/conduit/articles/views.py index 1866ee7..1880dfb 100644 --- a/conduit/articles/views.py +++ b/conduit/articles/views.py @@ -1,17 +1,17 @@ # coding: utf-8 import datetime as dt - -from flask import Blueprint, jsonify +from pprint import pprint +from flask import Blueprint, jsonify, request from flask_apispec import marshal_with, use_kwargs from flask_jwt_extended import current_user, jwt_required, jwt_optional from marshmallow import fields - +from conduit.extensions import db from conduit.exceptions import InvalidUsage from conduit.user.models import User -from .models import Article, Tags, Comment +from .models import Article, Tags, Comment, Category from .serializers import (article_schema, articles_schema, comment_schema, - comments_schema) + comments_schema, categories_schema, category_schema) blueprint = Blueprint('articles', __name__) @@ -40,9 +40,21 @@ def get_articles(tag=None, author=None, favorited=None, limit=20, offset=0): @jwt_required @use_kwargs(article_schema) @marshal_with(article_schema) -def make_article(body, title, description, tagList=None): +def make_article(body, title, description, tagList=None, categories=None): article = Article(title=title, description=description, body=body, author=current_user.profile) + if categories is not None: + for category in categories: + mcategory = Category.query.filter_by(catname=category).first() + if not mcategory: + mcategory = Category(catname=category) + mcategory.save() + article.add_category(mcategory) + else: + mcategory = Category(catname='uncategorized') + mcategory.save() + article.add_category(mcategory) + if tagList is not None: for tag in tagList: mtag = Tags.query.filter_by(tagname=tag).first() @@ -166,3 +178,62 @@ def delete_comment_on_article(slug, cid): comment = article.comments.filter_by(id=cid, author=current_user.profile).first() comment.delete() return '', 200 + +########## +#category +######### + +@blueprint.route('/api/categories', methods=['POST']) +@jwt_required +def create_category(): + json_data = request.get_json() + if not request.json or not 'category' in json_data: + return jsonify({'message': 'field is empty, please cross check', 'status': 404}), 404 + data, errors = category_schema.load(json_data) + category = Category.query.filter_by(catname=data['catname']).first() + if not category: + category_name = Category(catname=data['catname']) + db.session.add(category_name) + db.session.commit() + data, errors = category_schema.dump(category_name) + print(data) + return jsonify(data) ,201 + return jsonify({'message': 'category already exixts'}), 404 + +@blueprint.route('/api/categories/', methods=['DELETE']) +@jwt_required +def remove_category(id): + category = Category.query.filter_by(id=id).first() + if not category: + return jsonify({'message': 'not found', 'status': 404}), 404 + db.session.delete(category) + return jsonify({'category':{'id': id , 'message': 'category has been deleted'}, 'status': 200}), 200 + + +@blueprint.route('/api/categories//category', methods=['PATCH']) +@jwt_required +def edit_category(id): + json_data = request.get_json() + data, errors = category_schema.load(json_data) + category = Category.query.filter_by(id=id).first() + if not category: + return jsonify({'message': 'not found', 'status': 404}) ,404 + else: + category.update(catname=data['catname']) + db.session.add(category) + db.session.commit() + return jsonify({'category': {'id':id, 'message': \ + 'has been updated'}, 'status': 200}), 200 + + +@blueprint.route('/api/categories', methods=['GET']) +@jwt_required +def fetch_all_categories(): + categories = Category.query.all() + if not categories : + return jsonify({'message': ' not found', 'status' : 404}) ,404 + data, errors = categories_schema.dump(categories) + return jsonify(data), 200 + + + diff --git a/conduit/docs/create_category.yml b/conduit/docs/create_category.yml new file mode 100644 index 0000000..5c53842 --- /dev/null +++ b/conduit/docs/create_category.yml @@ -0,0 +1,45 @@ +conduit/docs/create_category.yml + +create category +--- +paths: + /categories: + post: + description: creates new category entity in the database table categories + parameters: + - in: header + name: Authorization + description: authorization header + required: true + schema: + type: string + id: Token + requestBody: + required: true + content: + application/json: + schema: + type: object + parameters: + catname: + type: string + example: Music + + responses: + 201: + description: Category successfully created. + 400: + description: category was not in right format + security: + - JWT: + description: Pass in jwt token. i.e Token + -in: header + name: Authorization + type: apiKey + scheme: bearer + bearerFormat: JWT + tags: + - Category + + + diff --git a/conduit/docs/delete_category.yml b/conduit/docs/delete_category.yml new file mode 100644 index 0000000..4a11ee3 --- /dev/null +++ b/conduit/docs/delete_category.yml @@ -0,0 +1,45 @@ +conduit/docs/delete_category.yml + +delete category +--- +paths: + /categories/{id}: + delete: + description: deletes an existing category entity in the database + parameters: + - in: header + name: Authorization + description: authorization header + required: true + schema: + type: string + id: Token + responses: + 200: + description: Category successfully deleted. + content: + application/json: + schema: + type: object + parameters: + id: + type: integer + example: 2 + message: + type: string + example: category has been deleted + 404: + description: category was not found + security: + - JWT: + description: Pass in jwt token. i.e Token + -in: header + name: Authorization + type: apiKey + scheme: bearer + bearerFormat: JWT + tags: + - Category + + + diff --git a/conduit/docs/edit_category.yml b/conduit/docs/edit_category.yml new file mode 100644 index 0000000..639de79 --- /dev/null +++ b/conduit/docs/edit_category.yml @@ -0,0 +1,55 @@ +conduit/docs/edit_category.yml + +update category +--- +paths: + /categories/{id}: + patch: + description: updates an existing category entity in the database + parameters: + - in: header + name: Authorization + description: authorization header + required: true + schema: + type: string + id: Token + requestBody: + required: true + content: + application/json: + schema: + type: object + parameters: + catname: + type: string + example: Music + responses: + 200: + description: Category successfully updated. + content: + application/json: + schema: + type: object + parameters: + id: + type: integer + example: 2 + message: + type: string + example: category has been updated + 404: + description: category was not found + security: + - JWT: + description: Pass in jwt token. i.e Token + -in: header + name: Authorization + type: apiKey + scheme: bearer + bearerFormat: JWT + tags: + - Category + + + diff --git a/conduit/docs/fetch_categories.yml b/conduit/docs/fetch_categories.yml new file mode 100644 index 0000000..b15edc6 --- /dev/null +++ b/conduit/docs/fetch_categories.yml @@ -0,0 +1,54 @@ +conduit/docs/fetch_categories.yml + +Pull categories +--- +paths: + /categories: + get: + description: gets a list of categories from the database table categories + parameters: + - in: header + name: Authorization + description: authorization header + required: true + schema: + type: string + id: Token + + responses: + 200: + description: successful pull of categories from the database. + content: + application/json: + schema: + type: list + parameters: + id: + type: integer + example: 2 + catname: + type: string + example: Music + 404: + description: categories table empty + content: + application/json: + schema: + type: object + parameters: + message: + type: string + example: not found + security: + - JWT: + description: Pass in jwt token. i.e Token + -in: header + name: Authorization + type: apiKey + scheme: bearer + bearerFormat: JWT + tags: + - Category + + + diff --git a/conduit/profile/models.py b/conduit/profile/models.py index 6f9bbea..269fde1 100644 --- a/conduit/profile/models.py +++ b/conduit/profile/models.py @@ -5,8 +5,8 @@ followers_assoc = db.Table("followers_assoc", - db.Column("follower", db.Integer, db.ForeignKey("userprofile.user_id")), - db.Column("followed_by", db.Integer, db.ForeignKey("userprofile.user_id"))) + db.Column("follower", db.Integer, db.ForeignKey("userprofile.id")), + db.Column("followed_by", db.Integer, db.ForeignKey("userprofile.id"))) class UserProfile(Model, SurrogatePK): diff --git a/conduit/settings.py b/conduit/settings.py index 0210f1d..b5bcac8 100644 --- a/conduit/settings.py +++ b/conduit/settings.py @@ -45,8 +45,8 @@ class DevConfig(Config): DEBUG = True DB_NAME = 'dev.db' # Put the db file in project root - DB_PATH = os.path.join(Config.PROJECT_ROOT, DB_NAME) - SQLALCHEMY_DATABASE_URI = 'sqlite:///{0}'.format(DB_PATH) + # DB_PATH = os.path.join(Config.PROJECT_ROOT, DB_NAME) + SQLALCHEMY_DATABASE_URI = 'postgresql://postgres:david@localhost:5432/realexample_db' CACHE_TYPE = 'simple' # Can be "memcached", "redis", etc. JWT_ACCESS_TOKEN_EXPIRES = timedelta(10 ** 6) @@ -56,6 +56,6 @@ class TestConfig(Config): TESTING = True DEBUG = True - SQLALCHEMY_DATABASE_URI = 'sqlite://' + SQLALCHEMY_DATABASE_URI = 'postgresql://postgres:david@localhost:5432/test_realexample_db' # For faster tests; needs at least 4 to avoid "ValueError: Invalid rounds" BCRYPT_LOG_ROUNDS = 4 diff --git a/conduit/user/serializers.py b/conduit/user/serializers.py index d1912b1..3a5754a 100644 --- a/conduit/user/serializers.py +++ b/conduit/user/serializers.py @@ -1,6 +1,7 @@ # coding: utf-8 from marshmallow import Schema, fields, pre_load, post_dump +import json class UserSchema(Schema): diff --git a/migrations/README b/migrations/README new file mode 100644 index 0000000..98e4f9c --- /dev/null +++ b/migrations/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/migrations/alembic.ini b/migrations/alembic.ini new file mode 100644 index 0000000..f8ed480 --- /dev/null +++ b/migrations/alembic.ini @@ -0,0 +1,45 @@ +# A generic, single database configuration. + +[alembic] +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/migrations/env.py b/migrations/env.py new file mode 100644 index 0000000..169d487 --- /dev/null +++ b/migrations/env.py @@ -0,0 +1,95 @@ +from __future__ import with_statement + +import logging +from logging.config import fileConfig + +from sqlalchemy import engine_from_config +from sqlalchemy import pool + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) +logger = logging.getLogger('alembic.env') + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +from flask import current_app +config.set_main_option('sqlalchemy.url', + current_app.config.get('SQLALCHEMY_DATABASE_URI')) +target_metadata = current_app.extensions['migrate'].db.metadata + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, target_metadata=target_metadata, literal_binds=True + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + + # this callback is used to prevent an auto-migration from being generated + # when there are no changes to the schema + # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html + def process_revision_directives(context, revision, directives): + if getattr(config.cmd_opts, 'autogenerate', False): + script = directives[0] + if script.upgrade_ops.is_empty(): + directives[:] = [] + logger.info('No changes in schema detected.') + + connectable = engine_from_config( + config.get_section(config.config_ini_section), + prefix='sqlalchemy.', + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure( + connection=connection, + target_metadata=target_metadata, + process_revision_directives=process_revision_directives, + **current_app.extensions['migrate'].configure_args + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/migrations/script.py.mako b/migrations/script.py.mako new file mode 100644 index 0000000..2c01563 --- /dev/null +++ b/migrations/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/tests/factories.py b/tests/factories.py index b7f9c34..80a9412 100644 --- a/tests/factories.py +++ b/tests/factories.py @@ -28,3 +28,7 @@ class Meta: """Factory configuration.""" model = User + + + + diff --git a/tests/test_categories.py b/tests/test_categories.py new file mode 100644 index 0000000..a7293a8 --- /dev/null +++ b/tests/test_categories.py @@ -0,0 +1,204 @@ +from flask import url_for +from datetime import datetime + +class TestCategoryViews: + + def test_create_category(self, testapp, user): + user = user.get() + resp = testapp.post_json(url_for('user.login_user'), {'user': { + 'email': user.email, + 'password': 'myprecious' + }}) + + token = str(resp.json['user']['token']) + response = testapp.post_json(url_for('articles.create_category'), { + "category": { + "catname": "Entertainment" + } + }, headers={ + 'Authorization': 'Token {}'.format(token) + }) + assert response.json['category'] + assert response.json['category']['catname'] == 'Entertainment' + + + def test_get_categories(self, testapp, user): + user = user.get() + resp = testapp.post_json(url_for('user.login_user'), {'user': { + 'email': user.email, + 'password': 'myprecious' + }}) + + token = str(resp.json['user']['token']) + categories = ('Football', 'Baseball', 'Cricket') + for category in categories: + resp1 = testapp.post_json(url_for('articles.create_category'), { + "category": { + "catname": category + } + }, headers={ + 'Authorization': 'Token {}'.format(token) + }) + + resp = testapp.get(url_for('articles.fetch_all_categories'), + headers={ + 'Authorization': 'Token {}'.format(token) + } + ) + assert resp.json['categories'] + assert resp.json['categories'][0]['catname'] == 'Football' + assert len(resp.json['categories']) == 3 + + + def test_delete_category(self, testapp, user): + user = user.get() + resp = testapp.post_json(url_for('user.login_user'), {'user': { + 'email': user.email, + 'password': 'myprecious' + }}) + + token = str(resp.json['user']['token']) + resp = testapp.post_json(url_for('articles.create_category'), { + "category": { + "catname": "Entertainment" + } + }, headers={ + 'Authorization': 'Token {}'.format(token) + }) + response = testapp.delete(url_for('articles.remove_category', id=resp.json['category']['id']), + headers={ + 'Authorization': 'Token {}'.format(token) + } + ) + assert response.json['category'] + assert response.json['category']['message'] == 'category has been deleted' + + + def test_edit_category(self, testapp, user): + user = user.get() + resp = testapp.post_json(url_for('user.login_user'), {'user': { + 'email': user.email, + 'password': 'myprecious' + }}) + + token = str(resp.json['user']['token']) + resps = testapp.post_json(url_for('articles.create_category'), { + "category": { + "catname": "Entertainment" + } + }, headers={ + 'Authorization': 'Token {}'.format(token) + }) + response = testapp.patch_json(url_for('articles.edit_category', id=resps.json['category']['id']),{ + "category": { + "catname": "Hockey" + } + }, headers={ + 'Authorization': 'Token {}'.format(token) + } + ) + assert response.json['category'] + assert response.json['category']['id'] == 1 + assert response.json['category']['message'] == 'has been updated' + + + def test_cannot_delete_nonexisting_category(self, testapp, user): + user = user.get() + resp = testapp.post_json(url_for('user.login_user'), {'user': { + 'email': user.email, + 'password': 'myprecious' + }}) + + token = str(resp.json['user']['token']) + response = testapp.delete(url_for('articles.remove_category', id=1), + {"category": { + "catname": "Hockey" + } + }, headers={ + 'Authorization': 'Token {}'.format(token) + }, expect_errors=True) + assert response.json['message'] == 'not found' + assert response.status_int == 404 + + + def test_cannot_create_existing_category_(self, testapp, user): + user = user.get() + resp = testapp.post_json(url_for('user.login_user'), {'user': { + 'email': user.email, + 'password': 'myprecious' + }}) + + token = str(resp.json['user']['token']) + response = testapp.post_json(url_for('articles.create_category'), { + "category": { + "catname": "Entertainment" + } + }, headers={ + 'Authorization': 'Token {}'.format(token) + }) + resps = testapp.post_json(url_for('articles.create_category'), { + "category": { + "catname": "Entertainment" + } + }, headers={ + 'Authorization': 'Token {}'.format(token) + }, expect_errors=True) + assert resps.json['message'] == 'category already exixts' + + + def test_cannot_create_category_without_auth(self, testapp): + response = testapp.post_json(url_for('articles.create_category'), { + "category": { + "catname": "Entertainment" + } + }, headers={ + 'Authorization': 'Token {}'.format("") + }, expect_errors=True) + assert response.status_int == 422 + + def test_make_article_without_categorylist(self, testapp, user): + user = user.get() + resp = testapp.post_json(url_for('user.login_user'), {'user': { + 'email': user.email, + 'password': 'myprecious' + }}) + + token = str(resp.json['user']['token']) + resp = testapp.post_json(url_for('articles.make_article'), { + "article": { + "title": "How to train your dragon", + "description": "Ever wonder how?", + "body": "You have to believe", + "tagList": ["reactjs", "angularjs", "dragons"] + } + }, headers={ + 'Authorization': 'Token {}'.format(token) + }) + print(resp.json) + assert resp.json['article']['categories'] == ['uncategorized'] + + + def test_make_article_with_categorylist(self, testapp, user): + user = user.get() + resp = testapp.post_json(url_for('user.login_user'), {'user': { + 'email': user.email, + 'password': 'myprecious' + }}) + + token = str(resp.json['user']['token']) + resp = testapp.post_json(url_for('articles.make_article'), { + "article": { + "title": "How to train your dragon", + "description": "Ever wonder how?", + "body": "You have to believe", + "tagList": ["reactjs", "angularjs", "dragons"], + "categories": ["Flying animals", "Informative articles"] + } + }, headers={ + 'Authorization': 'Token {}'.format(token) + }) + print(resp.json) + assert resp.json['article']['categories'] == ["Flying animals", "Informative articles"] + + + diff --git a/tests/test_model_category.py b/tests/test_model_category.py new file mode 100644 index 0000000..3472ea8 --- /dev/null +++ b/tests/test_model_category.py @@ -0,0 +1,36 @@ + +import pytest + +from conduit.articles.models import Category + + +@pytest.mark.usefixtures('db') +class TestCategory: + """Category tests.""" + + def test_get_categories(self): + """Get categories.""" + category1 = Category(catname='music') + category2 = Category(catname='sports') + category1.save() + category2.save() + + retrieved = Category.query.all() + assert len(retrieved) == 2 + + def test_append_children_category(self): + """Test creation children category.""" + category = Category(catname='olympic games') + category1 = Category(catname='Tennis') + category.parents.append(category1) + category.save() + assert category.parents + + + def test_create_category(self): + """Test create category.""" + category = Category(catname='Goof') + category.save() + assert category + + diff --git a/tests/test_models.py b/tests/test_models.py index 0db3002..72f0fc9 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -179,3 +179,5 @@ def test_make_comments(self, user): assert comment1.article == article assert comment1.author == user.profile assert len(article.comments.all()) == 2 + +