Browse Source

Refactoring of app.py to use the newer Flask CLI, also adapted for unittests

pull/37/head
Dashie der otter 9 months ago
parent
commit
a668ce3339
Signed by: Dashie <dashie@sigpipe.me> GPG Key ID: C2D57B325840B755
5 changed files with 293 additions and 202 deletions
  1. 18
    10
      README.md
  2. 0
    142
      ahrl.py
  3. 271
    47
      app.py
  4. 3
    3
      tests/config_tests.py
  5. 1
    0
      version.py

+ 18
- 10
README.md View File

@@ -7,7 +7,8 @@ Another Ham Radio Log
7 7
 
8 8
 
9 9
 # Versions requirement
10
- - Python >= 3.3 (3.0, 3.1, 3.2 not supported)
10
+ - Python >= 3.6 (all under 3.6 are not supported) (say bye-bye to debian stable, sorry)
11
+ - try https://github.com/chriskuehl/python3.6-debian-stretch if you use debian stable
11 12
 
12 13
 # Installation
13 14
     Install a BDD (mysql is supported, SQLite maybe, PostgreSQL should be)
@@ -16,19 +17,26 @@ Another Ham Radio Log
16 17
     cd ahrl
17 18
     git submodule init
18 19
     git submodule update
19
-    pip3 install git+https://github.com/ggramaize/libqth.git
20
-    pip3 install git+http://dev.sigpipe.me/DashieHam/pyHamQth.git
21
-    pip3 install --requirement requirements.txt  # if present
20
+    pip3 install --requirement requirements.txt
22 21
     cp config.py.sample config.py
23 22
     $EDITOR config.py
24
-    python3 ahrl.py db upgrade
25
-    python3 ahrl.py db seed
26
-    python3 ahrl.py cron update_dxcc_from_cty
27
-    python3 ahrl.py runserver # or whatever gunicorn whatever stuff
23
+    export FLASK_ENV=<development or production>
24
+    $ create your postgresql database like "ahrl"
25
+    flask db upgrade
26
+    flask seed
27
+    flask cron update_dxcc_from_cty
28
+    flask run
28 29
     Don't forget to update default Config by getting to "Your user" (top right) then "Config"
29 30
 
30
-# Gunicorn
31
-    gunicorn -w 2 -b 127.0.0.1:8000 --error-logfile=errors.log --access-logfile=access.log --chdir=$PWD ahrl:app
31
+# Creating an user
32
+
33
+If you have enabled registration in config, the first user registered will be ADMIN !
34
+
35
+Or if you have disabled registration, use the ``` flask createuser ``` command to create an user.
36
+
37
+# Production running
38
+
39
+TODO: venv, systemd services
32 40
 
33 41
 # Default config
34 42
  - LOTW Download URL: https://p1k.arrl.org/lotwuser/lotwreport.adi

+ 0
- 142
ahrl.py View File

