Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
11 changes: 10 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ jobs:
gcloud --quiet config set compute/zone ${GOOGLE_COMPUTE_ZONE}
openssl aes256 -md md5 -d -in docker-compose.circleci.yaml.enc -out docker-compose.yml -pass pass:$CONFIG_PASSWORD
docker-compose up -d
- run:
name: Format with black
command: docker-compose run web black --check api
- run:
name: Format with isort
command: docker-compose run web isort --check --settings-path=api/setup.cfg api
- run:
name: Format with prettier
command: docker-compose run webapp yarn prettier-ci
- run:
name: Run migrations
command: |
Expand All @@ -22,7 +31,7 @@ jobs:
docker-compose run web python3 api/run.py db upgrade --directory api/migrations
- run:
name: Run unit tests
command: docker-compose run web nosetests -v
command: docker-compose run web nosetests -v

integration-tests:
machine:
Expand Down
4 changes: 4 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
root = true

[*]
end_of_line = lf
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
text=auto eol=lf

*.{png,pdf,ico,enc,jpg,jpeg,gif,webp,woff,woff2} binary
4 changes: 0 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,6 @@ venv.bak/
.spyderproject
.spyproject

# VSCode project settings
.vscode


# IntelliJ/Pycharm/Webstorm/etc. project settings
.iml

Expand Down
7 changes: 7 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"recommendations": [
"editorconfig.editorconfig",
"ms-python.python",
"esbenp.prettier-vscode"
]
}
28 changes: 28 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"python.formatting.provider": "black",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true
},
"python.sortImports.args": [
"--settings-path=${workspaceFolder}/api/setup.cfg"
],
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
},
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
},
"[html]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
},
"[jsx-tags]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
},
"prettier.configPath": "./webapp/.prettierrc.json",
"prettier.ignorePath": "./webapp/.prettierignore",
"prettier.prettierPath": "./webapp/node_modules/prettier"
}
7 changes: 4 additions & 3 deletions api/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ ENV PATH /env/bin:$PATH

RUN apt-get update -qq \
&& apt-get install -y software-properties-common \
&& apt-get install -y libreoffice
&& apt-get install -y libreoffice \
&& python3 -m pip install --upgrade pip

# Upgrade pip
RUN python -m pip install --upgrade pip

# Add the application source code.
ADD requirements.txt /code/requirements.txt
RUN pip3 install -r /code/requirements.txt
COPY requirements.txt requirements-dev.txt /code/
RUN pip3 install -r /code/requirements.txt -r /code/requirements-dev.txt

ADD . /code/
WORKDIR /code
Expand Down
123 changes: 73 additions & 50 deletions api/app/__init__.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
from flask import Flask, g, url_for, redirect, render_template, request, flash
from flask_cors import CORS
import flask_login as login
import flask_restful as restful
from flask_sqlalchemy import SQLAlchemy
import tldextract
from flask import Flask, flash, g, redirect, render_template, request, url_for
from flask_admin import Admin, AdminIndexView, expose, helpers
from flask_admin.contrib.sqla import ModelView
from flask_bcrypt import Bcrypt
from flask_cors import CORS
from flask_migrate import Migrate, MigrateCommand
from flask_script import Manager
from flask_redis import FlaskRedis
from flask_script import Manager
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import check_password_hash, generate_password_hash
from wtforms import fields, form, validators

from .utils.logger import Logger
from flask_admin import Admin, AdminIndexView, helpers, expose
from flask_admin.contrib.sqla import ModelView
import flask_login as login
from wtforms import form, fields, validators
from werkzeug.security import generate_password_hash, check_password_hash
import tldextract

app = Flask(__name__)
app.config.from_object('config')
app.config.from_object("config")
CORS(app, resources={r"/api/*": {"origins": "*"}}, supports_credentials=True)
print((app.config['SQLALCHEMY_DATABASE_URI']))
print((app.config["SQLALCHEMY_DATABASE_URI"]))
rest_api = restful.Api(app)
db = SQLAlchemy(app)
bcrypt = Bcrypt(app)
Expand All @@ -29,49 +30,66 @@
migrate = Migrate(app, db)

manager = Manager(app)
manager.add_command('db', MigrateCommand)
manager.add_command("db", MigrateCommand)

from .organisation.resolver import OrganisationResolver


def get_domain():
# TODO: Remove this test-related hack!
if app.config['TESTING'] and 'HTTP_ORIGIN' not in request.environ and 'HTTP_REFERER' not in request.environ:
return 'org'

origin = request.environ.get('HTTP_ORIGIN', '')
if (
app.config["TESTING"]
and "HTTP_ORIGIN" not in request.environ
and "HTTP_REFERER" not in request.environ
):
return "org"

origin = request.environ.get("HTTP_ORIGIN", "")
if not origin: # Try to get from Referer header
origin = request.environ.get('HTTP_REFERER', '')
LOGGER.debug('No ORIGIN header, falling back to Referer: {}'.format(origin))
origin = request.environ.get("HTTP_REFERER", "")
LOGGER.debug("No ORIGIN header, falling back to Referer: {}".format(origin))

