├── app
├── templates
│ ├── auth
│ │ ├── home.html
│ │ └── login.html
│ ├── core
│ │ ├── home.html
│ │ ├── view_contact.html
│ │ ├── create_organisation.html
│ │ ├── create_contact.html
│ │ └── view_organisation.html
│ └── macros.html
├── extensions.py
├── auth
│ ├── __init__.py
│ ├── controller.py
│ ├── forms.py
│ └── models.py
├── core
│ ├── __init__.py
│ ├── forms.py
│ ├── controller.py
│ └── models.py
├── database
│ └── __init__.py
└── __init__.py
├── .travis.yml
├── run.py
├── README.md
├── requirements.txt
├── config.py
├── .gitignore
├── manage.py
└── tests
├── test_auth.py
└── test_core.py
/app/templates/auth/home.html:
--------------------------------------------------------------------------------
1 |
Home Page
--------------------------------------------------------------------------------
/app/templates/core/home.html:
--------------------------------------------------------------------------------
1 | Home Page
--------------------------------------------------------------------------------
/app/extensions.py:
--------------------------------------------------------------------------------
1 | from flask_bcrypt import Bcrypt
2 | bcrypt = Bcrypt()
3 |
4 | from flask_login import LoginManager
5 | login_manager = LoginManager()
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | - "2.7"
4 | # command to run tests
5 | install:
6 | - pip install -r requirements.txt
7 | script: nosetests
--------------------------------------------------------------------------------
/run.py:
--------------------------------------------------------------------------------
1 | # Launch dev server
2 | from app import create_app
3 |
4 | application = create_app()
5 | application.run(host='0.0.0.0', port=8080, debug=True)
6 |
--------------------------------------------------------------------------------
/app/auth/__init__.py:
--------------------------------------------------------------------------------
1 | from flask import Blueprint
2 |
3 | auth = Blueprint('auth', __name__, template_folder='templates/auth')
4 |
5 | from .models import User
6 | from . import controller
--------------------------------------------------------------------------------
/app/templates/core/view_contact.html:
--------------------------------------------------------------------------------
1 | {% from 'macros.html' import display_field %}
2 | {% for column in columns %}
3 | {{ display_field(column, record) }}
4 | {% endfor %}
--------------------------------------------------------------------------------
/app/core/__init__.py:
--------------------------------------------------------------------------------
1 | from flask import Blueprint
2 |
3 | from models import Base, Contact, Organisation
4 |
5 | core = Blueprint('core', __name__, template_folder='templates/core')
6 |
7 | from . import controller
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.org/cghall/EasyCRM)
2 | #EasyCRM#
3 | An open source Customer Relationship Management system powered by Flask and SQLAlchemy.
4 |
5 | **Created by [Chris Hall](www.chrishall.io)**
--------------------------------------------------------------------------------
/app/templates/auth/login.html:
--------------------------------------------------------------------------------
1 | Login
2 |
3 | {% from 'macros.html' import render_field %}
4 |
--------------------------------------------------------------------------------
/app/templates/core/create_organisation.html:
--------------------------------------------------------------------------------
1 | {% from 'macros.html' import render_field %}
2 | Create New Organisation
3 |
--------------------------------------------------------------------------------
/app/database/__init__.py:
--------------------------------------------------------------------------------
1 | from flask_sqlalchemy import SQLAlchemy
2 |
3 | db = SQLAlchemy()
4 |
5 | from app.auth import User
6 |
7 | def populate_db():
8 | """
9 | Adds fake data to the database.
10 | """
11 | admin = User(username='test@gmail.com', password='shh', first_name='chris', last_name='hall')
12 | db.session.add(admin)
13 | db.session.commit()
--------------------------------------------------------------------------------
/app/templates/macros.html:
--------------------------------------------------------------------------------
1 | {% macro render_field(field) %}
2 | {{ field.label }}
3 | {{ field(**kwargs)|safe }}
4 | {% if field.errors %}
5 |
6 | {% for error in field.errors %}
7 | - {{ error }}
8 | {% endfor %}
9 |
10 | {% endif %}
11 |
12 | {% endmacro %}
13 |
14 | {% macro display_field(field, record) %}
15 | {{ field }} {{ record[field] }}
16 | {% endmacro %}
--------------------------------------------------------------------------------
/app/templates/core/create_contact.html:
--------------------------------------------------------------------------------
1 | {% from 'macros.html' import render_field %}
2 | Create New Contact
3 |
--------------------------------------------------------------------------------
/app/core/forms.py:
--------------------------------------------------------------------------------
1 | from wtforms_alchemy import ModelForm
2 | from wtforms.ext.sqlalchemy.fields import QuerySelectField
3 |
4 | from app.core.models import Contact, Organisation
5 |
6 |
7 | def available_organisations():
8 | return Organisation.query.all()
9 |
10 |
11 | class CreateOrganisation(ModelForm):
12 | class Meta:
13 | model = Organisation
14 |
15 |
16 | class CreateContact(ModelForm):
17 | class Meta:
18 | model = Contact
19 |
20 | org_id = QuerySelectField('Organisation', query_factory=available_organisations, get_label='name')
21 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | bcrypt==2.0.0
2 | behave==1.2.5
3 | cffi==1.5.2
4 | decorator==4.0.9
5 | enum34==1.1.2
6 | Flask==0.10.1
7 | Flask-Bcrypt==0.7.1
8 | Flask-Login==0.3.2
9 | Flask-SQLAlchemy==2.1
10 | Flask-Testing==0.4.2
11 | Flask-WTF==0.12
12 | infinity==1.3
13 | intervals==0.6.0
14 | itsdangerous==0.24
15 | Jinja2==2.8
16 | MarkupSafe==0.23
17 | nose==1.3.7
18 | parse==1.6.6
19 | parse-type==0.3.4
20 | pycparser==2.14
21 | six==1.10.0
22 | SQLAlchemy==1.0.11
23 | SQLAlchemy-Utils==0.31.6
24 | validators==0.10
25 | Werkzeug==0.11.3
26 | wheel==0.24.0
27 | WTForms==2.1
28 | WTForms-Alchemy==0.15.0
29 | WTForms-Components==0.10.0
30 |
--------------------------------------------------------------------------------
/app/templates/core/view_organisation.html:
--------------------------------------------------------------------------------
1 | {% from 'macros.html' import display_field %}
2 |
3 | Organisation: {{ organisation.name }}
4 |
5 | {{ display_field('name', organisation) }}
6 | {{ display_field('type', organisation) }}
7 | {{ display_field('address', organisation) }}
8 |
9 | Contacts
10 |
11 |
12 | | Name |
13 | Email |
14 | Role |
15 |
16 | {% for contact in organisation.contacts %}
17 |
18 | | {{ contact.first_name }} {{ contact.last_name }} |
19 | {{ contact.email }} |
20 | {{ contact.role }} |
21 |
22 | {% endfor %}
23 |
--------------------------------------------------------------------------------
/app/__init__.py:
--------------------------------------------------------------------------------
1 | from flask import Flask
2 |
3 | from config import BaseConfig
4 | from app.extensions import bcrypt, login_manager
5 | from app.database import db
6 | from app.core import core as core_blueprint
7 | from app.auth import auth as auth_blueprint
8 |
9 |
10 | def create_app(config=BaseConfig):
11 | app = Flask(__name__)
12 | app.config.from_object(config)
13 |
14 | register_extensions(app)
15 | register_blueprints(app)
16 |
17 | return app
18 |
19 |
20 | def register_extensions(app):
21 | bcrypt.init_app(app)
22 | login_manager.init_app(app)
23 | db.init_app(app)
24 |
25 |
26 | def register_blueprints(app):
27 | app.register_blueprint(auth_blueprint)
28 | app.register_blueprint(core_blueprint)
29 |
--------------------------------------------------------------------------------
/app/auth/controller.py:
--------------------------------------------------------------------------------
1 | from flask import request, render_template, redirect, url_for
2 | from flask_login import login_user
3 |
4 | from app.auth.forms import LoginForm
5 | from app.auth.models import User
6 | from app.extensions import login_manager
7 | from . import auth
8 | from app.database import db
9 |
10 |
11 | @login_manager.user_loader
12 | def load_user(user_id):
13 | return User.query.filter_by(username=user_id).first()
14 |
15 |
16 | @auth.route('/login/', methods=['GET', 'POST'])
17 | def login():
18 | form = LoginForm(request.form)
19 | if form.validate_on_submit():
20 | form.user.authenticated = True
21 | db.session.add(form.user)
22 | db.session.commit()
23 | login_user(form.user)
24 | return redirect(url_for('core.home'))
25 | return render_template('auth/login.html', form=form)
26 |
27 |
--------------------------------------------------------------------------------
/app/auth/forms.py:
--------------------------------------------------------------------------------
1 | from flask_wtf import Form
2 | from wtforms import StringField, PasswordField
3 | from wtforms.validators import DataRequired, ValidationError
4 | from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound
5 |
6 | from .models import User
7 |
8 |
9 | class LoginForm(Form):
10 | username = StringField('Username', [DataRequired(message='Enter your Username')])
11 | password = PasswordField('Password', [DataRequired(message='Enter your Password')])
12 |
13 | def validate_password(form, field):
14 | try:
15 | user = User.query.filter(User.username == form.username.data).one()
16 | except (MultipleResultsFound, NoResultFound):
17 | raise ValidationError("Invalid user")
18 | if user is None:
19 | raise ValidationError("Invalid user")
20 | if not user.is_correct_password(form.password.data):
21 | raise ValidationError("Invalid password")
22 | form.user = user
23 | return True
--------------------------------------------------------------------------------
/config.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 |
4 | class BaseConfig(object):
5 | """Standard configuration options"""
6 | DEBUG = True
7 | BASE_DIR = os.path.abspath(os.path.dirname(__file__))
8 | SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(BASE_DIR, 'app.db')
9 | SQLALCHEMY_MIGRATE_REPO = os.path.join(BASE_DIR, 'db_repository')
10 | WTF_CSRF_ENABLED = False
11 | DATABASE_CONNECT_OPTIONS = {}
12 | THREADS_PER_PAGE = 2
13 | SECRET_KEY = "secret"
14 | BCRYPT_LOG_ROUNDS = 12
15 |
16 |
17 | class TestConfig(BaseConfig):
18 | """Configuration for general testing"""
19 | TESTING = True
20 | BASE_DIR = os.path.abspath(os.path.dirname(__file__))
21 | SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(BASE_DIR, 'test.db')
22 | SQLALCHEMY_TRACK_MODIFICATIONS = False
23 | WTF_CSRF_ENABLED = False
24 | LOGIN_DISABLED = True
25 | BCRYPT_LOG_ROUNDS = 4
26 |
27 |
28 | class AuthTestConfig(TestConfig):
29 | """For testing authentication we want to require login to check validation works"""
30 | LOGIN_DISABLED = False
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Database files
2 | *.db
3 |
4 | # Byte-compiled / optimized / DLL files
5 | __pycache__/
6 | *.py[cod]
7 | *$py.class
8 | *.pyc
9 |
10 | # C extensions
11 | *.so
12 |
13 | # Distribution / packaging
14 | .Python
15 | env/
16 | build/
17 | develop-eggs/
18 | dist/
19 | downloads/
20 | eggs/
21 | .eggs/
22 | lib/
23 | lib64/
24 | parts/
25 | sdist/
26 | var/
27 | *.egg-info/
28 | .installed.cfg
29 | *.egg
30 |
31 | # PyInstaller
32 | # Usually these files are written by a python script from a template
33 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
34 | *.manifest
35 | *.spec
36 |
37 | # Installer logs
38 | pip-log.txt
39 | pip-delete-this-directory.txt
40 |
41 | # Unit test / coverage reports
42 | htmlcov/
43 | .tox/
44 | .coverage
45 | .coverage.*
46 | .cache
47 | nosetests.xml
48 | coverage.xml
49 | *,cover
50 | .hypothesis/
51 |
52 | # Translations
53 | *.mo
54 | *.pot
55 |
56 | # Django stuff:
57 | *.log
58 |
59 | # Sphinx documentation
60 | docs/_build/
61 |
62 | # PyBuilder
63 | target/
64 |
65 | #Ipython Notebook
66 | .ipynb_checkpoints
67 |
--------------------------------------------------------------------------------
/manage.py:
--------------------------------------------------------------------------------
1 | from app import create_app
2 | from app.database import db, populate_db
3 | from flask_migrate import Migrate, MigrateCommand
4 | from flask_script import (
5 | Server,
6 | Shell,
7 | Manager,
8 | prompt_bool,
9 | )
10 |
11 |
12 | def _make_context():
13 | return dict(
14 | app=create_app(),
15 | db=db,
16 | populate_db=populate_db
17 | )
18 |
19 | app = create_app()
20 |
21 | migrate = Migrate(app, db)
22 |
23 | manager = Manager(app)
24 | manager.add_command('runserver', Server())
25 | manager.add_command('shell', Shell(make_context=_make_context))
26 | manager.add_command('db', MigrateCommand)
27 |
28 |
29 | @manager.command
30 | def create_db(num_users=5):
31 | """Creates database tables and populates them."""
32 | db.create_all()
33 | populate_db()
34 |
35 |
36 | @manager.command
37 | def drop_db():
38 | """Drops database tables."""
39 | if prompt_bool('Are you sure?'):
40 | db.drop_all()
41 |
42 |
43 | @manager.command
44 | def recreate_db():
45 | """Same as running drop_db() and create_db()."""
46 | drop_db()
47 | create_db()
48 |
49 |
50 | if __name__ == '__main__':
51 | manager.run()
52 |
--------------------------------------------------------------------------------
/app/auth/models.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy.ext.hybrid import hybrid_property
2 | from sqlalchemy.exc import IntegrityError
3 |
4 | from app.database import db
5 | from app.core import Base
6 | from app.extensions import bcrypt
7 |
8 |
9 | class User(Base):
10 |
11 | __tablename__ = 'user'
12 |
13 | username = db.Column(db.String(128), nullable=False)
14 | _password = db.Column(db.String(192), nullable=False)
15 | first_name = db.Column(db.String(128), nullable=False)
16 | last_name = db.Column(db.String(128), nullable=False)
17 | authenticated = db.Column(db.Boolean, default=False)
18 | active = db.Column(db.Boolean, default=True)
19 |
20 | @staticmethod
21 | def create(**kwargs):
22 | u = User(**kwargs)
23 | db.session.add(u)
24 | try:
25 | db.session.commit()
26 | except IntegrityError:
27 | db.session.rollback()
28 | return u
29 |
30 | def is_active(self):
31 | return self.active
32 |
33 | def get_id(self):
34 | return self.username
35 |
36 | def is_authenticated(self):
37 | return self.authenticated
38 |
39 | def is_anonymous(self):
40 | return False
41 |
42 | @hybrid_property
43 | def password(self):
44 | return self._password
45 |
46 | @password.setter
47 | def _set_password(self, plaintext):
48 | self._password = bcrypt.generate_password_hash(plaintext)
49 |
50 | def is_correct_password(self, plaintext):
51 | return bcrypt.check_password_hash(self._password, plaintext)
52 |
--------------------------------------------------------------------------------
/app/core/controller.py:
--------------------------------------------------------------------------------
1 | from flask import request, render_template, url_for, redirect
2 | from flask_login import login_required
3 |
4 | from app.core.forms import CreateContact, CreateOrganisation
5 | from app.core.models import Contact, Organisation
6 | from . import core
7 |
8 |
9 | @core.route('/')
10 | @login_required
11 | def home():
12 | return render_template('core/home.html')
13 |
14 |
15 | @core.route('/contact/create', methods=['GET', 'POST'])
16 | @login_required
17 | def create_contact():
18 | form = CreateContact(request.form)
19 | if request.method == 'POST':
20 | if form.validate():
21 | form.org_id.data = form.org_id.data.id
22 | contact = Contact.create(**form.data)
23 | return redirect(url_for('core.view_contact', con_id=contact.id))
24 | return render_template('core/create_contact.html', form=form)
25 |
26 |
27 | @core.route('/contact/')
28 | @login_required
29 | def view_contact(con_id):
30 | contact = Contact.query.filter_by(id=con_id).first()
31 | columns = [el.name for el in Contact.__table__.columns]
32 | return render_template('core/view_contact.html', columns=columns, record=contact)
33 |
34 |
35 | @core.route('/organisation/create', methods=['GET', 'POST'])
36 | @login_required
37 | def create_organisation():
38 | form = CreateOrganisation(request.form)
39 | if request.method == 'POST':
40 | if form.validate():
41 | org = Organisation.create(name=form.name.data, type=form.type.data, address=form.address.data)
42 | return redirect(url_for('core.view_organisation', org_id=org.id))
43 | return render_template('core/create_organisation.html', form=form)
44 |
45 |
46 | @core.route('/organisation/')
47 | @login_required
48 | def view_organisation(org_id):
49 | org = Organisation.query.filter_by(id=org_id).first()
50 | return render_template('core/view_organisation.html', organisation=org)
51 |
--------------------------------------------------------------------------------
/tests/test_auth.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from flask import url_for
4 | from flask_login import current_user
5 |
6 | from config import AuthTestConfig
7 | from app.auth import User
8 | from app import create_app
9 | from app.database import db
10 |
11 |
12 | class AuthTestCase(unittest.TestCase):
13 | def setUp(self):
14 | self.app = create_app(AuthTestConfig)
15 | self.app_context = self.app.test_request_context()
16 | self.app_context.push()
17 | db.app = self.app
18 | db.create_all()
19 | self.client = self.app.test_client(use_cookies=True)
20 |
21 | def tearDown(self):
22 | db.session.remove()
23 | db.drop_all()
24 | self.app_context.pop()
25 |
26 | def test_login_route(self):
27 | rv = self.client.get(url_for('auth.login'))
28 | self.assertEquals(rv.status_code, 200)
29 |
30 | def test_add_user_with_password_hashing(self):
31 | user = User.create(username='test@gmail.com', password='mysecret', first_name='chris', last_name='hall')
32 | self.assertEqual(user.username, 'test@gmail.com')
33 | self.assertNotEqual(user.password, 'mysecret', 'Password not hashed')
34 | self.assertTrue(user.is_correct_password('mysecret'))
35 | self.assertEqual(user.first_name, 'chris')
36 | self.assertEqual(user.last_name, 'hall')
37 |
38 | def test_valid_login_submit(self):
39 | with self.client:
40 | user = User.create(username='right@gmail.com', password='mysecret', first_name='chris', last_name='hall')
41 | form_data = {
42 | 'username': 'right@gmail.com',
43 | 'password': 'mysecret'
44 | }
45 | rv = self.client.post(url_for('auth.login'), data=form_data, follow_redirects=True)
46 | self.assertEquals(rv.status_code, 200)
47 | self.assertTrue(user.is_authenticated())
48 | self.assertEquals(current_user.id, user.id)
49 | rv = self.client.get('contact/create')
50 | self.assertEquals(rv.status_code, 200)
51 |
52 | def test_incorrect_password_display_message(self):
53 | User.create(username='wrong@gmail.com', password='mysecret', first_name='chris', last_name='hall')
54 | form_data = {
55 | 'username': 'wrong@gmail.com',
56 | 'password': 'wrongpassword'
57 | }
58 | rv = self.client.post(url_for('auth.login'), data=form_data, follow_redirects=True)
59 | self.assertEquals(rv.status_code, 200)
60 | self.assertTrue('Invalid password' in rv.data)
61 |
62 | # When we are not logged in trying access login_required pages should yield 401
63 | def test_login_required(self):
64 | rv = self.client.get('/')
65 | self.assertEquals(rv.status_code, 401)
66 | rv = self.client.get('/contact/create')
67 | self.assertEquals(rv.status_code, 401)
68 |
--------------------------------------------------------------------------------
/app/core/models.py:
--------------------------------------------------------------------------------
1 | from sqlalchemy_utils import EmailType, ChoiceType
2 | from sqlalchemy.exc import IntegrityError
3 |
4 | from app.database import db
5 |
6 |
7 | class Base(db.Model):
8 |
9 | __abstract__ = True
10 |
11 | id = db.Column(db.Integer, primary_key=True)
12 | date_created = db.Column(db.DateTime, default=db.func.current_timestamp())
13 | date_modified = db.Column(db.DateTime, default=db.func.current_timestamp(), onupdate=db.func.current_timestamp())
14 |
15 |
16 | class Contact(Base):
17 |
18 | """User input fields - these fields can be set by user and are included in forms"""
19 | first_name = db.Column(db.String(60), nullable=False, info={"label": "First Name"})
20 | last_name = db.Column(db.String(60), nullable=False, info={"label": "Last Name"})
21 | email = db.Column(EmailType, nullable=False, info={"label": "Email"})
22 | mobile = db.Column(db.Integer, info={"label": "Mobile"})
23 | role = db.Column(db.String(60), info={"label": "Role"})
24 | org_id = db.Column(db.Integer, db.ForeignKey('organisation.id'), info={"label": "Organisation"})
25 | created_by = db.Column(db.Integer, db.ForeignKey('user.id'))
26 |
27 | activities = db.relationship('Activity', backref='contact')
28 |
29 | @staticmethod
30 | def create(**kwargs):
31 | c = Contact(**kwargs)
32 | db.session.add(c)
33 | try:
34 | db.session.commit()
35 | except IntegrityError:
36 | db.session.rollback()
37 | return c
38 |
39 |
40 | class Organisation(Base):
41 | TYPE_CHOICE = [
42 | ('charity', 'Charity'),
43 | ('funder', 'Funder'),
44 | ('other', 'Other')
45 | ]
46 |
47 | name = db.Column(db.String(100), nullable=False)
48 | type = db.Column(ChoiceType(TYPE_CHOICE), nullable=False)
49 | address = db.Column(db.Text(180))
50 |
51 | created_by = db.Column(db.Integer, db.ForeignKey('user.id'))
52 |
53 | contacts = db.relationship('Contact', backref='organisation')
54 | activities = db.relationship('Activity', backref='contact_lookup')
55 |
56 | @staticmethod
57 | def create(**kwargs):
58 | o = Organisation(**kwargs)
59 | db.session.add(o)
60 | try:
61 | db.session.commit()
62 | except IntegrityError:
63 | db.session.rollback()
64 | return o
65 |
66 |
67 | class Project(Base):
68 | STATUS_CHOICE = [
69 | ('in_progress', 'In Progress'),
70 | ('completed', 'Completed')
71 | ]
72 |
73 | start_date = db.Column(db.Date)
74 | end_date = db.Column(db.Date)
75 | status = db.Column(ChoiceType(STATUS_CHOICE), nullable=False)
76 |
77 | created_by = db.Column(db.Integer, db.ForeignKey('user.id'))
78 | org_id = db.Column(db.Integer, db.ForeignKey('organisation.id'))
79 | contact_id = db.Column(db.Integer, db.ForeignKey('contact.id'))
80 |
81 | activities = db.relationship('Activity', backref='project')
82 | invoices = db.relationship('Invoice', backref='project')
83 |
84 |
85 | class Invoice(Base):
86 |
87 | issue_date = db.Column(db.Date)
88 | amount = db.Column(db.Integer, nullable=False)
89 | paid = db.Column(db.Boolean, default=False)
90 |
91 | created_by = db.Column(db.Integer, db.ForeignKey('user.id'))
92 | project_id = db.Column(db.Integer, db.ForeignKey('project.id'))
93 |
94 |
95 | class Activity(Base):
96 |
97 | subject = db.Column(db.String(100), nullable=False)
98 | detail = db.Column(db.Text)
99 |
100 | contact_id = db.Column(db.Integer, db.ForeignKey('contact.id'))
101 | org_id = db.Column(db.Integer, db.ForeignKey('organisation.id'))
102 | project_id = db.Column(db.Integer, db.ForeignKey('project.id'))
103 |
--------------------------------------------------------------------------------
/tests/test_core.py:
--------------------------------------------------------------------------------
1 | import unittest
2 |
3 | from config import TestConfig
4 | from app import create_app
5 | from app.database import db
6 | from app.core import Contact, Organisation
7 |
8 |
9 | class CoreTestCase(unittest.TestCase):
10 | def setUp(self):
11 | self.app = create_app(TestConfig)
12 | self.app_context = self.app.test_request_context()
13 | self.app_context.push()
14 | db.app = self.app
15 | db.create_all()
16 | self.client = self.app.test_client(use_cookies=True)
17 |
18 | def tearDown(self):
19 | db.session.remove()
20 | db.drop_all()
21 | self.app_context.pop()
22 |
23 | def test_home_page_route(self):
24 | rv = self.client.get('/')
25 | self.assertEquals(rv.status_code, 200)
26 |
27 | def test_create_contact_route(self):
28 | rv = self.client.get('/contact/create')
29 | self.assertEquals(rv.status_code, 200)
30 |
31 | def test_create_contact_empty_data(self):
32 | data = {}
33 | rv = self.client.post('/contact/create', data=data)
34 | self.assertEquals(rv.status_code, 200)
35 |
36 | def test_create_contact_valid_form(self):
37 | org_data = {
38 | 'name': 'test charity',
39 | 'type': 'charity',
40 | 'address': '1 My Road, London'
41 | }
42 | o = Organisation.create(**org_data)
43 | data = {
44 | 'first_name': 'test',
45 | 'last_name': 'contact',
46 | 'email': 'example@test.co.uk',
47 | 'org_id': o.id
48 | }
49 | rv = self.client.post('/contact/create', data=data)
50 | self.assertEquals(rv.status_code, 302)
51 | c = Contact.query.filter_by(email=data['email']).all()
52 | self.assertEqual(len(c), 1)
53 | self.assertEqual(c[0].first_name, data['first_name'])
54 | self.assertEqual(c[0].last_name, data['last_name'])
55 | self.assertEqual(c[0].email, data['email'])
56 |
57 | def test_create_contact_email_validation(self):
58 | data = {
59 | 'first_name': 'test',
60 | 'last_name': 'contact',
61 | 'email': 'notanemailaddress'
62 | }
63 | rv = self.client.post('/contact/create', data=data)
64 | self.assertEqual(rv.status_code, 200)
65 | c = Contact.query.filter_by(email=data['email']).all()
66 | self.assertEqual(len(c), 0)
67 |
68 | def test_view_contact_route(self):
69 | org_data = {
70 | 'name': 'test charity',
71 | 'type': 'charity',
72 | 'address': '1 My Road, London'
73 | }
74 | o = Organisation.create(**org_data)
75 | con_data = {
76 | 'first_name': 'test',
77 | 'last_name': 'contact',
78 | 'email': 'example@test.co.uk',
79 | 'org_id': o.id
80 | }
81 | self.client.post('/contact/create', data=con_data)
82 | c = Contact.query.filter_by(email=con_data['email']).first()
83 | rv = self.client.get('/contact/{}'.format(c.id))
84 | self.assertEquals(rv.status_code, 200)
85 |
86 | def test_create_organisation_route(self):
87 | rv = self.client.get('/organisation/create')
88 | self.assertEquals(rv.status_code, 200)
89 |
90 | def test_create_organisation_valid_form(self):
91 | data = {
92 | 'name': 'test charity',
93 | 'type': 'charity',
94 | 'address': '1 My Road, London'
95 | }
96 | rv = self.client.post('/organisation/create', data=data)
97 | self.assertEquals(rv.status_code, 302)
98 | o = Organisation.query.filter_by(name=data['name']).all()
99 | self.assertEqual(len(o), 1)
100 | self.assertEqual(o[0].name, data['name'])
101 | self.assertEqual(o[0].type, data['type'])
102 | self.assertEqual(o[0].address, data['address'])
103 |
104 | def test_contact_organisation_relationship(self):
105 | test_org_name = 'Test Organisation'
106 | test_org = Organisation.create(name=test_org_name, type='charity')
107 | test_contact = Contact.create(first_name='test', last_name='contact', email='example@test.co.uk',
108 | org_id=test_org.id)
109 | self.assertEquals(test_org.id, test_contact.org_id)
110 | self.assertEquals(len(test_org.contacts), 1)
111 | self.assertEquals(test_contact.organisation.name, test_org_name)
112 |
113 |
114 | if __name__ == '__main__':
115 | unittest.main()
--------------------------------------------------------------------------------