@@ -1,142 +0,0 @@
1
-# encoding: utf-8
2
-from pprint import pprint as pp
3
-import datetime
4
-import os
5
-import texttable
6
-from flask_debugtoolbar import DebugToolbarExtension
7
-from flask_migrate import MigrateCommand
8
-from flask_script import Manager
9
-from crons import (
10
-    update_qsos_without_countries,
11
-    update_dxcc_from_cty_xml,
12
-    populate_logs_gridsquare_cache,
13
-    cron_sync_eqsl,
14
-    update_qsos_from_hamqth,
15
-    cron_sync_from_eqsl,
16
-)
17
-from dbseed import make_db_seed
18
-from models import db
19
-
20
-try:
21
-    from raven.contrib.flask import Sentry
22
-    import raven
23
-
24
-    print(" * Sentry support loaded")
25
-    HAS_SENTRY = True
26
-except ImportError:
27
-    print(" * No Sentry support")
28
-    HAS_SENTRY = False
29
-
30
-from app import create_app
31
-
32
-app = create_app()
33
-
34
-if HAS_SENTRY:
35
-    app.config["SENTRY_RELEASE"] = raven.fetch_git_sha(os.path.dirname(__file__))
36
-    sentry = Sentry(app, dsn=app.config["SENTRY_DSN"])
37
-    print(" * Sentry support activated")
38
-    print(" * Sentry DSN: %s" % app.config["SENTRY_DSN"])
39
-
40
-toolbar = DebugToolbarExtension(app)
41
-manager = Manager(app)
42
-
43
-
44
-# Other commands
45
-@manager.command
46
-def routes():
47
-    """Dump all routes of defined app"""
48
-    table = texttable.Texttable()
49
-    table.set_deco(texttable.Texttable().HEADER)
50
-    table.set_cols_dtype(["t", "t", "t"])
51
-    table.set_cols_align(["l", "l", "l"])
52
-    table.set_cols_width([60, 30, 90])
53
-
54
-    table.add_rows([["Prefix", "Verb", "URI Pattern"]])
55
-
56
-    for rule in sorted(app.url_map.iter_rules(), key=lambda x: str(x)):
57
-        methods = ",".join(rule.methods)
58
-        table.add_row([rule.endpoint, methods, rule])
59
-
60
-    print(table.draw())
61
-
62
-
63
-@manager.command
64
-def config():
65
-    """Dump config"""
66
-    pp(app.config)
67
-
68
-
69
-@MigrateCommand.command
70
-def seed():
71
-    """Seed database with default content"""
72
-    make_db_seed(db)
73
-
74
-
75
-CacheCommand = Manager(usage="Perform cache actions")
76
-CronCommand = Manager(usage="Perform crons actions")
77
-
78
-
79
-@CronCommand.command
80
-def update_dxcc_from_cty():
81
-    """Update DXCC tables from cty.xml"""
82
-    print("-- STARTED on {0}".format(datetime.datetime.now()))
83
-    update_dxcc_from_cty_xml()
84
-    print("-- FINISHED on {0}".format(datetime.datetime.now()))
85
-
86
-
87
-@CronCommand.command
88
-def update_qsos_countries():
89
-    """Update QSOs with empty country"""
90
-    print("-- STARTED on {0}".format(datetime.datetime.now()))
91
-    update_qsos_without_countries()
92
-    print("-- FINISHED on {0}".format(datetime.datetime.now()))
93
-
94
-
95
-@CronCommand.command
96
-@CronCommand.option(
97
-    "--dryrun", dest="dry_run", action="store_true", default=False, help="Dry run, doesn't commit anything"
98
-)
99
-def sync_to_eqsl(dry_run=False):
100
-    """Push to eQSL logs with requested eQSL sync"""
101
-    print("-- STARTED on {0}".format(datetime.datetime.now()))
102
-    cron_sync_eqsl(dry_run)
103
-    print("-- FINISHED on {0}".format(datetime.datetime.now()))
104
-
105
-
106
-@CronCommand.command
107
-@CronCommand.option(
108
-    "--dryrun", dest="dry_run", action="store_true", default=False, help="Dry run, doesn't commit anything"
109
-)
110
-def sync_from_eqsl(dry_run=False):
111
-    """Fetch from eQSL logs """
112
-    print("-- STARTED on {0}".format(datetime.datetime.now()))
113
-    cron_sync_from_eqsl(dry_run)
114
-    print("-- FINISHED on {0}".format(datetime.datetime.now()))
115
-
116
-
117
-@CronCommand.command
118
-def update_qsos_hamqth():
119
-    """Update QSOs with datas from HamQTH"""
120
-    print("-- STARTED on {0}".format(datetime.datetime.now()))
121
-    update_qsos_from_hamqth()
122
-    print("-- FINISHED on {0}".format(datetime.datetime.now()))
123
-
124
-
125
-@CacheCommand.command
126
-def populate_logs_gridsquare():
127
-    """Update QSOs with empty gridsquare cache"""
128
-    print("-- STARTED on {0}".format(datetime.datetime.now()))
129
-    populate_logs_gridsquare_cache()
130
-    print("-- FINISHED on {0}".format(datetime.datetime.now()))
131
-
132
-
133
-manager.add_command("db", MigrateCommand)
134
-manager.add_command("cache", CacheCommand)
135
-manager.add_command("cron", CronCommand)
136
-
137
-if __name__ == "__main__":
138
-    try:
139
-        manager.run()
140
-    except KeyboardInterrupt as e:
141
-        print("Got KeyboardInterrupt, halting...")
142
-        print(e)