if origin:
domain = tldextract.extract(origin).domain
else:
LOGGER.warning('Could not determine origin domain')
domain = ''
LOGGER.warning("Could not determine origin domain")
domain = ""

return domain


@app.before_request
def populate_organisation():
domain = get_domain()
LOGGER.info('Origin Domain: {}'.format(domain)) # TODO: Remove this after testing
LOGGER.info("Origin Domain: {}".format(domain)) # TODO: Remove this after testing
g.organisation = OrganisationResolver.resolve_from_domain(domain)


## Flask Admin Config

# set optional bootswatch theme
app.config['FLASK_ADMIN_SWATCH'] = 'darkly'
app.config["FLASK_ADMIN_SWATCH"] = "darkly"
from app.utils.auth import admin_required, auth_required, generate_token
from app.utils.errors import FORBIDDEN, UNAUTHORIZED

from .applicationModel.models import Question, Section
from .responses.models import Response, Answer, ResponseReviewer
from .users.models import UserCategory, AppUser, UserComment
from .email_template.models import EmailTemplate
from .outcome.models import Outcome
from .events.models import Event, EventRole
from app.utils.auth import auth_required, admin_required, generate_token
from app.utils.errors import UNAUTHORIZED, FORBIDDEN
from .invitationletter.models import InvitationLetterRequest, InvitationTemplate
from .outcome.models import Outcome
from .registration.models import (
Offer,
Registration,
RegistrationAnswer,
RegistrationForm,
RegistrationQuestion,
RegistrationSection,
)
from .responses.models import Answer, Response, ResponseReviewer
from .reviews.models import ReviewForm, ReviewQuestion
from .registration.models import Offer, RegistrationForm, RegistrationSection, RegistrationQuestion, Registration, RegistrationAnswer
from .invitationletter.models import InvitationTemplate, InvitationLetterRequest
from .users.models import AppUser, UserCategory, UserComment


# Define login and registration forms (for flask-login)
class LoginForm(form.Form):
email = fields.TextField(validators=[validators.required()])
Expand All @@ -81,11 +99,10 @@ def validate(self):
user = self.get_user()

if user is None:
raise validators.ValidationError('Invalid user')

raise validators.ValidationError("Invalid user")

if not bcrypt.check_password_hash(user.password, self.password.data):
raise validators.ValidationError('Invalid password')
raise validators.ValidationError("Invalid password")

if not user.is_admin:
raise validators.ValidationError("Adminstrator rights required")
Expand All @@ -95,7 +112,10 @@ def validate(self):

def get_user(self):
# TODO: What organisation should we use to query here?
return db.session.query(AppUser).filter(AppUser.email==self.email.data).first()
return (
db.session.query(AppUser).filter(AppUser.email == self.email.data).first()
)


# Initialize flask-login
def init_login():
Expand All @@ -108,45 +128,48 @@ def load_user(user_id):
return db.session.query(AppUser).get(user_id)



# Create customized index view class that handles login & registration
class BaobabAdminIndexView(AdminIndexView):

@expose('/')
@expose("/")
def index(self):
if not login.current_user.is_authenticated:
return redirect(url_for('.login_view'))
return redirect(url_for(".login_view"))
return super(BaobabAdminIndexView, self).index()

@expose('/login/', methods=('GET', 'POST'))
@expose("/login/", methods=("GET", "POST"))
def login_view(self):
try:

# handle user login
form = LoginForm(request.form)
if request.method == 'POST':
if request.method == "POST":
if form.validate():
user = form.get_user()
login.login_user(user)

if login.current_user.is_authenticated:
return redirect(url_for('.index'))
return redirect(url_for(".index"))
except validators.ValidationError as error:
flash(str(error))

self._template_args['form'] = form
self._template_args["form"] = form
return super(BaobabAdminIndexView, self).index()


@expose('/logout/')
@expose("/logout/")
def logout_view(self):
login.logout_user()
return redirect(url_for('.index'))
return redirect(url_for(".index"))


# Initialize flask-login
init_login()

admin = Admin(app, name='Deep Learning Indaba Admin Portal', index_view=BaobabAdminIndexView(), template_mode='bootstrap3')
admin = Admin(
app,
name="Deep Learning Indaba Admin Portal",
index_view=BaobabAdminIndexView(),
template_mode="bootstrap3",
)


class BaobabModelView(ModelView):
Expand All @@ -155,7 +178,8 @@ def is_accessible(self):

def inaccessible_callback(self, name, **kwargs):
# redirect to login page if user doesn't have access
return redirect(url_for('admin.login_view', next=request.url))
return redirect(url_for("admin.login_view", next=request.url))


admin.add_view(BaobabModelView(Question, db.session))
admin.add_view(BaobabModelView(Section, db.session))
Expand All @@ -181,4 +205,3 @@ def inaccessible_callback(self, name, **kwargs):
admin.add_view(BaobabModelView(InvitationTemplate, db.session))
admin.add_view(BaobabModelView(InvitationLetterRequest, db.session))
admin.add_view(BaobabModelView(EmailTemplate, db.session))

Loading