├── .gitignore ├── LICENSE ├── README.md ├── flaskstarter ├── PBMC_study_meta │ ├── Country ISO code List.xlsx │ ├── PBMC_cell_type.xlsx │ ├── PBMC_study_meta.csv │ ├── PBMC_study_meta.xlsx │ └── ~$PBMC_study_meta.xlsx ├── __init__.py ├── app.py ├── config.py ├── decorators.py ├── emails │ └── __init__.py ├── extensions.py ├── frontend │ ├── __init__.py │ ├── forms.py │ ├── models.py │ └── views.py ├── model │ ├── meta.py │ └── umap.py ├── settings │ ├── __init__.py │ ├── forms.py │ └── views.py ├── static │ ├── D24H_Logo.png │ ├── Dataset_Banner.jpeg │ ├── Group 36.png │ ├── Group 37.png │ ├── Group 38.png │ ├── Group.png │ ├── Login_Banner.jpeg │ ├── Mask_Group.png │ ├── Pointer.png │ ├── Vector.svg │ ├── bootstrap.bundle.min.js │ ├── bootstrap.min.css │ ├── chevron-down.png │ ├── chevron-down.svg │ ├── css │ │ └── template.css │ ├── download-file 1.svg │ ├── exclamation-mark-64.png │ ├── explore.png │ ├── facebook.svg │ ├── folder-2.png │ ├── folder-3.png │ ├── folder.png │ ├── instagram.svg │ ├── jquery-3.6.0.min.js │ ├── jquery.slim.min.js │ ├── link icon.svg │ ├── linkin.svg │ ├── loading-icon.gif │ ├── location icon.svg │ ├── mail icon.svg │ ├── microscope.png │ ├── newspaper.png │ ├── tel icon.svg │ ├── twitter.svg │ └── vimeo.svg ├── tasks │ ├── .views.py.swp │ ├── __init__.py │ ├── forms.py │ └── views.py ├── templates │ ├── admin │ │ └── index.html │ ├── dashboard │ │ └── dashboard.html │ ├── frontend │ │ ├── change_password.html │ │ ├── contact_us.html │ │ ├── data.html │ │ ├── landing.html │ │ ├── login.html │ │ ├── reset_password.html │ │ ├── signup.html │ │ └── tutorial.html │ ├── layouts │ │ ├── banner.html │ │ ├── base.html │ │ ├── citation.html │ │ ├── footer.html │ │ └── header.html │ ├── macros │ │ ├── _confirm_account.html │ │ ├── _flash_msg.html │ │ ├── _form.html │ │ └── _reset_password.html │ ├── settings │ │ ├── password.html │ │ └── profile.html │ └── tasks │ │ ├── add_task.html │ │ ├── backup.html │ │ ├── contribute.html │ │ ├── edit_task.html │ │ ├── landing.html │ │ ├── my_tasks.html │ │ ├── show_plot.html │ │ ├── show_scfeature.html │ │ ├── show_search_plot.html │ │ ├── table_view.html │ │ └── view_task.html ├── user │ ├── __init__.py │ ├── constants.py │ └── models.py └── utils.py ├── gunicorn.conf.py ├── manage.py ├── requirements.txt ├── screenshots ├── Logo_gradient.png ├── admin.png ├── dashboard.png ├── homepage.png ├── login.png ├── logo.png ├── profile.png ├── signup.png └── tasks.png └── tests ├── __init__.py └── test_flaskstarter.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | venv/ 3 | *.sh 4 | __pycache__/ 5 | db.sqlite 6 | cov192kaxis.csv 7 | .idea/ 8 | *.log 9 | dump.rdb 10 | dump.rdb 11 | dump.rdb 12 | *.rdb -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023 Danqing Yin and others 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scope+: Open Source Generalizable Architecture for Single-cell Atlases 2 | 3 |

4 | 5 |