+ 271
- 47
app.py View File

@@ -1,47 +1,78 @@
1 1
 # encoding: utf-8
2 2
 import logging
3
-import os
4 3
 import subprocess
5 4
 from logging.handlers import RotatingFileHandler
6 5
 
7
-from flask import Flask, render_template, g, send_from_directory, jsonify
6
+from flask import Flask, render_template, g, send_from_directory, jsonify, request, safe_join, Response
8 7
 from flask_bootstrap import Bootstrap
9 8
 from flask_mail import Mail
10 9
 from flask_migrate import Migrate
11 10
 from flask_security import Security
12
-from flask_uploads import configure_uploads, UploadSet, IMAGES
13
-
14
-from controllers.admin import bp_admin
15
-from controllers.contacts import bp_contacts
16
-from controllers.logbooks import bp_logbooks
17
-from controllers.main import bp_main
18
-from controllers.notes import bp_notes
19
-from controllers.qsos import bp_qsos
20
-from controllers.tools import bp_tools
21
-from controllers.users import bp_users
22
-from controllers.extapi import bp_extapi
11
+from flask_security import signals as FlaskSecuritySignals
12
+from flask_security import confirmable as FSConfirmable
13
+from flask_security.utils import encrypt_password
14
+from flask_uploads import configure_uploads, UploadSet, IMAGES, patch_request_class
15
+from flask_babelex import gettext, Babel
16
+
23 17
 from forms import ExtendedRegisterForm
24
-from models import db, user_datastore
18
+from models import user_datastore, Role
25 19
 from utils import dt_utc_to_user_tz, InvalidUsage, show_date_no_offset, is_admin
20
+from pprint import pprint as pp
21
+import datetime
22
+import os
23
+import texttable
24
+from flask_debugtoolbar import DebugToolbarExtension
25
+from crons import (
26
+    update_qsos_without_countries,
27
+    update_dxcc_from_cty_xml,
28
+    populate_logs_gridsquare_cache,
29
+    cron_sync_eqsl,
30
+    update_qsos_from_hamqth,
31
+    cron_sync_from_eqsl,
32
+)
33
+from dbseed import make_db_seed
34
+from models import db
35
+import click
36
+
37
+from version import VERSION
38
+
39
+__VERSION__ = VERSION
40
+
41
+try:
42
+    from raven.contrib.flask import Sentry
43
+    import raven
44
+
45
+    print(" * Sentry support loaded")
46
+    HAS_SENTRY = True
47
+except ImportError:
48
+    print(" * No sentry support")
49
+    HAS_SENTRY = False
26 50
 
27
-__VERSION__ = "0.0.1"
51
+mail = Mail()
28 52
 
29 53
 
30
-def create_app(cfg=None):
31
-    # App Configuration
32
-    if cfg is None:
33
-        cfg = {}
34
-    app = Flask(__name__)
35
-    app.config.from_pyfile("config.py")
36
-    app.config.update(cfg)
54
+def create_app(config_filename="config.py", app_name=None, register_blueprints=True):
55
+    # App configuration
56
+    app = Flask(app_name or __name__)
57
+    app.config.from_pyfile(config_filename)
37 58
 
38 59
     Bootstrap(app)
39 60
 
40 61
     app.jinja_env.add_extension("jinja2.ext.with_")
41 62
     app.jinja_env.add_extension("jinja2.ext.do")
63
+    app.jinja_env.globals.update(is_admin=is_admin)
42 64
     app.jinja_env.filters["localize"] = dt_utc_to_user_tz
43 65
     app.jinja_env.filters["show_date_no_offset"] = show_date_no_offset
44
-    app.jinja_env.globals.update(is_admin=is_admin)
66
+
67
+    if HAS_SENTRY:
68
+        app.config["SENTRY_RELEASE"] = raven.fetch_git_sha(os.path.dirname(__file__))
69
+        sentry = Sentry(app, dsn=app.config["SENTRY_DSN"])  # noqa: F841
70
+        print(" * Sentry support activated")
71
+        print(" * Sentry DSN: %s" % app.config["SENTRY_DSN"])
72
+
73
+    if app.config["DEBUG"] is True:
74
+        app.jinja_env.auto_reload = True
75
+        app.logger.setLevel(logging.DEBUG)
45 76
 
46 77
     # Logging
47 78
     if not app.debug:
@@ -51,13 +82,30 @@ def create_app(cfg=None):
51 82
         file_handler.setFormatter(formatter)
52 83
         app.logger.addHandler(file_handler)
53 84
 
54
-    mail = Mail(app)  # noqa: F841
85
+    mail.init_app(app)
55 86
     migrate = Migrate(app, db)  # noqa: F841
87
+    babel = Babel(app)  # noqa: F841
88
+    toolbar = DebugToolbarExtension(app)  # noqa: F841
56 89
 
57 90
     db.init_app(app)
58 91
 
59 92
     # Setup Flask-Security
60
-    security = Security(app, user_datastore, register_form=ExtendedRegisterForm)  # noqa: F841
93
+    security = Security(  # noqa: F841
94
+        app, user_datastore, register_form=ExtendedRegisterForm, confirm_register_form=ExtendedRegisterForm
95
+    )
96
+
97
+    @FlaskSecuritySignals.password_reset.connect_via(app)
98
+    @FlaskSecuritySignals.password_changed.connect_via(app)
99
+    def log_password_reset(sender, user):
100
+        if not user:
101
+            return
102
+        # add_user_log(user.id, user.id, "user", "info", "Your password has been changed !")
103
+
104
+    @FlaskSecuritySignals.reset_password_instructions_sent.connect_via(app)
105
+    def log_reset_password_instr(sender, user, token):
106
+        if not user:
107
+            return
108
+        # add_user_log(user.id, user.id, "user", "info", "Password reset instructions sent.")
61 109
 
62 110
     git_version = ""
63 111
     gitpath = os.path.join(os.getcwd(), ".git")
@@ -66,13 +114,31 @@ def create_app(cfg=None):
66 114
         if git_version:
67 115
             git_version = git_version.strip().decode("UTF-8")
68 116
 
117
+    @babel.localeselector
118
+    def get_locale():
119
+        # if a user is logged in, use the locale from the user settings
120
+        identity = getattr(g, "identity", None)
121
+        if identity is not None and identity.id:
122
+            return identity.user.locale
123
+        # otherwise try to guess the language from the user accept
124
+        # header the browser transmits.  We support fr/en in this
125
+        # example.  The best match wins.
126
+        return request.accept_languages.best_match(["fr", "en"])
127
+
128
+    @babel.timezoneselector
129
+    def get_timezone():
130
+        identity = getattr(g, "identity", None)
131
+        if identity is not None and identity.id:
132
+            return identity.user.timezone
133
+
69 134
     @app.before_request
70 135
     def before_request():