6 | 7 | Scope+ is a modern, scalable and generalized single-cell atlas portal archictecture. The architecture has been applied to 5 million single-cell COVID-19 immune and blood cells data with realization in Covidscope web portal (https://www.covidsc.d24h.hk/). 8 | 9 | ## Tutorial 10 | The tutorial for implementation of Scope+ to adapt user's own atlas datasets is available at: [https://hiyin.github.io/scopeplus-user-tutorial/](https://hiyin.github.io/scopeplus-user-tutorial/) 11 | 12 | ## Introduction 13 | Single-cell technologies have been widely used to investigate the cellular response and mechanisms for COVID-19. Due to the increasing large-scale single-cell studies, many integrated datasets have been made available, and there is a lack of fast and easy access tools to examine such resources. These large-scale data poses a data science challenge of effective accessing, organizing, sharing and interpretation of integrated datasets. There are many individual platforms that try to host the single-cell atlas, each with their own limitations. We believe that there should be a generalizable architecture for development of single-cell atlas, enhancing reproducible research. Here, we develop an open source, highly optimized and scalable architecture, named Scope+, to allow quick access, meta-analysis and cell-level selection of the atlas data. We applied this architecture to our well-curated Covid-19 resources, releasing it as a portal i.e. Covidscope (https://covidsc.d24h.hk/) that facilitates effective meta-analytical downstream analysis for 5 million single-cell COVID-19 transcriptomic data from 20 studies globally. Covidscope is not limited to our own Covid-19 atlas, it can be adapted and reimplemented to any other single-cell transcriptomic atlas or integrated datasets. 14 | 15 | ## Innovation 16 | Scope+ addresses the big data challenges associated with fast access, cell-level querying and meta-analyses of 12.7B matrix count and ~5M metadata in a web application with innovative architecture design. We address the challenge with three key innovations: (i) server-side rendering of metadata via a fast pagination technique; (ii) novel database optimisation strategies to allow extremely fast retrieval of the large count matrix; and (iii) a novel architecture design using enabling users to filter, visualize and large queried data concurrently. Thus, Scope+ 17 | allows real-time data subsetting and visualizations of the large single cell expression data matrix based on user queries using patient or cell-level characteristics with real-time dynamic visualizations. 18 | 19 | ## Implication 20 | Existing single-cell atlas portal allow dataset-level browsing, no integrative cell-level browsing, while Scope+ provide efficient access, cell-level categorical selections and filtering of cells for meta-analysis for users who wish to use and adopt Scope+ to their own atlas data. Single cell gene expression data is sparse high-dimentional, the data query, retrieval and data visualization in Scope+ at cell-level granularity is accelerated by various computational optimization strategies. 21 | 22 | The development timeline for single cell atlas portal is around 1 to 2 years, with the help of Scope+, it can be streamlined to a weeke or two. Ultimately, Scope+ can translate any single cell atlases into a openly accessible data web portal for reproducible science, and making single-cell research data avaialble for community timely, this is particularly important for pandemics. 23 | 24 | ## Liscence 25 | The software architecture is open-sourced in this repository and for use under the MIT License. 26 | 27 | -------------------------------------------------------------------------------- /flaskstarter/PBMC_study_meta/Country ISO code List.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiyin/scopeplus/3d0f9f74d966390f101fabb38d1952f5e8c90ba6/flaskstarter/PBMC_study_meta/Country ISO code List.xlsx -------------------------------------------------------------------------------- /flaskstarter/PBMC_study_meta/PBMC_cell_type.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiyin/scopeplus/3d0f9f74d966390f101fabb38d1952f5e8c90ba6/flaskstarter/PBMC_study_meta/PBMC_cell_type.xlsx -------------------------------------------------------------------------------- /flaskstarter/PBMC_study_meta/PBMC_study_meta.csv: -------------------------------------------------------------------------------- 1 | Study,Accession,doi,Country,Country Code,Published Date,Journal,Title,Number of cells,Number of samples,Number of donors,,,,,,,,, 2 | Arunachalam_2020,GSE155673,https://www.science.org/doi/10.1126/science.abc6261,US,US,"August 11, 2020",Science,Systems biological assessment of immunity to mild versus severe COVID-19 infection in humans,56639,12,12,,,,,,,,, 3 | Bacher_2020,GSE162086,https://doi.org/10.1016/j.immuni.2020.11.016,Germany,DE,"December 15, 2020",Immunity,Low-Avidity CD4+ T Cell Responses to SARS-CoV-2 in Unexposed Individuals and Humans with Severe COVID-19,104417,20,20,,,,,,,,, 4 | Bost_PBMC_2021,GSE157344,https://doi.org/10.1038/s41467-021-21702-6,Israel,IL,"March 5, 2021",Nature communications,Deciphering the state of immune silence in fatal COVID-19 patients,50284,33,33,,,,,,,,, 5 | COMBAT_2022,EGAS00001005493,https://doi.org/10.1016/j.cell.2022.01.012,UK,GB,"March 3, 2022",Cell,A blood atlas of COVID-19 defines hallmarks of disease severity and specificity,783704,140,140,,,,,,,,, 6 | Combes_2021,GSE163668,https://doi.org/10.1038/s41586-021-03234-7,US,US,"January 25, 2021",Nature,Global absence and targeting of protective immune states in severe COVID-19,111990,44,44,,,,,,,,, 7 | Heohn_2021,GSE164381,https://doi.org/10.4049/jimmunol.2100135,US,US,"May 28, 2021",The Jounral of Immunology,Distinct B Cell Repertoires Characterize Patients with Mild and Severe COVID-19,24723,11,11,,,,,,,,, 8 | Kusnadi_2021,GSE153931,https://www.science.org/doi/10.1126/sciimmunol.abe4782,US,US,"January 21, 2021",Science immunology,Severely ill patients with COVID-19 display impaired exhaustion features in SARS-CoV-2–reactive CD8+ T cells,78897,52,45,,,,,,,,, 9 | Lee_2020,GSE147507,https://www.science.org/doi/10.1126/sciimmunol.abd15544,Korea,KR,"July 10, 2020",Science immunology,Immunophenotyping of COVID-19 and influenza highlights the role of type I interferons in development of severe COVID-19,59572,20,17,,,,,,,,, 10 | Liu_2021,GSE161918,https://doi.org/10.1016/j.cell.2021.02.0188,US,US,"April 1, 2021",Cell,Time-resolved systems immunology reveals a late juncture linked to fatal COVID-19,411902,70,47,,,,,,,,, 11 | Meckiff_2020,GSE152522,https://doi.org/10.1016/j.cell.2020.10.001,US,US,"November 25, 2020",Cell,Imbalance of Regulatory and Cytotoxic SARS-CoV-2-Reactive CD4+ T Cells in COVID-19,68379,43,43,,,,,,,,, 12 | Ramaswamy_2021,GSE166489,https://doi.org/10.1016/j.immuni.2021.04.003,US,US,"May 11, 2021",Immunity,Immune dysregulation and autoreactivity correlate with disease severity in SARS-CoV-2-associated multisystem inflammatory syndrome in children,271267,38,32,,,,,,,,, 13 | Ren_2021,GSE158055,https://doi.org/10.1016/j.cell.2021.01.053,China,CN,"April 1, 2021",Cell,COVID-19 immune features revealed by a large-scale single-cell transcriptome atlas,1405477,250,182,,,,,,,,, 14 | Schulte-Schrepping_2020,EGAS00001004571,https://doi.org/10.1016/j.cell.2020.08.001,Germany,DE,"September 17, 2020",Cell,Severe COVID-19 Is Marked by a Dysregulated Myeloid Cell Compartment,328780,147,74,,,,,,,,, 15 | Schuurman_2021,GSE164948,https://doi.org/10.7554/eLife.696611,Netherlands,NL,"August 23, 2021",eLife,"Integrated single-cell analysis unveils diverging immune features of COVID-19, influenza, and other community-acquired pneumonia",32384,20,20,,,,,,,,, 16 | Silvin_2020,E-MTAB-9221,https://doi.org/10.1016/j.cell.2020.08.002,France,FR,"September 17, 2020",Cell,Elevated Calprotectin and Abnormal Myeloid Cell Subsets Discriminate Severe from Mild COVID-19,6960,10,10,,,,,,,,, 17 | Sinha_2022,GSE157789,https://doi.org/10.1038/s41591-021-01576-3,Canada,CA,"January 1, 2022",Nature medicine,Dexamethasone modulates immature neutrophils and interferon programming in severe COVID-19,80994,21,21,,,,,,,,, 18 | Stephenson_2021,E-MTAB-10026,https://doi.org/10.1038/s41591-021-01329-2,UK,GB,"April 20, 2021",Nature medicine,Single-cell multi-omics analysis of the immune response in COVID-19,643071,143,130,,,,,,,,, 19 | Su_2020,E-MTAB-9357,https://doi.org/10.1016/j.cell.2020.10.037,US,US,"October 10, 2020",Cell,Multi-Omics Resolves a Sharp Disease-State Shift between Mild and Moderate COVID-19,538210,268,143,,,,,,,,, 20 | Thompson_2021,GSE166992,https://doi.org/10.1016/j.celrep.2021.108863,US,US,"March 16, 2021",Cell reports,Metabolic programs define dysfunctional immune responses in severe COVID-19 patients,63895,8,8,,,,,,,,, 21 | Unterman_2022,GSE155224,https://doi.org/10.1038/s41467-021-27716-4,US,US,"January 22, 2022",Nature communications,Single-cell multi-omics reveals dyssynchrony of the innate and adaptive immune system in progressive COVID-19,80789,18,10,,,,,,,,, 22 | Wilk_2021,GSE174072,https://doi.org/10.1084/jem.20210582,US,US,"June 15, 2021",Journal of Experimental Medicine,Multi-omic profiling reveals widespread dysregulation of innate immunity and hematopoiesis in COVID-19,174753,55,39,,,,,,,,, 23 | Yao_2021,GSE154567,https://doi.org/10.1016/j.celrep.2020.108590,US,US,"January 5, 2021",Cell reports,Cell-Type-Specific Immune Dysregulation in Severely Ill COVID-19 Patients,69983,17,17,,,,,,,,, 24 | Zhao_2021,CNP0001250,https://doi.org/10.1038/s41392-021-00753-7,China,CN,"September 16, 2021",Signal Transduction and Targeted Therapy,Single-cell immune profiling reveals distinct immune response in asymptomatic COVID-19 patients,88374,38,19,,,,,,,,, 25 | Zhu_2020,CNP0001102,https://doi.org/10.1016/j.immuni.2020.07.009,China,CN,"September 15, 2020",Immunity,Single-Cell Sequencing of Peripheral Mononuclear Cells Reveals Distinct Immune Response Landscapes of COVID-19 and Influenza Patients,46022,23,10,,,,,,,,, 26 | ,,,,,,,Total cells,5581466,1501,1127,,,,,,,,, -------------------------------------------------------------------------------- /flaskstarter/PBMC_study_meta/PBMC_study_meta.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiyin/scopeplus/3d0f9f74d966390f101fabb38d1952f5e8c90ba6/flaskstarter/PBMC_study_meta/PBMC_study_meta.xlsx -------------------------------------------------------------------------------- /flaskstarter/PBMC_study_meta/~$PBMC_study_meta.xlsx: -------------------------------------------------------------------------------- 1 | Microsoft Office User Microsoft Office User -------------------------------------------------------------------------------- /flaskstarter/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .app import create_app -------------------------------------------------------------------------------- /flaskstarter/app.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from flask import Flask 4 | 5 | from .config import DefaultConfig 6 | from .user import Users, UsersAdmin 7 | from .settings import settings 8 | from .tasks import tasks 9 | from .frontend import frontend, ContactUsAdmin 10 | from .extensions import db, mail, cache, login_manager, admin, mongo 11 | from .utils import pretty_date 12 | import logging 13 | import os 14 | from datetime import timedelta 15 | 16 | from pymongo import MongoClient 17 | from .model.umap import UmapView 18 | from .model.meta import MetaView 19 | 20 | # For import * 21 | __all__ = ['create_app'] 22 | 23 | DEFAULT_BLUEPRINTS = ( 24 | frontend, 25 | settings, 26 | tasks 27 | ) 28 | 29 | logging.basicConfig(level=logging.DEBUG, 30 | format='[%(asctime)s]: {} %(levelname)s %(message)s'.format(os.getpid()), 31 | datefmt='%Y-%m-%d %H:%M:%S', 32 | handlers=[logging.StreamHandler()]) 33 | 34 | logger = logging.getLogger() 35 | 36 | 37 | def create_app(config=None, app_name=None, blueprints=None): 38 | # Create a Flask app 39 | 40 | if app_name is None: 41 | app_name = DefaultConfig.PROJECT 42 | if blueprints is None: 43 | blueprints = DEFAULT_BLUEPRINTS 44 | 45 | app = Flask(app_name, 46 | instance_relative_config=True) 47 | 48 | # Add 0627 by junyi 49 | # app.config['LOGIN_DISABLED'] = True 50 | app.config['SECRET_KEY'] = os.urandom(24) 51 | app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(minutes=30) 52 | 53 | # add db 54 | 55 | 56 | configure_app(app, config) 57 | configure_hook(app) 58 | configure_blueprints(app, blueprints) 59 | configure_extensions(app) 60 | configure_logging(app) 61 | configure_template_filters(app) 62 | configure_error_handlers(app) 63 | 64 | return app 65 | 66 | 67 | def configure_app(app, config=None): 68 | # Different ways of configurations i.e local or production 69 | logger.info(f"Starting app in %s environment" % os.getenv('FLASK_ENV')) 70 | flask_env = os.environ.get('FLASK_ENV') 71 | if 'development' in flask_env: 72 | print('configuring for development') 73 | app.config.from_object(DefaultConfig) 74 | # if exists under root ./instance/X.cfg 75 | # app.config.from_pyfile('secret_config.cfg') 76 | # print(app.instance_path) 77 | if config: 78 | app.config.from_object(config) 79 | 80 | 81 | def configure_extensions(app): 82 | 83 | # flask-sqlalchemy 84 | db.init_app(app) 85 | 86 | # flask-mail 87 | mail.init_app(app) 88 | 89 | # flask-cache 90 | cache.init_app(app) 91 | 92 | # flask-admin 93 | # sqlite db data 94 | admin.add_view(ContactUsAdmin(db.session)) 95 | admin.add_view(UsersAdmin(db.session)) 96 | 97 | # mongo db data 98 | admin.add_view(UmapView(mongo['umap'])) 99 | admin.add_view(MetaView(mongo['single_cell_meta_country'])) 100 | 101 | admin.init_app(app) 102 | 103 | @login_manager.user_loader 104 | def load_user(id): 105 | return Users.query.get(id) 106 | login_manager.setup_app(app) 107 | 108 | 109 | def configure_blueprints(app, blueprints): 110 | # Configure blueprints in views 111 | 112 | for blueprint in blueprints: 113 | app.register_blueprint(blueprint) 114 | 115 | 116 | def configure_template_filters(app): 117 | 118 | @app.template_filter() 119 | def _pretty_date(value): 120 | return pretty_date(value) 121 | 122 | @app.template_filter() 123 | def format_date(value, format='%Y-%m-%d'): 124 | return value.strftime(format) 125 | 126 | 127 | def configure_logging(app): 128 | # Configure logging 129 | 130 | if app.debug: 131 | # Skip debug and test mode. Better check terminal output. 132 | return 133 | 134 | # TODO: production loggers for (info, email, etc) 135 | 136 | 137 | def configure_hook(app): 138 | @app.before_request 139 | def before_request(): 140 | pass 141 | 142 | 143 | def configure_error_handlers(app): 144 | 145 | @app.errorhandler(403) 146 | def forbidden_page(error): 147 | return "Oops! You don't have permission to access this page.", 403 148 | 149 | @app.errorhandler(404) 150 | def page_not_found(error): 151 | return "Opps! Page not found.", 404 152 | 153 | @app.errorhandler(500) 154 | def server_error_page(error): 155 | return "Oops! Internal server error. Please try after sometime.", 500 156 | 157 | -------------------------------------------------------------------------------- /flaskstarter/config.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | from .utils import TMP_FOLDER 5 | class BaseConfig(object): 6 | # Change these settings as per your needs 7 | 8 | PROJECT = "flaskstarter" 9 | PROJECT_NAME = "flaskstarter.domain" 10 | PROJECT_ROOT = os.path.abspath(os.path.dirname(os.path.dirname(__file__))) 11 | 12 | BASE_URL = "https://yourdomain-flaskstarter.domain" 13 | ADMIN_EMAILS = ['admin@flaskstarter.domain'] 14 | 15 | DEBUG = False 16 | TESTING = False 17 | 18 | SECRET_KEY = 'always-change-this-secret-key-with-random-alpha-nums' 19 | HOST = '0.0.0.0' 20 | 21 | 22 | class DefaultConfig(BaseConfig): 23 | FLASK_ENV = 'development' 24 | DEBUG = True 25 | 26 | # Flask-Sqlalchemy 27 | SQLALCHEMY_ECHO = False 28 | SQLALCHEMY_TRACK_MODIFICATIONS = False 29 | 30 | # SQLITE for dev 31 | SQLALCHEMY_DATABASE_URI = 'sqlite:///' + TMP_FOLDER + '/db.sqlite' 32 | 33 | 34 | 35 | # POSTGRESQL for dev 36 | # SQLALCHEMY_DATABASE_URI = 'postgresql+psycopg2://postgres:pass@ip/dbname' 37 | 38 | # Flask-cache 39 | CACHE_TYPE = 'simple' 40 | CACHE_DEFAULT_TIMEOUT = 60 41 | 42 | # Flask-mail 43 | MAIL_DEBUG = False 44 | MAIL_SERVER = "smtp.gmail.com" # something like 'smtp.gmail.com' 45 | MAIL_PORT = 465 46 | MAIL_USE_TLS = False 47 | MAIL_USE_SSL = True 48 | 49 | # Keep these in instance folder or in env variables 50 | MAIL_USERNAME = "covidscope@gmail.com" 51 | MAIL_PASSWORD = "szhslqcuvvoqadwo" 52 | MAIL_DEFAULT_SENDER = MAIL_USERNAME 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /flaskstarter/decorators.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from functools import wraps 4 | 5 | from flask import abort 6 | from flask_login import current_user 7 | 8 | 9 | def admin_required(f): 10 | @wraps(f) 11 | def decorated_function(*args, **kwargs): 12 | if not current_user.is_admin(): 13 | abort(403) 14 | return f(*args, **kwargs) 15 | return decorated_function 16 | -------------------------------------------------------------------------------- /flaskstarter/emails/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from flask_mail import Message 4 | 5 | from ..extensions import mail 6 | 7 | 8 | async def send_async_email(subject, html, send_to): 9 | """ send mail in async mode""" 10 | 11 | message = Message(subject=subject, html=html, recipients=[send_to]) 12 | await mail.send(message) 13 | -------------------------------------------------------------------------------- /flaskstarter/extensions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from flask_sqlalchemy import SQLAlchemy 4 | from flask_mail import Mail 5 | from flask_caching import Cache 6 | from flask_login import LoginManager 7 | from flask_admin import Admin, AdminIndexView 8 | from flask_admin.menu import MenuLink 9 | from pymongo import MongoClient 10 | # connect to default sqlite db 11 | db = SQLAlchemy() 12 | 13 | # connect to mongo db 14 | client = MongoClient('mongodb://localhost:27017/', connect=False) 15 | mongo = client.cov19atlas_test 16 | # scfeature = client.scfeature 17 | 18 | mail = Mail() 19 | 20 | cache = Cache() 21 | 22 | login_manager = LoginManager() 23 | login_manager.login_view = "frontend.login" 24 | login_manager.login_message = "Please login or signup to add a new trip" 25 | 26 | 27 | class HomeView(AdminIndexView): 28 | def is_visible(self): 29 | return False 30 | 31 | # Config default flask-admin view 32 | 33 | 34 | admin = Admin(name='Flask-Starter Admin', template_mode='bootstrap3', index_view=HomeView(name='Home')) 35 | 36 | admin.add_link(MenuLink(name='Back to Dashboard', url='/dashboard', icon_type='glyph', icon_value='glyphicon-circle-arrow-left')) 37 | 38 | admin.add_link(MenuLink(name='Logout', url='/logout', icon_type='glyph', icon_value='glyphicon-log-out')) 39 | -------------------------------------------------------------------------------- /flaskstarter/frontend/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .models import ContactUs, ContactUsAdmin 4 | 5 | from .views import frontend 6 | -------------------------------------------------------------------------------- /flaskstarter/frontend/forms.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from flask import Markup 4 | 5 | from flask_wtf import FlaskForm 6 | from wtforms import (ValidationError, HiddenField, BooleanField, TextField, 7 | PasswordField, SubmitField, TextAreaField) 8 | from wtforms.validators import Required, Length, EqualTo, Email 9 | from wtforms.fields.html5 import EmailField 10 | 11 | from ..utils import (NAME_LEN_MIN, NAME_LEN_MAX, PASSWORD_LEN_MIN, 12 | PASSWORD_LEN_MAX) 13 | 14 | from ..user import Users 15 | 16 | terms_html = Markup('Terms of service') 17 | 18 | 19 | class LoginForm(FlaskForm): 20 | next = HiddenField() 21 | login = TextField(u'Email', [Required()]) 22 | password = PasswordField('Password', [Required(), 23 | Length(PASSWORD_LEN_MIN, 24 | PASSWORD_LEN_MAX)]) 25 | remember = BooleanField(' Remember me') 26 | submit = SubmitField(' Sign in') 27 | 28 | 29 | class SignupForm(FlaskForm): 30 | next = HiddenField() 31 | name = TextField(u'Name', [Required(), Length(NAME_LEN_MIN, NAME_LEN_MAX)]) 32 | email = EmailField(u'Email', [Required(), Email()]) 33 | password = PasswordField(u'Password', 34 | [Required(), Length(PASSWORD_LEN_MIN, PASSWORD_LEN_MAX)], 35 | description=u' 6 or more characters.') 36 | agree = BooleanField(u' Agree to the ' + terms_html, [Required()]) 37 | submit = SubmitField(' Sign up') 38 | 39 | def validate_email(self, field): 40 | if Users.query.filter_by(email=field.data).first() is not None: 41 | raise ValidationError(u'This email is taken') 42 | 43 | 44 | class RecoverPasswordForm(FlaskForm): 45 | email = EmailField(u'Your email', [Email()]) 46 | submit = SubmitField(' Send') 47 | 48 | 49 | class ChangePasswordForm(FlaskForm): 50 | email_activation_key = HiddenField() 51 | email = HiddenField() 52 | password = PasswordField(u'Password', [Required()]) 53 | password_again = PasswordField(u'Password again', [EqualTo('password', message="Passwords don't match")]) 54 | submit = SubmitField('Save') 55 | 56 | 57 | class ContactUsForm(FlaskForm): 58 | name = TextField(u'Name', [Required(), Length(max=64)]) 59 | email = EmailField(u'Your Email', [Required(), Email(), Length(max=64)]) 60 | subject = TextField(u'Subject', [Required(), Length(5, 128)]) 61 | message = TextAreaField(u'Your Message', [Required(), Length(10, 1024)]) 62 | submit = SubmitField('Submit') 63 | -------------------------------------------------------------------------------- /flaskstarter/frontend/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from sqlalchemy import Column 4 | 5 | from ..extensions import db 6 | from ..utils import get_current_time 7 | 8 | from flask_login import current_user 9 | from flask_admin.contrib import sqla 10 | 11 | 12 | class ContactUs(db.Model): 13 | 14 | __tablename__ = 'contactus' 15 | 16 | id = Column(db.Integer, primary_key=True) 17 | name = Column(db.String(64), nullable=False) 18 | email = Column(db.String(64), nullable=False) 19 | subject = Column(db.String(128), nullable=False) 20 | message = Column(db.String(2048), nullable=False) 21 | 22 | received_time = Column(db.DateTime, default=get_current_time) 23 | 24 | def __unicode__(self): 25 | _str = '%s. %s %s' % (self.id, self.name, self.email) 26 | return str(_str) 27 | 28 | 29 | # Customized ContactUs model admin 30 | class ContactUsAdmin(sqla.ModelView): 31 | column_sortable_list = ('id', 'name', 'email', 'received_time') 32 | column_filters = ('id', 'name', 'email', 'received_time') 33 | 34 | def __init__(self, session): 35 | super(ContactUsAdmin, self).__init__(ContactUs, session) 36 | 37 | def is_accessible(self): 38 | if current_user.role == 'admin': 39 | return current_user.is_authenticated() 40 | -------------------------------------------------------------------------------- /flaskstarter/frontend/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from uuid import uuid4 4 | 5 | from itsdangerous import URLSafeSerializer 6 | 7 | from flask import (Blueprint, render_template, current_app, request, 8 | flash, url_for, redirect) 9 | from flask_login import (login_required, login_user, current_user, 10 | logout_user, login_fresh) 11 | 12 | 13 | from ..tasks import MyTaskForm 14 | from ..user import Users, ACTIVE 15 | from ..extensions import db, login_manager, mongo 16 | from .forms import (SignupForm, LoginForm, RecoverPasswordForm, 17 | ChangePasswordForm, ContactUsForm) 18 | from .models import ContactUs 19 | from ..utils import TMP_FOLDER 20 | from ..emails import send_async_email 21 | import sqlite3 22 | from datetime import datetime 23 | import os 24 | import pandas as pd 25 | from bson.son import SON 26 | import pprint 27 | 28 | frontend = Blueprint('frontend', __name__) 29 | 30 | @frontend.route('/dashboard') 31 | @login_required 32 | def dashboard(): 33 | 34 | _task_form = MyTaskForm() 35 | 36 | return render_template('dashboard/dashboard.html', task_form=_task_form, _active_dash=True) 37 | 38 | 39 | # def get_all_study_meta(): 40 | # res = list(mongo.pbmc_all_study_meta_v4.find()) 41 | # print(res[1]) 42 | # #uniq_field = mongo.single_cell_meta.aggregate([{"$group": {"_id": '$%s' % field_name}}]); 43 | # #key = [r['_id'] for r in uniq_field] 44 | 45 | return res 46 | 47 | 48 | def get_field_count(): 49 | pipeline = [ 50 | {"$unwind": "$level2"}, 51 | {"$group": {"_id": "$level2", "count": {"$sum": 1}}}, 52 | {"$project": SON([("count", 1), ("_id", 1)])} 53 | ] 54 | 55 | res = list(mongo.single_cell_meta.aggregate(pipeline)) 56 | 57 | # for item in res: 58 | # print(item) 59 | # print(item['_id']) 60 | # print(item["count"]) 61 | return res 62 | 63 | # Replace old get field count method 64 | def get_celltype_count(): 65 | # db.createCollection('stats_celltype_count') 66 | # db.stats_celltype_count.insertMany(db.single_cell_meta_v4.aggregate([{"$group": {"_id": "$level2", "count":{"$sum": 1}}}]).toArray()) 67 | collection_name = 'stats_celltype_count' 68 | res = list(mongo.stats_celltype_count.find()) 69 | 70 | return res 71 | 72 | def get_overview(): 73 | res = list(mongo.stats_meta_overview.find()) 74 | 75 | return res 76 | 77 | # Load landing page data 78 | filepath = os.path.abspath(os.getcwd()) 79 | filename = os.path.join( 80 | filepath, 'flaskstarter/PBMC_study_meta/PBMC_study_meta.csv') 81 | df = pd.read_csv(filename) 82 | df = df.dropna(subset=['Country Code']) 83 | 84 | @frontend.route('/', methods=["GET", "POST"]) 85 | def index(): 86 | data = df.to_dict() 87 | # fdataset = get_all_study_meta() # remove this from architecture version 88 | res = get_celltype_count() 89 | #counting = get_overview() 90 | # counts = counting 91 | #pprint.pprint(res) 92 | if current_user.is_authenticated: 93 | return render_template('tasks/landing.html', data=data, _active_home=True, fcelltype=res) 94 | return render_template('tasks/landing.html', data=data, _active_home=True, fcelltype=res) 95 | # @frontend.route('/') 96 | # def index(): 97 | # # current_app.logger.debug('debug') 98 | # if current_user.is_authenticated: 99 | # return redirect(url_for('tasks.table_view')) 100 | 101 | # return render_template('tasks/landing.html', _active_home=True) 102 | 103 | @frontend.route('/tutorial', methods=['GET', 'POST']) 104 | def tutorial(): 105 | return render_template('frontend/tutorial.html') 106 | 107 | 108 | @frontend.route('/data', methods=['GET', 'POST']) 109 | def data(): 110 | return render_template('frontend/data.html') 111 | 112 | @frontend.route('/contact-us', methods=['GET', 'POST']) 113 | def contact_us(): 114 | form = ContactUsForm() 115 | 116 | if form.validate_on_submit(): 117 | _contact = ContactUs() 118 | form.populate_obj(_contact) 119 | db.session.add(_contact) 120 | db.session.commit() 121 | 122 | flash('Thanks! We\'ll get back to you shortly!', 'success') 123 | 124 | return redirect(url_for('frontend.contact_us')) 125 | 126 | return render_template('frontend/contact_us.html', form=form) 127 | 128 | 129 | @frontend.route('/login', methods=['GET', 'POST']) 130 | def login(): 131 | if current_user.is_authenticated: 132 | return redirect(url_for('frontend.index')) 133 | 134 | form = LoginForm(login=request.args.get('login', None), 135 | next=request.args.get('next', None)) 136 | print("Debug login") 137 | print(request) 138 | 139 | if form.validate_on_submit(): 140 | user, authenticated = Users.authenticate(form.login.data, form.password.data) 141 | print(request.form) 142 | print(request.args) 143 | 144 | if user and authenticated: 145 | 146 | if user.status_code != 2: 147 | flash("Please verify your email address to continue", "danger") 148 | return redirect(url_for('frontend.login')) 149 | 150 | remember = request.form.get('remember') == 'y' 151 | 152 | if login_user(user, remember=remember): 153 | flash("Logged in", 'success') 154 | return redirect(form.next.data or url_for('frontend.index')) 155 | else: 156 | flash('Sorry, invalid login', 'danger') 157 | 158 | return render_template('frontend/login.html', form=form, _active_login=True) 159 | 160 | 161 | @frontend.route('/logout') 162 | @login_required 163 | def logout(): 164 | logout_user() 165 | flash('Logged out', 'success') 166 | return redirect(url_for('frontend.index')) 167 | 168 | 169 | @frontend.route('/signup', methods=['GET', 'POST']) 170 | def signup(): 171 | if current_user.is_authenticated: 172 | return redirect(url_for('frontend.dashboard')) 173 | 174 | form = SignupForm(next=request.args.get('next')) 175 | 176 | if form.validate_on_submit(): 177 | user = Users() 178 | user.status_code = 2 179 | user.account_type = 0 180 | form.populate_obj(user) 181 | 182 | db.session.add(user) 183 | db.session.commit() 184 | 185 | confirm_user_mail(form.name.data, form.email.data) 186 | 187 | flash(u'Registration successful! Please verify!', 'success') 188 | return redirect(url_for('frontend.login')) 189 | 190 | return render_template('frontend/signup.html', form=form, 191 | _active_signup=True) 192 | 193 | 194 | def confirm_user_mail(name, email): 195 | s = URLSafeSerializer('serliaizer_code') 196 | key = s.dumps([name, email]) 197 | 198 | subject = 'Confirm your account for ' + current_app.config['PROJECT_NAME'] 199 | url = url_for('frontend.confirm_account', secretstring=key, _external=True) 200 | html = render_template('macros/_confirm_account.html', 201 | project=current_app.config['PROJECT_NAME'], 202 | url=url) 203 | 204 | send_async_email(subject, html, email) 205 | 206 | 207 | @frontend.route('/confirm_account/', methods=['GET', 'POST']) 208 | def confirm_account(secretstring): 209 | s = URLSafeSerializer('serliaizer_code') 210 | uname, uemail = s.loads(secretstring) 211 | user = Users.query.filter_by(name=uname).first() 212 | user.status_code = ACTIVE 213 | db.session.add(user) 214 | db.session.commit() 215 | flash(u'Your account was confirmed succsessfully!!!', 'success') 216 | return redirect(url_for('frontend.login')) 217 | 218 | 219 | @frontend.route('/change_password', methods=['GET', 'POST']) 220 | def change_password(): 221 | 222 | if current_user.is_authenticated: 223 | if not login_fresh(): 224 | return login_manager.needs_refresh() 225 | 226 | form = ChangePasswordForm(email_activation_key=request.values["email_activation_key"], 227 | email=request.values["email"]) 228 | 229 | if form.validate_on_submit(): 230 | update_password(form.email.data, form.email_activation_key.data, form.password.data) 231 | flash(u"Your password has been changed, log in again", "success") 232 | return redirect(url_for("frontend.login")) 233 | 234 | return render_template("frontend/change_password.html", form=form) 235 | 236 | 237 | def update_password(email, email_activation_key, password): 238 | user = Users.query.filter_by(email_activation_key=email_activation_key, email=email).first() 239 | user.password = password 240 | user.email_activation_key = None 241 | db.session.add(user) 242 | db.session.commit() 243 | 244 | 245 | @frontend.route('/reset_password', methods=['GET', 'POST']) 246 | def reset_password(): 247 | form = RecoverPasswordForm() 248 | 249 | if form.validate_on_submit(): 250 | user = Users.query.filter_by(email=form.email.data).first() 251 | 252 | if user: 253 | flash('Please see your email for instructions on how to access your account', 'success') 254 | 255 | user.email_activation_key = str(uuid4()) 256 | db.session.add(user) 257 | db.session.commit() 258 | 259 | subject = 'Reset your password in ' + current_app.config['PROJECT_NAME'] 260 | url = url_for('frontend.change_password', email=user.email, 261 | email_activation_key=user.email_activation_key, _external=True) 262 | html = render_template('macros/_reset_password.html', project=current_app.config['PROJECT_NAME'], 263 | name=user.name, url=url) 264 | 265 | send_async_email(subject, html, user.email) 266 | 267 | return render_template('frontend/reset_password.html', form=form) 268 | else: 269 | flash('Sorry, no user found for that email address', 'danger') 270 | 271 | return render_template('frontend/reset_password.html', form=form) 272 | 273 | 274 | @frontend.route('/terms') 275 | def terms(): 276 | return "To be updated soon.." 277 | 278 | 279 | @frontend.route('/about-us') 280 | def about_us(): 281 | return "To be updated soon.." 282 | -------------------------------------------------------------------------------- /flaskstarter/model/meta.py: -------------------------------------------------------------------------------- 1 | from wtforms import form, fields 2 | import flask_admin.contrib.pymongo as admin_py 3 | from flask_admin.contrib.pymongo import filters 4 | 5 | class MetaForm(form.Form): 6 | id = fields.StringField('id') 7 | meta_sample_id2 = fields.StringField('meta_sample_id2') 8 | meta_patient_id = fields.StringField('meta_patient_id') 9 | meta_age = fields.StringField('meta_age') 10 | level2 = fields.StringField('level2') 11 | meta_severity = fields.StringField('meta_severity') 12 | meta_dataset = fields.StringField('meta_dataset') 13 | 14 | 15 | # View 16 | class MetaView(admin_py.ModelView): 17 | column_list = ('id', 'meta_sample_id2', 'meta_patient_id', 'meta_age', 'level2', 'meta_severity', 'meta_dataset') 18 | form = MetaForm # Specifies Data Model 19 | 20 | column_sortable_list = ('meta_age', 'meta_patient_id') 21 | 22 | column_filters = (filters.FilterEqual('id', 'id'), 23 | filters.FilterLike('meta_age','meta_age'), 24 | filters.FilterEqual('meta_age','meta_age'),) 25 | 26 | column_searchable_list = ('id', 'meta_sample_id2') -------------------------------------------------------------------------------- /flaskstarter/model/umap.py: -------------------------------------------------------------------------------- 1 | # Data Model 2 | from wtforms import form, fields 3 | import flask_admin.contrib.pymongo as admin_py 4 | from flask_admin.contrib.pymongo.filters import BooleanEqualFilter 5 | 6 | class UmapForm(form.Form): 7 | id = fields.StringField('id') 8 | UMAP1 = fields.StringField('UMAP1') 9 | UMAP2 = fields.StringField('UMAP2') 10 | 11 | # View 12 | 13 | class UmapView(admin_py.ModelView): 14 | column_list = ('id', 'UMAP1', 'UMAP2') 15 | form = UmapForm # Specifies Data Model 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /flaskstarter/settings/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .views import settings 4 | -------------------------------------------------------------------------------- /flaskstarter/settings/forms.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from flask_wtf import FlaskForm 4 | from wtforms.fields.html5 import EmailField 5 | from wtforms import ValidationError, TextField, PasswordField, SubmitField 6 | from wtforms.validators import (Required, Length, EqualTo, Email) 7 | from flask_login import current_user 8 | 9 | from ..user import Users 10 | from ..utils import PASSWORD_LEN_MIN, PASSWORD_LEN_MAX 11 | 12 | 13 | class ProfileForm(FlaskForm): 14 | multipart = True 15 | name = TextField(u'Name', [Length(max=50)]) 16 | email = EmailField(u'Email', [Required(), Email()]) 17 | submit = SubmitField(u'Update') 18 | 19 | 20 | class PasswordForm(FlaskForm): 21 | password = PasswordField('Current password', [Required()]) 22 | new_password = PasswordField('New password', [Required(), Length(PASSWORD_LEN_MIN, PASSWORD_LEN_MAX)]) 23 | password_again = PasswordField('Password again', [Required(), Length(PASSWORD_LEN_MIN, PASSWORD_LEN_MAX), EqualTo('new_password')]) 24 | submit = SubmitField(u'Update') 25 | 26 | def validate_password(form, field): 27 | user = Users.get_by_id(current_user.id) 28 | if not user.check_password(field.data): 29 | raise ValidationError("Password is wrong") 30 | -------------------------------------------------------------------------------- /flaskstarter/settings/views.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from flask import Blueprint, render_template, request, flash 4 | from flask_login import login_required, current_user 5 | 6 | from ..extensions import db 7 | from ..user import Users 8 | from .forms import ProfileForm, PasswordForm 9 | 10 | 11 | settings = Blueprint('settings', __name__, url_prefix='/settings') 12 | 13 | 14 | @settings.route('/profile', methods=['GET', 'POST']) 15 | @login_required 16 | def profile(): 17 | user = Users.query.filter_by(email=current_user.email).first_or_404() 18 | form = ProfileForm(obj=user, 19 | email=current_user.email, 20 | role_code=current_user.role_code, 21 | status_code=current_user.status_code) 22 | 23 | if form.validate_on_submit(): 24 | 25 | form.populate_obj(user) 26 | 27 | db.session.add(user) 28 | db.session.commit() 29 | 30 | flash('Profile Changes Saved!', 'success') 31 | 32 | return render_template('settings/profile.html', 33 | user=user, active="profile", form=form) 34 | 35 | 36 | @settings.route('/password', methods=['GET', 'POST']) 37 | @login_required 38 | def password(): 39 | user = Users.query.filter_by(email=current_user.email).first_or_404() 40 | form = PasswordForm(next=request.args.get('next')) 41 | 42 | if form.validate_on_submit(): 43 | form.populate_obj(user) 44 | user.password = form.new_password.data 45 | 46 | db.session.add(user) 47 | db.session.commit() 48 | 49 | flash('Password updated.', 'success') 50 | 51 | return render_template('settings/password.html', 52 | user=user, active="password", form=form) 53 | -------------------------------------------------------------------------------- /flaskstarter/static/D24H_Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiyin/scopeplus/3d0f9f74d966390f101fabb38d1952f5e8c90ba6/flaskstarter/static/D24H_Logo.png -------------------------------------------------------------------------------- /flaskstarter/static/Dataset_Banner.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiyin/scopeplus/3d0f9f74d966390f101fabb38d1952f5e8c90ba6/flaskstarter/static/Dataset_Banner.jpeg -------------------------------------------------------------------------------- /flaskstarter/static/Group 36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiyin/scopeplus/3d0f9f74d966390f101fabb38d1952f5e8c90ba6/flaskstarter/static/Group 36.png -------------------------------------------------------------------------------- /flaskstarter/static/Group 37.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiyin/scopeplus/3d0f9f74d966390f101fabb38d1952f5e8c90ba6/flaskstarter/static/Group 37.png -------------------------------------------------------------------------------- /flaskstarter/static/Group 38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiyin/scopeplus/3d0f9f74d966390f101fabb38d1952f5e8c90ba6/flaskstarter/static/Group 38.png -------------------------------------------------------------------------------- /flaskstarter/static/Group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiyin/scopeplus/3d0f9f74d966390f101fabb38d1952f5e8c90ba6/flaskstarter/static/Group.png -------------------------------------------------------------------------------- /flaskstarter/static/Login_Banner.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiyin/scopeplus/3d0f9f74d966390f101fabb38d1952f5e8c90ba6/flaskstarter/static/Login_Banner.jpeg -------------------------------------------------------------------------------- /flaskstarter/static/Mask_Group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiyin/scopeplus/3d0f9f74d966390f101fabb38d1952f5e8c90ba6/flaskstarter/static/Mask_Group.png -------------------------------------------------------------------------------- /flaskstarter/static/Pointer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiyin/scopeplus/3d0f9f74d966390f101fabb38d1952f5e8c90ba6/flaskstarter/static/Pointer.png -------------------------------------------------------------------------------- /flaskstarter/static/Vector.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /flaskstarter/static/chevron-down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiyin/scopeplus/3d0f9f74d966390f101fabb38d1952f5e8c90ba6/flaskstarter/static/chevron-down.png -------------------------------------------------------------------------------- /flaskstarter/static/chevron-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /flaskstarter/static/download-file 1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /flaskstarter/static/exclamation-mark-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiyin/scopeplus/3d0f9f74d966390f101fabb38d1952f5e8c90ba6/flaskstarter/static/exclamation-mark-64.png -------------------------------------------------------------------------------- /flaskstarter/static/explore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiyin/scopeplus/3d0f9f74d966390f101fabb38d1952f5e8c90ba6/flaskstarter/static/explore.png -------------------------------------------------------------------------------- /flaskstarter/static/facebook.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /flaskstarter/static/folder-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiyin/scopeplus/3d0f9f74d966390f101fabb38d1952f5e8c90ba6/flaskstarter/static/folder-2.png -------------------------------------------------------------------------------- /flaskstarter/static/folder-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiyin/scopeplus/3d0f9f74d966390f101fabb38d1952f5e8c90ba6/flaskstarter/static/folder-3.png -------------------------------------------------------------------------------- /flaskstarter/static/folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiyin/scopeplus/3d0f9f74d966390f101fabb38d1952f5e8c90ba6/flaskstarter/static/folder.png -------------------------------------------------------------------------------- /flaskstarter/static/link icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /flaskstarter/static/linkin.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /flaskstarter/static/loading-icon.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiyin/scopeplus/3d0f9f74d966390f101fabb38d1952f5e8c90ba6/flaskstarter/static/loading-icon.gif -------------------------------------------------------------------------------- /flaskstarter/static/location icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /flaskstarter/static/mail icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /flaskstarter/static/microscope.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiyin/scopeplus/3d0f9f74d966390f101fabb38d1952f5e8c90ba6/flaskstarter/static/microscope.png -------------------------------------------------------------------------------- /flaskstarter/static/newspaper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiyin/scopeplus/3d0f9f74d966390f101fabb38d1952f5e8c90ba6/flaskstarter/static/newspaper.png -------------------------------------------------------------------------------- /flaskstarter/static/tel icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /flaskstarter/tasks/.views.py.swp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiyin/scopeplus/3d0f9f74d966390f101fabb38d1952f5e8c90ba6/flaskstarter/tasks/.views.py.swp -------------------------------------------------------------------------------- /flaskstarter/tasks/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .views import tasks 4 | from .forms import MyTaskForm -------------------------------------------------------------------------------- /flaskstarter/tasks/forms.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from flask_wtf import FlaskForm 4 | from wtforms import SubmitField, TextAreaField, SelectField 5 | from wtforms.validators import Required, Length 6 | 7 | class MyTaskForm(FlaskForm): 8 | task = TextAreaField(u'Your Task', [Required(), Length(5, 2048)]) 9 | submit = SubmitField(u'Save Task') 10 | 11 | class UmapForm(FlaskForm): 12 | def __init__(self,choices=[('def','Default')]): 13 | self.cell_color = SelectField('cell_color',choices=choices) -------------------------------------------------------------------------------- /flaskstarter/templates/admin/index.html: -------------------------------------------------------------------------------- 1 | {% extends 'admin/master.html' %} 2 | 3 | {% block body %} 4 |

Hello, Welcome to Flask-Starter's Admin Dashboard

5 | {% endblock %} -------------------------------------------------------------------------------- /flaskstarter/templates/dashboard/dashboard.html: -------------------------------------------------------------------------------- 1 | {% from "macros/_form.html" import render_form %} 2 | 3 | {% set page_title = 'Dashboard' %} 4 | 5 | {% extends 'layouts/base.html' %} 6 | 7 | {% block body %} 8 |
9 |
10 |
11 | Home 12 |
13 |
14 |
15 | {% endblock %} 16 | -------------------------------------------------------------------------------- /flaskstarter/templates/frontend/change_password.html: -------------------------------------------------------------------------------- 1 | {% from "macros/_form.html" import render_form %} 2 | 3 | {% set page_title = 'Change Password' %} 4 | 5 | {% extends 'layouts/base.html' %} 6 | 7 | {% block body %} 8 |
9 |
10 |
11 | {{ render_form(url_for('frontend.change_password'), form)}} 12 |
13 |
14 |
15 | {% endblock %} -------------------------------------------------------------------------------- /flaskstarter/templates/frontend/contact_us.html: -------------------------------------------------------------------------------- 1 | {% from "macros/_form.html" import render_form %} 2 | 3 | {% set page_title = 'Contact us' %} 4 | 5 | {% extends 'layouts/base.html' %} 6 | 7 | {% block body %} 8 | 17 |
18 |
19 |
20 |
21 |
22 |
23 | Tel 24 |
25 |
26 | +852 3910 3800 27 |
28 |
29 |
30 |
31 | Fax 32 |
33 |
34 | +852 2793 3233 35 |
36 |
37 |
38 |
39 | Email 40 |
41 |
42 | covidscope@gmail.com 43 |
44 |
45 |
46 |
47 | Website 48 |
49 | 52 |
53 |
54 |
55 |
56 | Address 57 |
58 |
59 |
Units 1201-1206, 1223 & 1225, 12/F, Building 19W, 19 Science Park West Avenue, Hong 60 | Kong Science Park, 61 | Pak Shek Kok, New Territories, Hong Kong.
62 | 63 |
64 |
65 |
68 |
69 | 70 | 71 | 72 |
73 | 74 | 75 | 82 | {% endblock %} -------------------------------------------------------------------------------- /flaskstarter/templates/frontend/landing.html: -------------------------------------------------------------------------------- 1 | {% from "macros/_form.html" import render_form %} 2 | 3 | {% set page_title = 'Home' %} 4 | 5 | {% extends 'layouts/base.html' %} 6 | 7 | {% block body %} 8 | {% include 'layouts/banner.html' %} 9 |
10 |
11 |

Home

12 |
13 | 14 |
15 | {% endblock %} 16 | 17 | {% block scripts %} 18 | 19 | 20 | 21 | 22 | {% endblock %} -------------------------------------------------------------------------------- /flaskstarter/templates/frontend/login.html: -------------------------------------------------------------------------------- 1 | {% from "macros/_form.html" import render_form %} 2 | 3 | {% set page_title = 'Login' %} 4 | 5 | {% extends 'layouts/base.html' %} 6 | 7 | {% block body %} 8 |
9 |
10 |
11 |
12 |

Sign in

13 |
14 | {{ render_form(url_for('frontend.login'), form)}} 15 |
16 |

Don't have an account? Sign up

18 |
19 |
20 | Forgot password? 21 |
22 |
23 | 24 | 25 | 26 |
27 |
28 | {% endblock %} -------------------------------------------------------------------------------- /flaskstarter/templates/frontend/reset_password.html: -------------------------------------------------------------------------------- 1 | {% from "macros/_form.html" import render_form %} 2 | 3 | {% set page_title = 'Reset Password' %} 4 | 5 | {% extends 'layouts/base.html' %} 6 | 7 | {% block body %} 8 |
9 |
10 |
11 | {{ render_form(url_for('frontend.reset_password'), form)}} 12 |
13 |
14 |
15 | {% endblock %} -------------------------------------------------------------------------------- /flaskstarter/templates/frontend/signup.html: -------------------------------------------------------------------------------- 1 | {% from "macros/_form.html" import render_form %} 2 | 3 | {% set page_title = 'Sign up' %} 4 | 5 | {% extends 'layouts/base.html' %} 6 | 7 | {% block body %} 8 |
9 |
10 |

Sign up

11 | {{ render_form(url_for('frontend.signup'), form)}} 12 |
13 |
14 | {% endblock %} -------------------------------------------------------------------------------- /flaskstarter/templates/frontend/tutorial.html: -------------------------------------------------------------------------------- 1 | {% set page_title = 'Tutorial' %} 2 | 3 | {% extends 'layouts/base.html' %} 4 | 5 | {% block body %} 6 |
7 |
8 |
9 |
Tutorial
10 |
11 |
12 |
13 |
Guide to use Covidscope Webserver(Video)
14 |
15 | 16 |
17 |
19 |
20 | Also find the down-stream analysis tutorial in following link after you downloaded data from Covidscope .... 21 |
22 | 26 |
27 |
28 | 40 | 52 |
53 | 54 |
55 |
56 | 57 | 58 | {% endblock %} -------------------------------------------------------------------------------- /flaskstarter/templates/layouts/banner.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 26 | 27 |
28 |
29 |
30 |
31 |
32 |

50000000

33 |
34 |
Cells
35 |
36 |
37 |
38 |
39 |

1000

40 |
41 |
Donors
42 |
43 |
44 |
45 |
46 |

1

47 |
48 |
Data sets
49 |
50 |
51 |
52 |
53 |

Oct, 2022

54 |
55 |
Data date processed
56 |
57 |
58 | 59 |
60 | 61 | 62 | -------------------------------------------------------------------------------- /flaskstarter/templates/layouts/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {% block title %}{{ page_title|default('Flask-Starter') }}{% endblock %} - Covidscope 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 25 | 26 | 28 | 29 | 30 | 31 | 32 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 51 | {% include 'macros/_flash_msg.html' %} 52 | 53 | {% block body %} 54 | 55 | {% endblock %} 56 | 57 | 59 | 60 | 61 | 62 | 64 | 66 | 67 | 68 | 69 | 70 | 71 | 73 | 74 | {% block scripts %}{% endblock %} 75 | 76 | 77 | {% include 'layouts/footer.html' %} 78 | 79 | -------------------------------------------------------------------------------- /flaskstarter/templates/layouts/citation.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | APA 7th edition 5 |
6 |
7 |
8 |
9 |
10 | 2.5 million Single Cell (2022, February 23). The Laboratory of Data Discovery for Health. 11 |
12 |
14 | 15 |
16 | 17 | 20 |
21 | Citation 22 |
23 |
24 |
-------------------------------------------------------------------------------- /flaskstarter/templates/layouts/footer.html: -------------------------------------------------------------------------------- 1 | 94 | 95 | {% block scripts %} 96 | 97 | {% endblock %} -------------------------------------------------------------------------------- /flaskstarter/templates/layouts/header.html: -------------------------------------------------------------------------------- 1 | {% if current_user.is_authenticated %} 2 | 38 | 39 | {% else %} 40 | 75 | {% endif %} -------------------------------------------------------------------------------- /flaskstarter/templates/macros/_confirm_account.html: -------------------------------------------------------------------------------- 1 |

You have created an account on {{ project }}.

2 | 3 |

To activate your account, click on the link below (or copy and paste the URL into your browser):

4 | 5 | Click to Activate 6 | 7 |

Best Wishes,

8 |

{{ project }} Team

9 | -------------------------------------------------------------------------------- /flaskstarter/templates/macros/_flash_msg.html: -------------------------------------------------------------------------------- 1 | {% with messages = get_flashed_messages(with_categories=true) %} 2 | {% if messages %} 3 | {% for category, message in messages %} 4 | 7 | {% endfor %} 8 | {% endif %} 9 | {% endwith %} -------------------------------------------------------------------------------- /flaskstarter/templates/macros/_form.html: -------------------------------------------------------------------------------- 1 | {% macro render_checkbox(field) %} 2 |
3 | 6 | {{ field.description }} 7 | {% if field.errors -%} 8 | 13 | {%- endif %} 14 |
15 | {% endmacro%} 16 | 17 | {% macro render_radio(field) %} 18 |
19 | {{ field.label(class_="control-label") }} 20 |
21 | {% for subfield in field -%} 22 | 25 | {%- endfor %} 26 | {{ field.description }} 27 | {% if field.errors -%} 28 |
    29 | {% for error in field.errors -%} 30 |
  • {{ error }}
  • 31 | {%- endfor %} 32 |
33 | {%- endif %} 34 |
35 |
36 | {% endmacro %} 37 | 38 | {% macro render_datepicker(field) %} 39 |
40 | 44 |
45 | {{ field() }} 46 | {{ field.description }} 47 | {% if field.errors -%} 48 |
    49 | {% for error in field.errors -%} 50 |
  • {{ error }}
  • 51 | {%- endfor %} 52 |
53 | {%- endif %} 54 |
55 |
56 | {% endmacro %} 57 | 58 | {% macro render_textarea(field) %} 59 |
60 | 64 |
65 | {{ field(class_="form-control", rows="4") }} 66 | {{ field.description }} 67 | {% if field.errors -%} 68 |
    69 | {% for error in field.errors -%} 70 |
  • {{ error }}
  • 71 | {%- endfor %} 72 |
73 | {%- endif %} 74 |
75 |
76 | {% endmacro %} 77 | 78 | {% macro render_file(field) %} 79 |
80 | 84 |
85 | {{ field }} 86 | {{ field.description }} 87 | {% if field.errors -%} 88 |
    89 | {% for error in field.errors -%} 90 |
  • {{ error }}
  • 91 | {%- endfor %} 92 |
93 | {%- endif %} 94 |
95 |
96 | {% endmacro %} 97 | 98 | {% macro render_input(field) %} 99 |
100 | 101 | {{ field(class_="form-control") }} 102 | {{ field.description }} 103 | 104 | {% if field.errors -%} 105 | 110 | {% endif %} 111 |
112 | {% endmacro %} 113 | 114 | {% macro render_action(field) %} 115 |
116 |
117 | {{ field(class_="btn btn-primary") }} 118 |
119 |
120 | {% endmacro %} 121 | 122 | {% macro render_form(url, form, horizontal=False, legend=None, confirm_msg=None, formid=None) %} 123 | {% set idattr = "id=" + formid if formid else "" %} 124 |
127 | {{ form.hidden_tag() }} 128 | {% if legend %} 129 | {{ legend|safe }} 130 | {% endif %} 131 | {% set focus = True %} 132 | {% for field in form %} 133 | {% if field.type != "HiddenField" and field.type != "CSRFTokenField" %} 134 | {% if field.type == "RadioField" %} 135 | {{ render_radio(field) }} 136 | {% elif field.type == "BooleanField" %} 137 | {{ render_checkbox(field) }} 138 | {% elif field.type == "SubmitField" %} 139 | {{ render_action(field) }} 140 | {% elif field.type == "TextAreaField" %} 141 | {{ render_textarea(field) }} 142 | {% elif field.type == "DateField" %} 143 | {{ render_datepicker(field) }} 144 | {% elif field.type == "FileField" %} 145 | {{ render_file(field) }} 146 | {% elif field.type == "TextField" %} 147 | {% if focus %} 148 | {{ render_input(field) }} 149 | {% set focus = False %} 150 | {% else %} 151 | {{ render_input(field) }} 152 | {% endif %} 153 | {% else %} 154 | {{ render_input(field) }} 155 | {% endif %} 156 | {% endif %} 157 | {% endfor %} 158 |
159 | {% endmacro %} 160 | -------------------------------------------------------------------------------- /flaskstarter/templates/macros/_reset_password.html: -------------------------------------------------------------------------------- 1 |

Forgot your password, {{ name }}?

2 | 3 |

{{ project }} received a request to reset the password for your {{ project }} account {{ name }}.

4 | 5 |

If you want to reset your password, click on the link below (or copy and paste the URL into your browser):

6 | 7 | Click to Reset Password 8 | 9 |

This link takes you to a secure page where you can change your password.

10 | 11 |

If you don't want to reset your password, please ignore this message. Your password will not be reset. If you have any concerns, please contact us at {{ project }} Support.

12 | 13 |

Best Wishes,

14 |

{{ project }} Team

15 | -------------------------------------------------------------------------------- /flaskstarter/templates/settings/password.html: -------------------------------------------------------------------------------- 1 | {% from "macros/_form.html" import render_form %} 2 | 3 | {% set page_title = 'Update Password' %} 4 | 5 | {% extends 'layouts/base.html' %} 6 | 7 | {% block body %} 8 |
9 |
10 |

Password

11 | {{ render_form(url_for('settings.password'), form)}} 12 |
13 |
14 | {% include 'layouts/footer.html' %} 15 | {% endblock %} -------------------------------------------------------------------------------- /flaskstarter/templates/settings/profile.html: -------------------------------------------------------------------------------- 1 | {% from "macros/_form.html" import render_form %} 2 | 3 | {% set page_title = 'Update Profile' %} 4 | 5 | {% extends 'layouts/base.html' %} 6 | 7 | {% block body %} 8 |
9 |
10 |

Profile

11 | {{ render_form(url_for('settings.profile'), form)}} 12 |
13 |
14 | {% include 'layouts/footer.html' %} 15 | {% endblock %} -------------------------------------------------------------------------------- /flaskstarter/templates/tasks/add_task.html: -------------------------------------------------------------------------------- 1 | {% from "macros/_form.html" import render_form %} 2 | 3 | {% set page_title = 'Add Task' %} 4 | 5 | {% extends 'layouts/base.html' %} 6 | 7 | {% block body %} 8 | {% include 'layouts/banner.html' %} 9 |
10 |

Contribute

11 |
12 | 5.0 million single cell is build for the benefit of scientific community in health sector. Our team welcomes contributions of raw data of PBMC Single Cells, as long as it is consented for open access to the public. 13 |
14 |
15 |
Data Submission Process
16 |
We currenly accept csv form of dataset. Please follow the following steps to upload data.
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 29 | 30 | 34 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 |
27 | Prepare metadata 28 | 31 | Upload data 32 | 33 | 35 | See and share 36 |
in communication with our teamto a secure data storage areaaccessioned data on the portal
46 |
47 |
48 |
49 | 50 |
51 |
52 |
53 | {% include 'layouts/footer.html' %} 54 | {% endblock %} -------------------------------------------------------------------------------- /flaskstarter/templates/tasks/contribute.html: -------------------------------------------------------------------------------- 1 | {% from "macros/_form.html" import render_form %} 2 | 3 | {% set page_title = 'Contribute' %} 4 | 5 | {% extends 'layouts/base.html' %} 6 | 7 | {% block body %} 8 | 9 |
10 |
11 |
12 |
Upload your data
13 |
Please upload file in xls or 14 | csv format. 15 |
16 |
17 |
18 |
19 |
20 | 22 | 23 |
24 |
25 |
26 |
27 |
28 | 29 |
30 |
31 | 32 | 33 |
34 | 35 |
36 |
37 |
D²4H Single-cell Data Share Life Cycle
38 |
39 | The D²4H Single-cell Data Share provides a platform for sharing 40 | single-cell data. We welcome anyone to contribute on our data sharing 41 | coummuniaty. 42 |
43 |
44 | 45 | 46 | 49 | 52 | 55 | 58 | 61 | 62 | 63 | 66 | 67 | 70 | 71 | 74 | 75 |
47 | 48 | 50 | 51 | 53 | 54 | 56 | 57 | 59 | 60 |
64 |

Users contribute single-cell data

65 |
68 |

D²4H processes the data

69 |
72 |

Users can find suitable data to download

73 |
76 |
77 |
78 |
79 | 80 | 90 | 91 |
92 | 93 | {% endblock %} 94 | {% block scripts %} 95 | 100 | {% endblock %} -------------------------------------------------------------------------------- /flaskstarter/templates/tasks/edit_task.html: -------------------------------------------------------------------------------- 1 | {% from "macros/_form.html" import render_form %} 2 | 3 | {% set page_title = 'Edit Task' %} 4 | 5 | {% extends 'layouts/base.html' %} 6 | 7 | {% block body %} 8 |
9 |
10 |
11 | {{ render_form(url_for('tasks.edit_task', id=task.id), form)}} 12 |
13 |
14 |
15 | {% endblock %} -------------------------------------------------------------------------------- /flaskstarter/templates/tasks/landing.html: -------------------------------------------------------------------------------- 1 | {% from "macros/_form.html" import render_form %} {% set page_title = 'Home' %} 2 | {% extends 'layouts/base.html' %} {% block body %} {% include 3 | 'layouts/banner.html' %} 4 | 5 |
6 | 7 |
8 |
What is Covidscope?
9 |
10 | Covidscope stores and provides over 4.8 million 11 | single-cell data from 20 published single-cell sequencing data sets. Anyone 12 | can explore the insights from our analysis and download the data provided 13 | here for further analysis. 14 |
15 |
16 | 17 | 18 | 21 | 22 | 25 | 26 | 29 | 30 | 31 | 34 | 35 | 38 | 39 | 42 | 43 |
19 | 20 | 23 | 24 | 27 | 28 |
32 |

Insight

33 |
36 |

Explore data

37 |
40 |

Download and analyse

41 |
44 |
45 |
46 |
47 | 48 | 58 | 59 | 103 | {% endblock %} 104 | 105 | {% block scripts %} 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 132 | 251 | 252 | 357 | {% endblock %} -------------------------------------------------------------------------------- /flaskstarter/templates/tasks/my_tasks.html: -------------------------------------------------------------------------------- 1 | {% from "macros/_form.html" import render_form %} 2 | 3 | {% set page_title = 'Explore' %} 4 | 5 | {% extends 'layouts/base.html' %} 6 | 7 | {% block body %} 8 | {% include 'layouts/banner.html' %} 9 |
10 |
11 |

Explore Data

12 |
13 |
14 | 15 |

Fliter    

16 | 17 |
18 | 19 |
20 | 21 | 23 | 24 | 26 | 27 | 29 | 30 | 300 |
301 | 302 |
303 |
304 | 306 |
307 |
308 | 309 |
310 | {% endblock %} 311 | 312 | 313 | {% block scripts %} 314 | 333 | 347 | 356 | 365 | 374 | {% endblock %} -------------------------------------------------------------------------------- /flaskstarter/templates/tasks/show_plot.html: -------------------------------------------------------------------------------- 1 | {% from "macros/_form.html" import render_form %} 2 | 3 | {% set page_title = 'UMAP Plot' %} 4 | 5 | {% extends 'layouts/base.html' %} 6 | 7 | {% block body %} 8 | 9 | 15 | 16 |
17 |
18 |
19 |
UMAP plot
20 |
21 |
22 |
23 | 29 |
30 | 33 | 34 |
35 | 41 |
42 | 45 |
46 |
47 | 48 |
49 | 50 |
51 |
52 | 53 |
54 |
55 | 56 |
57 |
58 | 59 |
60 |
61 |
62 |
63 |
64 |
65 | 66 |
67 | 68 | 69 | {% endblock %} 70 | 71 | 72 | 73 | {% block scripts %} 74 | 75 | 85 | 86 | 87 | 93 | 94 | 95 | 101 | 102 | 103 | 108 | 109 | {% endblock %} -------------------------------------------------------------------------------- /flaskstarter/templates/tasks/show_scfeature.html: -------------------------------------------------------------------------------- 1 | {% from "macros/_form.html" import render_form %} 2 | 3 | {% set page_title = 'ScFeature' %} 4 | 5 | {% extends 'layouts/base.html' %} 6 | 7 | {% block body %} 8 | 9 | 15 | 16 |
17 |
18 |
19 |
scFeature plots
20 |
21 | 33 | 34 | 35 |
36 | 44 |
45 | 51 |
52 | 53 | 61 | 62 | 65 |
66 | 67 |
68 | 69 | 70 |
71 |
72 | 73 |
74 |
76 | 77 |
78 |
79 | 80 |
81 |
82 |
83 | 84 |
85 |
86 |
87 | 98 |
99 |
100 |
101 | 112 | 113 |
114 |
115 |
116 | 117 | 118 |
119 | 120 |
121 | 122 | {% endblock %} 123 | 124 | 125 | 126 | {% block scripts %} 127 | 128 | 138 | 139 | 140 | 146 | 147 | 148 | 155 | 156 | 157 | 163 | 164 | 165 | 172 | 173 | 174 | 179 | 180 | 185 | 186 | 187 | 196 | 197 | 198 | {% endblock %} -------------------------------------------------------------------------------- /flaskstarter/templates/tasks/show_search_plot.html: -------------------------------------------------------------------------------- 1 | {% from "macros/_form.html" import render_form %} 2 | 3 | {% set page_title = 'Cell count meta' %} 4 | 5 | {% extends 'layouts/base.html' %} 6 | 7 | {% block body %} 8 | 9 |
10 |
11 |
12 |
13 |
14 | 15 |
16 |
17 |
18 |
19 |
Barplot of Cell Type Counts
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
Barplot of Severity Counts
28 |
29 |
30 |
31 |
32 |
33 |
34 | 35 |
36 |
37 |
38 |
Barplot of Disease Outcome Counts
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
Barplot of Gender Counts
47 |
48 |
49 |
50 |
51 |
52 |
53 | 54 |
55 |
56 |
57 |
Barplot of Age Counts
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
Histogram of Days from Onset of Symptoms
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | 75 | {% endblock %} 76 | 77 | {% block scripts %} 78 | 79 | 85 | 86 | 92 | 93 | 99 | 100 | 106 | 112 | 113 | 119 | 120 | {% endblock %} -------------------------------------------------------------------------------- /flaskstarter/templates/tasks/view_task.html: -------------------------------------------------------------------------------- 1 | {% from "macros/_form.html" import render_form %} 2 | 3 | {% set page_title = 'View Task' %} 4 | 5 | {% extends 'layouts/base.html' %} 6 | 7 | {% block body %} 8 |
9 |
10 |
11 |
12 |
13 |

{{ task.task }}

14 |
15 | {{ task.added_time | _pretty_date }} 16 |
17 |
18 |
19 |
20 |
21 |
22 | {% endblock %} -------------------------------------------------------------------------------- /flaskstarter/user/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .models import Users, UsersAdmin 4 | from .constants import USER_ROLE, ADMIN, USER, USER_STATUS, NEW, ACTIVE 5 | -------------------------------------------------------------------------------- /flaskstarter/user/constants.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # User role 4 | ADMIN = 0 5 | STAFF = 1 6 | USER = 2 7 | USER_ROLE = { 8 | ADMIN: 'admin', 9 | STAFF: 'staff', 10 | USER: 'user', 11 | } 12 | 13 | # User status 14 | INACTIVE = 0 15 | NEW = 1 16 | ACTIVE = 2 17 | USER_STATUS = { 18 | INACTIVE: 'inactive', 19 | NEW: 'new', 20 | ACTIVE: 'active', 21 | } 22 | 23 | 24 | # Account Type 25 | BASIC = 0 26 | PRO = 1 27 | PREMIUM = 2 28 | ACCOUNT_TYPE = { 29 | BASIC: 'basic', 30 | PRO: 'pro', 31 | PREMIUM: 'premium' 32 | } 33 | -------------------------------------------------------------------------------- /flaskstarter/user/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from sqlalchemy import Column, types 4 | from sqlalchemy.ext.mutable import Mutable 5 | from werkzeug.security import generate_password_hash, check_password_hash 6 | from flask_login import UserMixin 7 | 8 | from ..extensions import db 9 | from ..utils import get_current_time, STRING_LEN 10 | from .constants import USER, USER_ROLE, ADMIN, INACTIVE, USER_STATUS 11 | 12 | from flask_login import current_user 13 | from flask_admin.contrib import sqla 14 | 15 | 16 | class DenormalizedText(Mutable, types.TypeDecorator): 17 | """ 18 | Stores denormalized primary keys that can be 19 | accessed as a set. 20 | 21 | :param coerce: coercion function that ensures correct 22 | type is returned 23 | 24 | :param separator: separator character 25 | """ 26 | 27 | impl = types.Text 28 | 29 | def __init__(self, coerce=int, separator=" ", **kwargs): 30 | 31 | self.coerce = coerce 32 | self.separator = separator 33 | 34 | super(DenormalizedText, self).__init__(**kwargs) 35 | 36 | def process_bind_param(self, value, dialect): 37 | if value is not None: 38 | items = [str(item).strip() for item in value] 39 | value = self.separator.join(item for item in items if item) 40 | return value 41 | 42 | def process_result_value(self, value, dialect): 43 | if not value: 44 | return set() 45 | return set(self.coerce(item) for item in value.split(self.separator)) 46 | 47 | def copy_value(self, value): 48 | return set(value) 49 | 50 | 51 | class Users(db.Model, UserMixin): 52 | 53 | __tablename__ = 'users' 54 | 55 | id = Column(db.Integer, primary_key=True) 56 | 57 | name = Column(db.String(STRING_LEN)) 58 | 59 | email = Column(db.String(STRING_LEN), unique=True) 60 | email_activation_key = Column(db.String(STRING_LEN)) 61 | 62 | created_time = Column(db.DateTime, default=get_current_time) 63 | 64 | _password = Column('password', db.String(100), nullable=False) 65 | 66 | def _get_password(self): 67 | return self._password 68 | 69 | def _set_password(self, password): 70 | self._password = generate_password_hash(password) 71 | 72 | # Hide password encryption by exposing password field only. 73 | password = db.synonym('_password', 74 | descriptor=property(_get_password, 75 | _set_password)) 76 | 77 | def check_password(self, password): 78 | if self.password is None: 79 | return False 80 | return check_password_hash(self.password, password) 81 | 82 | role_code = Column(db.SmallInteger, default=USER, nullable=False) 83 | 84 | @property 85 | def role(self): 86 | return USER_ROLE[self.role_code] 87 | 88 | def is_admin(self): 89 | return self.role_code == ADMIN 90 | 91 | def is_authenticated(self): 92 | return True 93 | 94 | # One-to-many relationship between users and user_statuses. 95 | status_code = Column(db.SmallInteger, default=INACTIVE) 96 | 97 | @property 98 | def status(self): 99 | return USER_STATUS[self.status_code] 100 | 101 | # Class methods 102 | 103 | @classmethod 104 | def authenticate(cls, login, password): 105 | user = cls.query.filter_by(email=login).first() 106 | 107 | if user: 108 | authenticated = user.check_password(password) 109 | else: 110 | authenticated = False 111 | 112 | return user, authenticated 113 | 114 | @classmethod 115 | def get_by_id(cls, user_id): 116 | return cls.query.filter_by(id=user_id).first_or_404() 117 | 118 | def check_email(self, email): 119 | return Users.query.filter(Users.email == email).count() == 0 120 | 121 | def __unicode__(self): 122 | _str = '%s. %s' % (self.id, self.name) 123 | return str(_str) 124 | 125 | 126 | # Customized User model admin 127 | class UsersAdmin(sqla.ModelView): 128 | column_list = ('id', 'name', 'email', 'role_code', 'status_code', 129 | 'created_time', 'activation_key') 130 | column_sortable_list = ('id', 'name', 'email', 'created_time', 131 | 'role_code', 'status_code') 132 | column_searchable_list = ('email', Users.email) 133 | column_filters = ('id', 'name', 'email', 'created_time', 'role_code') 134 | 135 | form_excluded_columns = ('password') 136 | 137 | form_choices = { 138 | 'role_code': [ 139 | ('2', 'User'), 140 | ('0', 'Admin') 141 | ], 142 | 'status_code': [ 143 | ('0', 'Inactive Account'), 144 | ('1', 'New Account'), 145 | ('2', 'Active Account') 146 | ] 147 | } 148 | 149 | column_choices = { 150 | 'role_code': [ 151 | (2, 'User'), 152 | (1, 'Staff'), 153 | (0, 'Admin') 154 | ], 155 | 'status_code': [ 156 | (0, 'Inactive Account'), 157 | (1, 'New Account'), 158 | (2, 'Active Account') 159 | ] 160 | } 161 | 162 | def __init__(self, session): 163 | super(UsersAdmin, self).__init__(Users, session) 164 | 165 | def is_accessible(self): 166 | if current_user.role == 'admin': 167 | return current_user.is_authenticated() 168 | -------------------------------------------------------------------------------- /flaskstarter/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Common utilities to be used in application 4 | """ 5 | 6 | import os 7 | 8 | import datetime 9 | # TMP path, this stores all flask required files 10 | TMP_FOLDER = os.environ.get('HOME') + '/flask_resources' 11 | #TMP_FOLDER = '/data_disk/flask_resources' 12 | 13 | # Form validation 14 | 15 | NAME_LEN_MIN = 4 16 | NAME_LEN_MAX = 25 17 | 18 | PASSWORD_LEN_MIN = 6 19 | PASSWORD_LEN_MAX = 16 20 | 21 | 22 | # Model 23 | STRING_LEN = 64 24 | 25 | 26 | def get_current_time(): 27 | return datetime.datetime.utcnow() 28 | 29 | 30 | def pretty_date(dt, default=None): 31 | # Returns string representing "time since" eg 3 days ago, 5 hours ago etc. 32 | 33 | if default is None: 34 | default = 'just now' 35 | 36 | now = datetime.datetime.utcnow() 37 | diff = now - dt 38 | 39 | periods = ( 40 | (diff.days / 365, 'year', 'years'), 41 | (diff.days / 30, 'month', 'months'), 42 | (diff.days / 7, 'week', 'weeks'), 43 | (diff.days, 'day', 'days'), 44 | (diff.seconds / 3600, 'hour', 'hours'), 45 | (diff.seconds / 60, 'minute', 'minutes'), 46 | (diff.seconds, 'second', 'seconds'), 47 | ) 48 | 49 | for period, singular, plural in periods: 50 | 51 | if not period: 52 | continue 53 | 54 | if int(period) >= 1: 55 | if int(period) > 1: 56 | return u'%d %s ago' % (period, plural) 57 | return u'%d %s ago' % (period, singular) 58 | 59 | return default 60 | -------------------------------------------------------------------------------- /gunicorn.conf.py: -------------------------------------------------------------------------------- 1 | preload_app=False 2 | accesslog = "./gunicorn_access_new.log" 3 | errorlog = "./gunicorn_error_new.log" 4 | #certfile = "/etc/ssl/certs/ssl-cert-snakeoil.pem" 5 | #keyfile = "/etc/ssl/private/ssl-cert-snakeoil.key" 6 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from sqlalchemy.orm.mapper import configure_mappers 4 | 5 | from flaskstarter import create_app 6 | from flaskstarter.extensions import db 7 | from flaskstarter.user import Users, ADMIN, USER, ACTIVE 8 | 9 | from flask_pymongo import PyMongo 10 | # from gevent import monkey 11 | # monkey.patch_all() 12 | 13 | from celery import Celery 14 | 15 | def register_celery(app): 16 | #celery.from_config_object("celeryconfig") 17 | class ContextTask(celery.Task): 18 | def __call__(self, *args, **kwargs): 19 | with app.app_context(): 20 | return self.run(*args, **kwargs) 21 | celery.Task = ContextTask 22 | 23 | celery = Celery(__name__, broker='redis://localhost:6379/0') 24 | application = create_app() 25 | application.app_context().push() 26 | application.test_request_context().push() 27 | register_celery(application) 28 | 29 | # 30 | # if __name__ == "__main__": 31 | # application.run() 32 | # 33 | 34 | 35 | @application.cli.command("initdb") 36 | def initdb(): 37 | """Init/reset database.""" 38 | 39 | db.drop_all() 40 | configure_mappers() 41 | db.create_all() 42 | 43 | admin = Users(name='Admin Flask-Starter', 44 | email=u'admin@your-mail.com', 45 | password=u'adminpassword', 46 | role_code=ADMIN, 47 | status_code=ACTIVE) 48 | 49 | db.session.add(admin) 50 | 51 | for i in range(1, 2): 52 | user = Users(name='Demo User', 53 | email=u'demo@your-mail.com', 54 | password=u'demopassword', 55 | role_code=USER, 56 | status_code=ACTIVE) 57 | db.session.add(user) 58 | 59 | for i in range(1, 5): 60 | _task = MyTaskModel(task="Task Random Number ## " + str(i), users_id=2) 61 | 62 | db.session.add(_task) 63 | 64 | db.session.commit() 65 | 66 | print("Database initialized with 2 users (admin, demo)") 67 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | amqp==5.1.1 2 | asgiref==3.5.0 3 | async-timeout==4.0.2 4 | attrs==21.4.0 5 | bcrypt==3.2.2 6 | billiard==3.6.4.0 7 | biopython==1.79 8 | blinker==1.4 9 | boto3==1.24.54 10 | botocore==1.27.54 11 | Brotli==1.0.9 12 | cachelib==0.6.0 13 | celery==5.2.7 14 | certifi==2022.5.18.1 15 | cffi==1.15.1 16 | charset-normalizer==2.0.12 17 | click==8.0.3 18 | click-didyoumean==0.3.0 19 | click-plugins==1.1.1 20 | click-repl==0.2.0 21 | colour==0.1.5 22 | cryptography==37.0.4 23 | cycler==0.11.0 24 | dash==2.5.0 25 | dash-bio==1.0.2 26 | dash-core-components==2.0.0 27 | dash-html-components==2.0.0 28 | dash-table==5.0.0 29 | Deprecated==1.2.13 30 | dnspython==2.2.0 31 | email-validator==1.1.3 32 | eventlet==0.33.1 33 | Flask==2.0.2 34 | Flask-Admin==1.5.8 35 | Flask-Caching==1.10.1 36 | Flask-Compress==1.12 37 | Flask-Login==0.5.0 38 | Flask-Mail==0.9.1 39 | Flask-PyMongo==2.3.0 40 | Flask-Session==0.4.0 41 | Flask-SQLAlchemy==2.5.1 42 | Flask-WTF==1.0.0 43 | fonttools==4.33.3 44 | GEOparse==2.0.3 45 | gevent==21.12.0 46 | greenlet==1.1.2 47 | gunicorn==20.1.0 48 | idna==3.3 49 | iniconfig==1.1.1 50 | itsdangerous==2.0.1 51 | Jinja2==3.0.3 52 | jmespath==1.0.1 53 | joblib==1.1.0 54 | jsonschema==4.6.0 55 | kiwisolver==1.4.2 56 | kombu==5.2.4 57 | MarkupSafe==2.0.1 58 | matplotlib==3.5.2 59 | mongo-datatables==0.3.0 60 | numpy==1.22.1 61 | packaging==21.3 62 | pandas==1.4.0 63 | paramiko==2.11.0 64 | ParmEd==3.4.3 65 | periodictable==1.6.1 66 | Pillow==9.1.1 67 | plotly==5.5.0 68 | pluggy==1.0.0 69 | prompt-toolkit==3.0.30 70 | py==1.11.0 71 | pycparser==2.21 72 | pymongo==4.0.1 73 | PyNaCl==1.5.0 74 | pyparsing==3.0.7 75 | pyrsistent==0.18.1 76 | pytest==6.2.5 77 | python-dateutil==2.8.2 78 | pytz==2021.3 79 | redis==4.3.4 80 | requests==2.28.0 81 | s3transfer==0.6.0 82 | scikit-learn==1.1.1 83 | scipy==1.8.1 84 | seaborn==0.11.2 85 | shortuuid==1.0.8 86 | six==1.16.0 87 | SQLAlchemy==1.4.31 88 | ssh-pymongo==1.0.4 89 | sshtunnel==0.4.0 90 | tenacity==8.0.1 91 | threadpoolctl==3.1.0 92 | toml==0.10.2 93 | tqdm==4.64.0 94 | urllib3==1.26.9 95 | vine==5.0.0 96 | wcwidth==0.2.5 97 | Werkzeug==2.0.2 98 | wrapt==1.14.1 99 | WTForms==2.3.3 100 | zope.event==4.5.0 101 | zope.interface==5.4.0 102 | -------------------------------------------------------------------------------- /screenshots/Logo_gradient.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiyin/scopeplus/3d0f9f74d966390f101fabb38d1952f5e8c90ba6/screenshots/Logo_gradient.png -------------------------------------------------------------------------------- /screenshots/admin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiyin/scopeplus/3d0f9f74d966390f101fabb38d1952f5e8c90ba6/screenshots/admin.png -------------------------------------------------------------------------------- /screenshots/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiyin/scopeplus/3d0f9f74d966390f101fabb38d1952f5e8c90ba6/screenshots/dashboard.png -------------------------------------------------------------------------------- /screenshots/homepage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiyin/scopeplus/3d0f9f74d966390f101fabb38d1952f5e8c90ba6/screenshots/homepage.png -------------------------------------------------------------------------------- /screenshots/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiyin/scopeplus/3d0f9f74d966390f101fabb38d1952f5e8c90ba6/screenshots/login.png -------------------------------------------------------------------------------- /screenshots/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiyin/scopeplus/3d0f9f74d966390f101fabb38d1952f5e8c90ba6/screenshots/logo.png -------------------------------------------------------------------------------- /screenshots/profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiyin/scopeplus/3d0f9f74d966390f101fabb38d1952f5e8c90ba6/screenshots/profile.png -------------------------------------------------------------------------------- /screenshots/signup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiyin/scopeplus/3d0f9f74d966390f101fabb38d1952f5e8c90ba6/screenshots/signup.png -------------------------------------------------------------------------------- /screenshots/tasks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiyin/scopeplus/3d0f9f74d966390f101fabb38d1952f5e8c90ba6/screenshots/tasks.png -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hiyin/scopeplus/3d0f9f74d966390f101fabb38d1952f5e8c90ba6/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_flaskstarter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import pytest 4 | 5 | from ..flaskstarter import create_app 6 | from ..flaskstarter.extensions import db 7 | from ..flaskstarter.user import Users 8 | 9 | 10 | def test_home_page(): 11 | # TODO 12 | return True 13 | 14 | 15 | def test_login_page(): 16 | # TODO 17 | 18 | return True 19 | 20 | 21 | def test_user_model(): 22 | # TODO 23 | 24 | return True 25 | --------------------------------------------------------------------------------