71
-        g.cfg = {
72
-            "AHRL_VERSION_VER": __VERSION__,
136
+        cfg = {
137
+            "AHRL_VERSION_VER": VERSION,
73 138
             "AHRL_VERSION_GIT": git_version,
74
-            "AHRL_VERSION": "{0} ({1})".format(__VERSION__, git_version),
139
+            "AHRL_VERSION": "{0} ({1})".format(VERSION, git_version),
75 140
         }
141
+        g.cfg = cfg
76 142
 
77 143
     @app.errorhandler(InvalidUsage)
78 144
     def handle_invalid_usage(error):
@@ -82,43 +148,201 @@ def create_app(cfg=None):
82 148
 
83 149
     pictures = UploadSet("pictures", IMAGES)
84 150
     configure_uploads(app, pictures)
151
+    patch_request_class(app, 5 * 1024 * 1024)  # 5m limit
152
+
153
+    if register_blueprints:
154
+        from controllers.admin import bp_admin
155
+
156
+        app.register_blueprint(bp_admin)
157
+
158
+        from controllers.contacts import bp_contacts
159
+
160
+        app.register_blueprint(bp_contacts)
161
+
162
+        from controllers.logbooks import bp_logbooks
163
+
164
+        app.register_blueprint(bp_logbooks)
165
+
166
+        from controllers.main import bp_main
167
+
168
+        app.register_blueprint(bp_main)
169
+
170
+        from controllers.notes import bp_notes
171
+
172
+        app.register_blueprint(bp_notes)
173
+
174
+        from controllers.qsos import bp_qsos
175
+
176
+        app.register_blueprint(bp_qsos)
177
+
178
+        from controllers.tools import bp_tools
85 179
 
86
-    app.register_blueprint(bp_main)
87
-    app.register_blueprint(bp_users)
88
-    app.register_blueprint(bp_notes)
89
-    app.register_blueprint(bp_qsos)
90
-    app.register_blueprint(bp_tools)
91
-    app.register_blueprint(bp_contacts)
92
-    app.register_blueprint(bp_logbooks)
93
-    app.register_blueprint(bp_admin)
94
-    app.register_blueprint(bp_extapi)
95
-
96
-    # Used in development
97
-    @app.route("/uploads/<path:stuff>", methods=["GET"])
98
-    def get_uploads_stuff(stuff):
99
-        print("Get {0} from {1}".format(stuff, app.config["UPLOADS_DEFAULT_DEST"]))
100
-        return send_from_directory(app.config["UPLOADS_DEFAULT_DEST"], stuff, as_attachment=False)
180
+        app.register_blueprint(bp_tools)
181
+
182
+        from controllers.users import bp_users
183
+
184
+        app.register_blueprint(bp_users)
185
+
186
+        from controllers.extapi import bp_extapi
187
+
188
+        app.register_blueprint(bp_extapi)
189
+
190
+    @app.route("/uploads/<string:thing>/<path:stuff>", methods=["GET"])
191
+    def get_uploads_stuff(thing, stuff):
192
+        if app.debug:
193
+            directory = safe_join(app.config["UPLOADS_DEFAULT_DEST"], thing)
194
+            app.logger.debug(f"serving {stuff} from {directory}")
195
+            return send_from_directory(directory, stuff, as_attachment=True)
196
+        else:
197
+            app.logger.debug(f"X-Accel-Redirect serving {stuff}")
198
+            resp = Response("")
199
+            resp.headers["Content-Disposition"] = f"attachment; filename={stuff}"
200
+            resp.headers["X-Accel-Redirect"] = f"/_protected/media/tracks/{thing}/{stuff}"
201
+            return resp
101 202
 
102 203
     @app.errorhandler(404)
103 204
     def page_not_found(msg):
104
-        pcfg = {"title": "Whoops, something failed.", "error": 404, "message": "Page not found", "e": msg}
205
+        pcfg = {
206
+            "title": gettext("Whoops, something failed."),
207
+            "error": 404,
208
+            "message": gettext("Page not found"),
209
+            "e": msg,
210
+        }
105 211
         return render_template("error_page.jinja2", pcfg=pcfg), 404
106 212
 
107 213
     @app.errorhandler(403)
108 214
     def err_forbidden(msg):
109
-        pcfg = {"title": "Whoops, something failed.", "error": 403, "message": "Access forbidden", "e": msg}
215
+        pcfg = {
216
+            "title": gettext("Whoops, something failed."),
217
+            "error": 403,
218
+            "message": gettext("Access forbidden"),
219
+            "e": msg,
220
+        }
110 221
         return render_template("error_page.jinja2", pcfg=pcfg), 403
111 222
 
112 223
     @app.errorhandler(410)
113 224
     def err_gone(msg):
114
-        pcfg = {"title": "Whoops, something failed.", "error": 410, "message": "Gone", "e": msg}
225
+        pcfg = {"title": gettext("Whoops, something failed."), "error": 410, "message": gettext("Gone"), "e": msg}
115 226
         return render_template("error_page.jinja2", pcfg=pcfg), 410
116 227
 
117 228
     if not app.debug:
118 229
 
119 230
         @app.errorhandler(500)
120 231
         def err_failed(msg):
121
-            pcfg = {"title": "Whoops, something failed.", "error": 500, "message": "Something is broken", "e": msg}
232
+            pcfg = {
233
+                "title": gettext("Whoops, something failed."),
234
+                "error": 500,
235
+                "message": gettext("Something is broken"),
236
+                "e": msg,
237
+            }
122 238
             return render_template("error_page.jinja2", pcfg=pcfg), 500
123 239
 
240
+    @app.after_request
241
+    def set_x_powered_by(response):
242
+        response.headers["X-Powered-By"] = "ahrl"
243
+        return response
244
+
245
+    # Commands from Flask CLI
246
+
247
+    @app.cli.command()
248
+    def routes():
249
+        """Dump all routes of defined app"""
250
+        table = texttable.Texttable()
251
+        table.set_deco(texttable.Texttable().HEADER)
252
+        table.set_cols_dtype(["t", "t", "t"])
253
+        table.set_cols_align(["l", "l", "l"])
254
+        table.set_cols_width([50, 30, 80])
255
+
256
+        table.add_rows([["Prefix", "Verb", "URI Pattern"]])
257
+
258
+        for rule in sorted(app.url_map.iter_rules(), key=lambda x: str(x)):
259
+            methods = ",".join(rule.methods)
260
+            table.add_row([rule.endpoint, methods, rule])
261
+
262
+        print(table.draw())
263
+
264
+    @app.cli.command()
265
+    def config():
266
+        """Dump config"""
267
+        pp(app.config)
268
+
269
+    @app.cli.command()
270
+    def seed():
271
+        """Seed database with default content"""
272
+        make_db_seed(db)
273
+
274
+    @app.cli.command()
275
+    def createuser():
276
+        """Create an user"""
277
+        username = click.prompt("Username", type=str)
278
+        email = click.prompt("Email", type=str)
279
+        password = click.prompt("Password", type=str, hide_input=True, confirmation_prompt=True)
280
+        while True:
281
+            role = click.prompt("Role [admin/user]", type=str)
282
+            if role == "admin" or role == "user":
283
+                break
284
+
285
+        if click.confirm("Do you want to continue ?"):
286
+            role = Role.query.filter(Role.name == role).first()
287
+            if not role:
288
+                raise click.UsageError("Roles not present in database")
289
+            u = user_datastore.create_user(
290
+                name=username, email=email, password=encrypt_password(password), roles=[role]
291
+            )
292
+
293
+            db.session.commit()
294
+
295
+            if FSConfirmable.requires_confirmation(u):
296
+                FSConfirmable.send_confirmation_instructions(u)
297
+                print("Look at your emails for validation instructions.")
298
+
299
+    @app.cli.group()
300
+    def cron():
301
+        """Commands to be run regullary"""
302
+        pass
303
+
304
+    @cron.command()
305
+    def update_dxcc_from_cty():
306
+        """Update DXCC tables from cty.xml"""
307
+        print("-- STARTED on {0}".format(datetime.datetime.now()))
308
+        update_dxcc_from_cty_xml()
309
+        print("-- FINISHED on {0}".format(datetime.datetime.now()))
310
+
311
+    @cron.command()
312
+    def update_qsos_countries():
313
+        """Update QSOs with empty country"""
314
+        print("-- STARTED on {0}".format(datetime.datetime.now()))
315
+        update_qsos_without_countries()
316
+        print("-- FINISHED on {0}".format(datetime.datetime.now()))
317
+
318
+    @cron.command()
319
+    @click.option("--dryrun", default=False, help="Dry run, doesn't commit anything")
320
+    def sync_to_eqsl(dryrun=False):
321
+        """Push to eQSL logs with requested eQSL sync"""
322
+        print("-- STARTED on {0}".format(datetime.datetime.now()))
323
+        cron_sync_eqsl(dryrun)
324
+        print("-- FINISHED on {0}".format(datetime.datetime.now()))
325
+
326
+    @cron.command()
327
+    @click.option("--dryrun", default=False, help="Dry run, doesn't commit anything")
328
+    def sync_from_eqsl(dryrun=False):
329
+        """Fetch from eQSL logs """
330
+        print("-- STARTED on {0}".format(datetime.datetime.now()))
331
+        cron_sync_from_eqsl(dryrun)
332
+        print("-- FINISHED on {0}".format(datetime.datetime.now()))
333
+
334
+    @cron.command()
335
+    def update_qsos_hamqth():
336
+        """Update QSOs with datas from HamQTH"""
337
+        print("-- STARTED on {0}".format(datetime.datetime.now()))
338
+        update_qsos_from_hamqth()
339
+        print("-- FINISHED on {0}".format(datetime.datetime.now()))
340
+
341
+    @cron.command()
342
+    def populate_logs_gridsquare():
343
+        """Update QSOs with empty gridsquare cache"""
344
+        print("-- STARTED on {0}".format(datetime.datetime.now()))
345
+        populate_logs_gridsquare_cache()
346
+        print("-- FINISHED on {0}".format(datetime.datetime.now()))
347
+
124 348
     return app

+ 3
- 3
tests/config_tests.py View File

@@ -17,7 +17,7 @@ UPLOADS_DEFAULT_DEST = "/home/dashie/dev/ahrl/uploads"
17 17
 TEMP_DOWNLOAD_FOLDER = "/home/dashie/dev/ahrl/tmp"
18 18
 
19 19
 # If using sentry, set a DSN
20
-SENTRY_USER_ATTRS = ['name', 'email']
20
+SENTRY_USER_ATTRS = ["name", "email"]
21 21
 SENTRY_DSN = ""
22 22
 
23 23
 # Domain serving this app
@@ -39,7 +39,7 @@ SECURITY_REGISTERABLE = True
39 39
 SECURITY_RECOVERABLE = True
40 40
 # Salt used for password hashing
41 41
 # Do not change after users have registered
42
-SECURITY_PASSWORD_SALT = 'awooo'
42
+SECURITY_PASSWORD_SALT = "awooo"
43 43
 # Do not change after users have registered
44 44
 SECRET_KEY = "ahahahahahahahahahaquack"
45 45
 
@@ -73,7 +73,7 @@ SQLALCHEMY_RECORD_QUERIES = True
73 73
 # Do not disable, will breaks things
74 74
 SECURITY_CHANGEABLE = True
75 75
 # Password hash algorithm
76
-SECURITY_PASSWORD_HASH = 'bcrypt'
76
+SECURITY_PASSWORD_HASH = "bcrypt"
77 77
 BABEL_DEFAULT_TIMEZONE = "UTC"
78 78
 
79 79
 

+ 1
- 0
version.py View File

@@ -0,0 +1 @@
1
+VERSION = "0.0.1"

Loading…
Cancel
Save