Browse Source

Reformat with Black

pull/9/head
Dashie der otter 11 months ago
parent
commit
6464af08f7
Signed by: Dashie <dashie@sigpipe.me> GPG Key ID: C2D57B325840B755
50 changed files with 2484 additions and 2114 deletions
  1. 8
    8
      adif.py
  2. 27
    17
      ahrl.py
  3. 24
    27
      app.py
  4. 9
    9
      controllers/admin.py
  5. 5
    5
      controllers/api.py
  6. 63
    56
      controllers/contacts.py
  7. 7
    7
      controllers/extapi.py
  8. 36
    29
      controllers/logbooks.py
  9. 13
    8
      controllers/main.py
  10. 31
    21
      controllers/notes.py
  11. 533
    394
      controllers/qsos.py
  12. 119
    108
      controllers/tools.py
  13. 38
    21
      controllers/users.py
  14. 106
    103
      crons.py
  15. 137
    134
      dbseed.py
  16. 164
    157
      forms.py
  17. 191
    103
      libjambon.py
  18. 17
    14
      migrations/env.py
  19. 305
    291
      migrations/versions/001_33200cef9060.py
  20. 16
    15
      migrations/versions/002_1981d67bd949.py
  21. 20
    19
      migrations/versions/003_dba2c36b8d15.py
  22. 16
    15
      migrations/versions/004_85dc2959dc8c.py
  23. 55
    52
      migrations/versions/005_b68e04878516.py
  24. 30
    28
      migrations/versions/006_d1c8faa24092.py
  25. 6
    6
      migrations/versions/007_f4d4e3c42eb7.py
  26. 98
    102
      migrations/versions/008_488cd2ea543d.py
  27. 6
    6
      migrations/versions/009_790b9af160b5.py
  28. 2
    2
      migrations/versions/010_516077ddda8d.py
  29. 70
    45
      migrations/versions/011_8ddd19e5391a.py
  30. 4
    4
      migrations/versions/012_0222deffc1dd.py
  31. 8
    8
      migrations/versions/013_a69619a8fd47.py
  32. 4
    4
      migrations/versions/014_229e1f2ce062.py
  33. 26
    24
      migrations/versions/015_4669be672bc4.py
  34. 4
    4
      migrations/versions/016_71ebbd5fe6de.py
  35. 7
    6
      migrations/versions/017_eb1dd834c778.py
  36. 6
    6
      migrations/versions/018_1cfbdbdced17.py
  37. 6
    6
      migrations/versions/019_4b8ff69cf574.py
  38. 6
    6
      migrations/versions/020_96dd40d300ed.py
  39. 4
    4
      migrations/versions/021_b71a316c6504.py
  40. 38
    38
      migrations/versions/022_98ea6ef6fc14.py
  41. 26
    9
      migrations/versions/024_5539ae4bd72a.py
  42. 8
    8
      migrations/versions/025_df93b7d9aa63.py
  43. 4
    4
      migrations/versions/026_eb8858fd3d9c.py
  44. 4
    4
      migrations/versions/027_e072762b7964_.py
  45. 107
    106
      models.py
  46. 1
    1
      tests/__init__.py
  47. 29
    33
      tests/test_app.py
  48. 17
    16
      tests/test_libjambon.py
  49. 2
    1
      tests/test_utils.py
  50. 21
    20
      utils.py

+ 8
- 8
adif.py View File

@@ -3,31 +3,31 @@ import datetime

# Comes from https://web.bxhome.org/content/adifpy

ADIF_REC_RE = re.compile(b'<(.*?):(\d+).*?>([^<\t\f\v]+)')
ADIF_REC_RE = re.compile(b"<(.*?):(\d+).*?>([^<\t\f\v]+)")


def parse(s):
raw = re.split(b'<eor>|<eoh>(?i)', s)
raw = re.split(b"<eor>|<eoh>(?i)", s)
logbook = []
for record in raw[1:-1]:
qso = {}
tags = ADIF_REC_RE.findall(record)
for tag in tags:
qso[tag[0].lower().decode("utf-8")] = tag[2][:int(tag[1])].decode("utf-8")
qso[tag[0].lower().decode("utf-8")] = tag[2][: int(tag[1])].decode("utf-8")
logbook.append(qso)
return logbook


def save(fn, data):
fh = open(fn, 'w')
fh.write('ADIF.PY by OK4BX\nhttp://web.bxhome.org\n<EOH>\n')
fh = open(fn, "w")
fh.write("ADIF.PY by OK4BX\nhttp://web.bxhome.org\n<EOH>\n")
for qso in data:
for key in sorted(qso):
value = qso[key]
fh.write('<%s:%i>%s ' % (key.upper(), len(value), value))
fh.write('<EOR>\n')
fh.write("<%s:%i>%s " % (key.upper(), len(value), value))
fh.write("<EOR>\n")
fh.close()


def conv_datetime(adi_date, adi_time):
return datetime.datetime.strptime(adi_date+adi_time.ljust(6, "0"), "%Y%m%d%H%M%S")
return datetime.datetime.strptime(adi_date + adi_time.ljust(6, "0"), "%Y%m%d%H%M%S")

+ 27
- 17
ahrl.py View File

@@ -6,14 +6,21 @@ import texttable
from flask_debugtoolbar import DebugToolbarExtension
from flask_migrate import MigrateCommand
from flask_script import Manager
from crons import update_qsos_without_countries, update_dxcc_from_cty_xml, \
populate_logs_gridsquare_cache, cron_sync_eqsl, update_qsos_from_hamqth, cron_sync_from_eqsl
from crons import (
update_qsos_without_countries,
update_dxcc_from_cty_xml,
populate_logs_gridsquare_cache,
cron_sync_eqsl,
update_qsos_from_hamqth,
cron_sync_from_eqsl,
)
from dbseed import make_db_seed
from models import db

try:
from raven.contrib.flask import Sentry
import raven

print(" * Sentry support loaded")
HAS_SENTRY = True
except ImportError as e:
@@ -25,10 +32,10 @@ from app import create_app
app = create_app()

if HAS_SENTRY:
app.config['SENTRY_RELEASE'] = raven.fetch_git_sha(os.path.dirname(__file__))
sentry = Sentry(app, dsn=app.config['SENTRY_DSN'])
app.config["SENTRY_RELEASE"] = raven.fetch_git_sha(os.path.dirname(__file__))
sentry = Sentry(app, dsn=app.config["SENTRY_DSN"])
print(" * Sentry support activated")
print(" * Sentry DSN: %s" % app.config['SENTRY_DSN'])
print(" * Sentry DSN: %s" % app.config["SENTRY_DSN"])

toolbar = DebugToolbarExtension(app)
manager = Manager(app)
@@ -40,14 +47,14 @@ def routes():
"""Dump all routes of defined app"""
table = texttable.Texttable()
table.set_deco(texttable.Texttable().HEADER)
table.set_cols_dtype(['t', 't', 't'])
table.set_cols_dtype(["t", "t", "t"])
table.set_cols_align(["l", "l", "l"])
table.set_cols_width([60, 30, 90])

table.add_rows([["Prefix", "Verb", "URI Pattern"]])

for rule in sorted(app.url_map.iter_rules(), key=lambda x: str(x)):
methods = ','.join(rule.methods)
methods = ",".join(rule.methods)
table.add_row([rule.endpoint, methods, rule])

print(table.draw())
@@ -64,8 +71,9 @@ def seed():
"""Seed database with default content"""
make_db_seed(db)

CacheCommand = Manager(usage='Perform cache actions')
CronCommand = Manager(usage='Perform crons actions')

CacheCommand = Manager(usage="Perform cache actions")
CronCommand = Manager(usage="Perform crons actions")


@CronCommand.command
@@ -85,8 +93,9 @@ def update_qsos_countries():


@CronCommand.command
@CronCommand.option('--dryrun', dest='dry_run', action='store_true', default=False,
help="Dry run, doesn't commit anything")
@CronCommand.option(
"--dryrun", dest="dry_run", action="store_true", default=False, help="Dry run, doesn't commit anything"
)
def sync_to_eqsl(dry_run=False):
"""Push to eQSL logs with requested eQSL sync"""
print("-- STARTED on {0}".format(datetime.datetime.now()))
@@ -95,8 +104,9 @@ def sync_to_eqsl(dry_run=False):


@CronCommand.command
@CronCommand.option('--dryrun', dest='dry_run', action='store_true', default=False,
help="Dry run, doesn't commit anything")
@CronCommand.option(
"--dryrun", dest="dry_run", action="store_true", default=False, help="Dry run, doesn't commit anything"
)
def sync_from_eqsl(dry_run=False):
"""Fetch from eQSL logs """
print("-- STARTED on {0}".format(datetime.datetime.now()))
@@ -120,11 +130,11 @@ def populate_logs_gridsquare():
print("-- FINISHED on {0}".format(datetime.datetime.now()))


manager.add_command('db', MigrateCommand)
manager.add_command('cache', CacheCommand)
manager.add_command('cron', CronCommand)
manager.add_command("db", MigrateCommand)
manager.add_command("cache", CacheCommand)
manager.add_command("cron", CronCommand)

if __name__ == '__main__':
if __name__ == "__main__":
try:
manager.run()
except KeyboardInterrupt as e:

+ 24
- 27
app.py View File

@@ -37,16 +37,16 @@ def create_app(cfg=None):

Bootstrap(app)

app.jinja_env.add_extension('jinja2.ext.with_')
app.jinja_env.add_extension('jinja2.ext.do')
app.jinja_env.filters['localize'] = dt_utc_to_user_tz
app.jinja_env.filters['show_date_no_offset'] = show_date_no_offset
app.jinja_env.add_extension("jinja2.ext.with_")
app.jinja_env.add_extension("jinja2.ext.do")
app.jinja_env.filters["localize"] = dt_utc_to_user_tz
app.jinja_env.filters["show_date_no_offset"] = show_date_no_offset
app.jinja_env.globals.update(is_admin=is_admin)

# Logging
if not app.debug:
formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]')
file_handler = RotatingFileHandler("%s/errors_app.log" % os.getcwd(), 'a', 1000000, 1)
formatter = logging.Formatter("%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]")
file_handler = RotatingFileHandler("%s/errors_app.log" % os.getcwd(), "a", 1000000, 1)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)
app.logger.addHandler(file_handler)
@@ -57,22 +57,21 @@ def create_app(cfg=None):
db.init_app(app)

# Setup Flask-Security
security = Security(app, user_datastore,
register_form=ExtendedRegisterForm)
security = Security(app, user_datastore, register_form=ExtendedRegisterForm)

git_version = ""
gitpath = os.path.join(os.getcwd(), ".git")
if os.path.isdir(gitpath):
git_version = subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD'])
git_version = subprocess.check_output(["git", "rev-parse", "--short", "HEAD"])
if git_version:
git_version = git_version.strip().decode('UTF-8')
git_version = git_version.strip().decode("UTF-8")

@app.before_request
def before_request():
g.cfg = {
'AHRL_VERSION_VER': __VERSION__,
'AHRL_VERSION_GIT': git_version,
'AHRL_VERSION': "{0} ({1})".format(__VERSION__, git_version),
"AHRL_VERSION_VER": __VERSION__,
"AHRL_VERSION_GIT": git_version,
"AHRL_VERSION": "{0} ({1})".format(__VERSION__, git_version),
}

@app.errorhandler(InvalidUsage)
@@ -81,7 +80,7 @@ def create_app(cfg=None):
response.status_code = error.status_code
return response

pictures = UploadSet('pictures', IMAGES)
pictures = UploadSet("pictures", IMAGES)
configure_uploads(app, pictures)

app.register_blueprint(bp_main)
@@ -95,33 +94,31 @@ def create_app(cfg=None):
app.register_blueprint(bp_extapi)

# Used in development
@app.route('/uploads/<path:stuff>', methods=['GET'])
@app.route("/uploads/<path:stuff>", methods=["GET"])
def get_uploads_stuff(stuff):
print("Get {0} from {1}".format(stuff, app.config['UPLOADS_DEFAULT_DEST']))
return send_from_directory(app.config['UPLOADS_DEFAULT_DEST'], stuff, as_attachment=False)
print("Get {0} from {1}".format(stuff, app.config["UPLOADS_DEFAULT_DEST"]))
return send_from_directory(app.config["UPLOADS_DEFAULT_DEST"], stuff, as_attachment=False)

@app.errorhandler(404)
def page_not_found(msg):
pcfg = {"title": "Whoops, something failed.",
"error": 404, "message": "Page not found", "e": msg}
return render_template('error_page.jinja2', pcfg=pcfg), 404
pcfg = {"title": "Whoops, something failed.", "error": 404, "message": "Page not found", "e": msg}
return render_template("error_page.jinja2", pcfg=pcfg), 404

@app.errorhandler(403)
def err_forbidden(msg):
pcfg = {"title": "Whoops, something failed.",
"error": 403, "message": "Access forbidden", "e": msg}
return render_template('error_page.jinja2', pcfg=pcfg), 403
pcfg = {"title": "Whoops, something failed.", "error": 403, "message": "Access forbidden", "e": msg}
return render_template("error_page.jinja2", pcfg=pcfg), 403

@app.errorhandler(410)
def err_gone(msg):
pcfg = {"title": "Whoops, something failed.",
"error": 410, "message": "Gone", "e": msg}
return render_template('error_page.jinja2', pcfg=pcfg), 410
pcfg = {"title": "Whoops, something failed.", "error": 410, "message": "Gone", "e": msg}
return render_template("error_page.jinja2", pcfg=pcfg), 410

if not app.debug:

@app.errorhandler(500)
def err_failed(msg):
pcfg = {"title": "Whoops, something failed.", "error": 500, "message": "Something is broken", "e": msg}
return render_template('error_page.jinja2', pcfg=pcfg), 500
return render_template("error_page.jinja2", pcfg=pcfg), 500

return app

+ 9
- 9
controllers/admin.py View File

@@ -5,32 +5,32 @@ from forms import ConfigForm
from models import db, Logging, Config
from utils import check_default_profile, is_admin

bp_admin = Blueprint('bp_admin', __name__)
bp_admin = Blueprint("bp_admin", __name__)


@bp_admin.route('/admin/logs', methods=['GET'])
@bp_admin.route("/admin/logs", methods=["GET"])
@login_required
@check_default_profile
def logs():
if not is_admin():
return redirect(url_for('bp_home.home'))
return redirect(url_for("bp_home.home"))
pcfg = {"title": "Application Logs"}
_logs = Logging.query.order_by(Logging.timestamp.desc()).limit(100).all()
return render_template('admin/logs.jinja2', pcfg=pcfg, logs=_logs)
return render_template("admin/logs.jinja2", pcfg=pcfg, logs=_logs)


@bp_admin.route('/admin/config', methods=['GET', 'POST'])
@bp_admin.route("/admin/config", methods=["GET", "POST"])
@login_required
@check_default_profile
def config():
if not is_admin():
return redirect(url_for('bp_main.home'))
return redirect(url_for("bp_main.home"))

pcfg = {"title": "Application Config"}

_config = Config.query.one()
if not _config:
flash("Config not found", 'error')
flash("Config not found", "error")
return redirect(url_for("bp_main.home"))

form = ConfigForm(request.form, obj=_config)
@@ -47,6 +47,6 @@ def config():

db.session.commit()
flash("Configuration updated", "info")
return redirect(url_for('bp_admin.config'))
return redirect(url_for("bp_admin.config"))

return render_template('admin/config.jinja2', pcfg=pcfg, form=form)
return render_template("admin/config.jinja2", pcfg=pcfg, form=form)

+ 5
- 5
controllers/api.py View File

@@ -4,10 +4,10 @@ from flask_security import login_required, current_user
from models import db, Apitoken
from utils import generate_uniques_apitoken

bp_api = Blueprint('bp_api', __name__)
bp_api = Blueprint("bp_api", __name__)


@bp_api.route('/api/token/new')
@bp_api.route("/api/token/new")
@login_required
def apitoken_new():
apitoken = generate_uniques_apitoken()
@@ -20,10 +20,10 @@ def apitoken_new():
a.secret = apitoken["secret"]
db.session.add(a)
db.session.commit()
return redirect(url_for('bp_users.user_profile'))
return redirect(url_for("bp_users.user_profile"))


@bp_api.route('/api/token/<string:apit>/del')
@bp_api.route("/api/token/<string:apit>/del")
@login_required
def apitoken_del(apit):
apitoken = Apitoken.query.filter(Apitoken.id == apit).first()
@@ -33,4 +33,4 @@ def apitoken_del(apit):

db.session.delete(apitoken)
db.session.commit()
return redirect(url_for('bp_users.user_profile'))
return redirect(url_for("bp_users.user_profile"))

+ 63
- 56
controllers/contacts.py View File

@@ -10,21 +10,26 @@ from libjambon import geo_bearing_star
from models import db, Contact, User, Logbook, Log
from utils import check_default_profile, InvalidUsage

bp_contacts = Blueprint('bp_contacts', __name__)
bp_contacts = Blueprint("bp_contacts", __name__)


@bp_contacts.route('/contacts', methods=['GET'])
@bp_contacts.route("/contacts", methods=["GET"])
@login_required
@check_default_profile
def contacts():
pcfg = {"title": "My contacts"}
_contacts = Contact.query.filter(Contact.user_id == current_user.id).all()
logbooks = db.session.query(Logbook.id, Logbook.name, func.count(Log.id)).join(
Log).filter(Logbook.user_id == current_user.id).group_by(Logbook.id).all()
return render_template('contacts/view.jinja2', pcfg=pcfg, contacts=_contacts, logbooks=logbooks)
logbooks = (
db.session.query(Logbook.id, Logbook.name, func.count(Log.id))
.join(Log)
.filter(Logbook.user_id == current_user.id)
.group_by(Logbook.id)
.all()
)
return render_template("contacts/view.jinja2", pcfg=pcfg, contacts=_contacts, logbooks=logbooks)


@bp_contacts.route('/contacts/<int:contact_id>/edit', methods=['GET', 'POST'])
@bp_contacts.route("/contacts/<int:contact_id>/edit", methods=["GET", "POST"])
@login_required
@check_default_profile
def edit(contact_id):
@@ -41,33 +46,39 @@ def edit(contact_id):
a.gridsquare = form.gridsquare.data

if not current_user.locator or not form.gridsquare.data:
flash('Missing locator_qso or locator_user', 'error')
flash("Missing locator_qso or locator_user", "error")
return redirect(url_for("bp_contacts.contacts"))

if not is_valid_qth(current_user.locator, 6) or not is_valid_qth(form.gridsquare.data, 6):
flash('One of the supplied QTH is not valid', 'error')
flash("One of the supplied QTH is not valid", "error")
return redirect(url_for("bp_contacts.contacts"))

_f = qth_to_coords(current_user.locator, 6) # precision, latitude, longitude
_t = qth_to_coords(form.gridsquare.data, 6) # precision, latitude, longitude

a.latitude = _t['latitude']
a.longitude = _t['longitude']
a.distance = distance.haversine_km(_f['latitude'], _f['longitude'], _t['latitude'], _t['longitude'])
a.bearing = bearing.initial_compass_bearing(_f['latitude'], _f['longitude'], _t['latitude'], _t['longitude'])
a.latitude = _t["latitude"]
a.longitude = _t["longitude"]
a.distance = distance.haversine_km(_f["latitude"], _f["longitude"], _t["latitude"], _t["longitude"])
a.bearing = bearing.initial_compass_bearing(_f["latitude"], _f["longitude"], _t["latitude"], _t["longitude"])
a.bearing_star = geo_bearing_star(a.bearing)

db.session.commit()
flash("Success saving contact: {0}".format(a.callsign), 'success')
return redirect(url_for('bp_contacts.contacts'))
flash("Success saving contact: {0}".format(a.callsign), "success")
return redirect(url_for("bp_contacts.contacts"))

logbooks = db.session.query(Logbook.id, Logbook.name, func.count(Log.id)).join(
Log).filter(Logbook.user_id == current_user.id).group_by(Logbook.id).all()
return render_template('contacts/edit.jinja2', pcfg=pcfg, form=form, contact=a,
contact_id=contact_id, logbooks=logbooks)
logbooks = (
db.session.query(Logbook.id, Logbook.name, func.count(Log.id))
.join(Log)
.filter(Logbook.user_id == current_user.id)
.group_by(Logbook.id)
.all()
)
return render_template(
"contacts/edit.jinja2", pcfg=pcfg, form=form, contact=a, contact_id=contact_id, logbooks=logbooks
)


@bp_contacts.route('/contacts/new', methods=['GET', 'POST'])
@bp_contacts.route("/contacts/new", methods=["GET", "POST"])
@login_required
@check_default_profile
def new():
@@ -81,35 +92,40 @@ def new():
a.gridsquare = form.gridsquare.data

if not current_user.locator or not form.gridsquare.data:
flash('Missing locator_qso or locator_user', 'error')
flash("Missing locator_qso or locator_user", "error")
return redirect(url_for("bp_contacts.contacts"))

if not is_valid_qth(current_user.locator, 6) or not is_valid_qth(form.gridsquare.data, 6):
flash('One of the supplied QTH is not valid', 'error')
flash("One of the supplied QTH is not valid", "error")
return redirect(url_for("bp_contacts.contacts"))

_f = qth_to_coords(current_user.locator, 6) # precision, latitude, longitude
_t = qth_to_coords(form.gridsquare.data, 6) # precision, latitude, longitude

a.latitude = _t['latitude']
a.longitude = _t['longitude']
a.distance = distance.haversine_km(_f['latitude'], _f['longitude'], _t['latitude'], _t['longitude'])
a.bearing = bearing.initial_compass_bearing(_f['latitude'], _f['longitude'], _t['latitude'], _t['longitude'])
a.latitude = _t["latitude"]
a.longitude = _t["longitude"]
a.distance = distance.haversine_km(_f["latitude"], _f["longitude"], _t["latitude"], _t["longitude"])
a.bearing = bearing.initial_compass_bearing(_f["latitude"], _f["longitude"], _t["latitude"], _t["longitude"])
a.bearing_star = geo_bearing_star(a.bearing)

a.user_id = current_user.id

db.session.add(a)
db.session.commit()
flash("Success updating contact: {0}".format(a.callsign), 'success')
return redirect(url_for('bp_contacts.contacts'))
flash("Success updating contact: {0}".format(a.callsign), "success")
return redirect(url_for("bp_contacts.contacts"))

logbooks = db.session.query(Logbook.id, Logbook.name, func.count(Log.id)).join(
Log).filter(Logbook.user_id == current_user.id).group_by(Logbook.id).all()
return render_template('contacts/new.jinja2', pcfg=pcfg, form=form, logbooks=logbooks)
logbooks = (
db.session.query(Logbook.id, Logbook.name, func.count(Log.id))
.join(Log)
.filter(Logbook.user_id == current_user.id)
.group_by(Logbook.id)
.all()
)
return render_template("contacts/new.jinja2", pcfg=pcfg, form=form, logbooks=logbooks)


@bp_contacts.route('/contacts/<int:contact_id>/delete', methods=['GET', 'DELETE', 'PUT'])
@bp_contacts.route("/contacts/<int:contact_id>/delete", methods=["GET", "DELETE", "PUT"])
@login_required
@check_default_profile
def delete(contact_id):
@@ -120,43 +136,37 @@ def delete(contact_id):

db.session.delete(contact)
db.session.commit()
flash("Success deleting contact: {0}".format(contact.callsign), 'success')
return redirect(url_for('bp_contacts.contacts'))
flash("Success deleting contact: {0}".format(contact.callsign), "success")
return redirect(url_for("bp_contacts.contacts"))


@bp_contacts.route('/contacts/<string:username>/geojson', methods=['GET'])
@bp_contacts.route("/contacts/<string:username>/geojson", methods=["GET"])
def contacts_geojson(username):
if not username:
raise InvalidUsage('Missing username', status_code=400)
raise InvalidUsage("Missing username", status_code=400)

user = User.query.filter(User.name == username).first()
if not user:
raise InvalidUsage('User not found', status_code=404)
raise InvalidUsage("User not found", status_code=404)

_contacts = Contact.query.filter(User.id == user.id).all()

if not is_valid_qth(user.locator, 6):
raise InvalidUsage('QTH is not valid', status_code=400)
raise InvalidUsage("QTH is not valid", status_code=400)
_u = qth_to_coords(user.locator, 6) # precision, latitude, longitude

j = [{
"type": "Feature",
"properties": {
"name": user.cutename(),
"callsign": user.callsign,
"own": True,
"icon": "home"
},
"geometry": {
"type": "Point",
"coordinates": [_u['longitude'], _u['latitude']]
j = [
{
"type": "Feature",
"properties": {"name": user.cutename(), "callsign": user.callsign, "own": True, "icon": "home"},
"geometry": {"type": "Point", "coordinates": [_u["longitude"], _u["latitude"]]},
}
}]
]

for log in _contacts:
if log.gridsquare:
if not is_valid_qth(log.gridsquare, 6):
raise InvalidUsage('QTH is not valid', status_code=400)
raise InvalidUsage("QTH is not valid", status_code=400)
_f = qth_to_coords(log.gridsquare, 6) # precision, latitude, longitude
else:
_f = log.country_grid_coords()
@@ -170,13 +180,10 @@ def contacts_geojson(username):
"distance": log.distance,
"bearing": log.bearing,
"bearing_star": log.bearing_star,
"icon": "qso"
"icon": "qso",
},
"geometry": {
"type": "Point",
"coordinates": [_f['longitude'], _f['latitude']]
}
"geometry": {"type": "Point", "coordinates": [_f["longitude"], _f["latitude"]]},
}
j.append(f)

return Response(json.dumps(j), mimetype='application/json')
return Response(json.dumps(j), mimetype="application/json")

+ 7
- 7
controllers/extapi.py View File

@@ -3,7 +3,7 @@ from xmlrpc.server import CGIXMLRPCRequestHandler, SimpleXMLRPCDispatcher
from adif import parse as adif_parser
from models import db, Log, Band, Mode

bp_extapi = Blueprint('bp_extapi', __name__)
bp_extapi = Blueprint("bp_extapi", __name__)

handler = SimpleXMLRPCDispatcher(allow_none=True, encoding=None)

@@ -22,7 +22,7 @@ handler.register_introspection_functions()
def add_record(adif_record):
print("Log.add_record")
# FLDIGI send only a record, add fake end-of-header to not break parser
parsed = adif_parser(b'<eoh>' + adif_record.encode('UTF-8'))
parsed = adif_parser(b"<eoh>" + adif_record.encode("UTF-8"))
if len(parsed) >= 1:
parsed = parsed[0]
"""
@@ -42,19 +42,19 @@ def get_record(call):
print("Log.get_record")
return "xxx"


handler.register_function(add_record, name="log.add_record")
handler.register_function(check_dup, name="log.check_dup")
handler.register_function(get_record, name="log.get_record")


@bp_extapi.route('/extapi/<path:whatever>', methods=['POST', 'GET'])
@bp_extapi.route("/extapi/<path:whatever>", methods=["POST", "GET"])
def catchall(whatever):
print("You wanted: {0}".format(whatever))


@bp_extapi.route('/extapi/RPC2', methods=['POST', 'GET'])
@bp_extapi.route("/extapi/RPC2", methods=["POST", "GET"])
def endpoint():
dispatch = getattr(handler, '_dispatch', None)
dispatch = getattr(handler, "_dispatch", None)
req = handler._marshaled_dispatch(request.data, dispatch)
return Response(req, mimetype='text/xml')

return Response(req, mimetype="text/xml")

+ 36
- 29
controllers/logbooks.py View File

@@ -6,38 +6,48 @@ from forms import LogbookForm
from models import db, Logbook, User, Log
from utils import check_default_profile

bp_logbooks = Blueprint('bp_logbooks', __name__)
bp_logbooks = Blueprint("bp_logbooks", __name__)


@bp_logbooks.route('/logbooks/<string:user>', methods=['GET'])
@bp_logbooks.route("/logbooks/<string:user>", methods=["GET"])
@check_default_profile
def logbooks(user):
user = User.query.filter(User.name == user).first()
if not user:
flash("User not found", 'error')
return redirect(url_for('bp_main.home'))
flash("User not found", "error")
return redirect(url_for("bp_main.home"))

pcfg = {"title": "{0}'s logbooks".format(user.name)}

if current_user.is_authenticated:
_logbooks = db.session.query(Logbook.id, Logbook.name, func.count(Log.id)).join(
Log).filter(Logbook.user_id == current_user.id).group_by(Logbook.id).all()
_logbooks = (
db.session.query(Logbook.id, Logbook.name, func.count(Log.id))
.join(Log)
.filter(Logbook.user_id == current_user.id)
.group_by(Logbook.id)
.all()
)
else:
_logbooks = db.session.query(Logbook.id, Logbook.name, func.count(Log.id)).join(
Log).filter(Logbook.user_id == user.id, Logbook.public.is_(True)).group_by(Logbook.id).all()
_logbooks = (
db.session.query(Logbook.id, Logbook.name, func.count(Log.id))
.join(Log)
.filter(Logbook.user_id == user.id, Logbook.public.is_(True))
.group_by(Logbook.id)
.all()
)

return render_template('logbooks/logbooks.jinja2', pcfg=pcfg, user=user, logbooks=_logbooks)
return render_template("logbooks/logbooks.jinja2", pcfg=pcfg, user=user, logbooks=_logbooks)


@bp_logbooks.route('/logbooks/<string:logbook_slug>/edit', methods=['GET', 'POST'])
@bp_logbooks.route("/logbooks/<string:logbook_slug>/edit", methods=["GET", "POST"])
@login_required
@check_default_profile
def edit(logbook_slug):
pcfg = {"title": "Edit my logbooks"}
a = Logbook.query.filter(Logbook.user_id == current_user.id, Logbook.slug == logbook_slug).first()
if not a:
flash("Logbook not found", 'error')
return redirect(url_for('bp_logbooks.logbooks', user=current_user.name))
flash("Logbook not found", "error")
return redirect(url_for("bp_logbooks.logbooks", user=current_user.name))

form = LogbookForm(request.form, obj=a)

@@ -62,13 +72,13 @@ def edit(logbook_slug):
fl.default = True

db.session.commit()
flash("Success saving logbook: {0}".format(a.name), 'success')
return redirect(url_for('bp_logbooks.logbooks', user=current_user.name))
flash("Success saving logbook: {0}".format(a.name), "success")
return redirect(url_for("bp_logbooks.logbooks", user=current_user.name))

return render_template('logbooks/edit.jinja2', pcfg=pcfg, form=form, logbook=a, logbook_slug=logbook_slug)
return render_template("logbooks/edit.jinja2", pcfg=pcfg, form=form, logbook=a, logbook_slug=logbook_slug)


@bp_logbooks.route('/logbooks/new', methods=['GET', 'POST'])
@bp_logbooks.route("/logbooks/new", methods=["GET", "POST"])
@login_required
@check_default_profile
def new():
@@ -76,7 +86,7 @@ def new():

form = LogbookForm()

if request.method == 'GET':
if request.method == "GET":
form.callsign.data = current_user.callsign
form.locator.data = current_user.locator
form.default.data = False
@@ -95,10 +105,7 @@ def new():
a.default = form.default.data

if form.default.data:
cur_dflt = Logbook.query.filter(
Logbook.user_id == current_user.id,
Logbook.default.is_(True)
).all()
cur_dflt = Logbook.query.filter(Logbook.user_id == current_user.id, Logbook.default.is_(True)).all()
for l in cur_dflt:
l.default = False
else:
@@ -111,24 +118,24 @@ def new():
db.session.add(a)
db.session.commit()

flash("Success updating logbook: {0}".format(a.name), 'success')
return redirect(url_for('bp_logbooks.logbooks', user=current_user.name))
flash("Success updating logbook: {0}".format(a.name), "success")
return redirect(url_for("bp_logbooks.logbooks", user=current_user.name))

return render_template('logbooks/new.jinja2', pcfg=pcfg, form=form)
return render_template("logbooks/new.jinja2", pcfg=pcfg, form=form)


@bp_logbooks.route('/logbooks/<string:logbook_slug>/delete', methods=['GET', 'DELETE', 'PUT'])
@bp_logbooks.route("/logbooks/<string:logbook_slug>/delete", methods=["GET", "DELETE", "PUT"])
@login_required
@check_default_profile
def delete(logbook_slug):
logbook = Logbook.query.filter(Logbook.user_id == current_user.id, Logbook.slug == logbook_slug).first()
if not logbook:
flash("Logbook not found", 'error')
return redirect(url_for('bp_logbooks.logbooks', user=current_user.name))
flash("Logbook not found", "error")
return redirect(url_for("bp_logbooks.logbooks", user=current_user.name))

db.session.delete(logbook)
db.session.commit()

flash("Success deleting logbook: {0}".format(logbook.name), 'success')
flash("Success deleting logbook: {0}".format(logbook.name), "success")

return redirect(url_for('bp_logbooks.logbooks', user=current_user.name))
return redirect(url_for("bp_logbooks.logbooks", user=current_user.name))

+ 13
- 8
controllers/main.py View File

@@ -4,11 +4,11 @@ from sqlalchemy import func

from models import db, User, Logbook, Log, DxccEntities, DxccExceptions, DxccPrefixes

bp_main = Blueprint('bp_main', __name__)
bp_main = Blueprint("bp_main", __name__)


# Show public logbooks
@bp_main.route('/')
@bp_main.route("/")
def home():
# Sanity check
_dp = db.session.query(DxccPrefixes.id).count()
@@ -19,20 +19,25 @@ def home():
# check are lowered to avoid changing them maybe too frequently
if _dp < 3500 or _dx < 16300 or _de < 300:
flash("DXCC Tables are empty, check manual", "error")
#abort(500)
# abort(500)

pcfg = {"title": "AHRL - Another Ham Radio Log"}
users = User.query.all()

logbooks = None
if current_user.is_authenticated:
logbooks = db.session.query(Logbook.id, Logbook.slug, Logbook.name, func.count(Log.id)).join(
Log).filter(Logbook.user_id == current_user.id).group_by(Logbook.id).all()
logbooks = (
db.session.query(Logbook.id, Logbook.slug, Logbook.name, func.count(Log.id))
.join(Log)
.filter(Logbook.user_id == current_user.id)
.group_by(Logbook.id)
.all()
)

return render_template('home.jinja2', pcfg=pcfg, users=users, logbooks=logbooks)
return render_template("home.jinja2", pcfg=pcfg, users=users, logbooks=logbooks)


@bp_main.route('/about')
@bp_main.route("/about")
def about():
pcfg = {"title": "About AHRL - Another Ham Radio Log"}
return render_template('about.jinja2', pcfg=pcfg)
return render_template("about.jinja2", pcfg=pcfg)

+ 31
- 21
controllers/notes.py View File

@@ -6,23 +6,28 @@ from forms import NoteForm
from models import db, Note, Logbook, Log
from utils import check_default_profile

bp_notes = Blueprint('bp_notes', __name__)
bp_notes = Blueprint("bp_notes", __name__)


@bp_notes.route('/notes', methods=['GET'])
@bp_notes.route("/notes", methods=["GET"])
@login_required
@check_default_profile
def notes():
pcfg = {"title": "My notes"}

_notes = Note.query.filter(Note.user_id == current_user.id).all()
logbooks = db.session.query(Logbook.id, Logbook.name, func.count(Log.id)).join(
Log).filter(Logbook.user_id == current_user.id).group_by(Logbook.id).all()
logbooks = (
db.session.query(Logbook.id, Logbook.name, func.count(Log.id))
.join(Log)
.filter(Logbook.user_id == current_user.id)
.group_by(Logbook.id)
.all()
)

return render_template('notes/view.jinja2', pcfg=pcfg, notes=_notes, logbooks=logbooks)
return render_template("notes/view.jinja2", pcfg=pcfg, notes=_notes, logbooks=logbooks)


@bp_notes.route('/notes/<int:note_id>/edit', methods=['GET', 'POST'])
@bp_notes.route("/notes/<int:note_id>/edit", methods=["GET", "POST"])
@login_required
@check_default_profile
def edit(note_id):
@@ -30,8 +35,8 @@ def edit(note_id):

a = Note.query.filter(Note.user_id == current_user.id, Note.id == note_id).first()
if not a:
flash("Note not found", 'error')
return redirect(url_for('bp_notes.notes'))
flash("Note not found", "error")
return redirect(url_for("bp_notes.notes"))

form = NoteForm(request.form, obj=a)

@@ -42,15 +47,15 @@ def edit(note_id):

db.session.commit()

flash("Success saving note: {0}".format(a.title), 'success')
return redirect(url_for('bp_notes.notes'))
flash("Success saving note: {0}".format(a.title), "success")
return redirect(url_for("bp_notes.notes"))

logbooks = Logbook.query.filter(Logbook.user_id == current_user.id).all()

return render_template('notes/edit.jinja2', pcfg=pcfg, form=form, note=a, note_id=note_id, logbooks=logbooks)
return render_template("notes/edit.jinja2", pcfg=pcfg, form=form, note=a, note_id=note_id, logbooks=logbooks)


@bp_notes.route('/notes/new', methods=['GET', 'POST'])
@bp_notes.route("/notes/new", methods=["GET", "POST"])
@login_required
@check_default_profile
def new():
@@ -68,24 +73,29 @@ def new():
db.session.add(a)
db.session.commit()

flash("Success updating note: {0}".format(a.title), 'success')
return redirect(url_for('bp_notes.notes'))
flash("Success updating note: {0}".format(a.title), "success")
return redirect(url_for("bp_notes.notes"))

logbooks = db.session.query(Logbook.id, Logbook.name, func.count(Log.id)).join(
Log).filter(Logbook.user_id == current_user.id).group_by(Logbook.id).all()
return render_template('notes/new.jinja2', pcfg=pcfg, form=form, logbooks=logbooks)
logbooks = (
db.session.query(Logbook.id, Logbook.name, func.count(Log.id))
.join(Log)
.filter(Logbook.user_id == current_user.id)
.group_by(Logbook.id)
.all()
)
return render_template("notes/new.jinja2", pcfg=pcfg, form=form, logbooks=logbooks)


@bp_notes.route('/notes/<int:note_id>/delete', methods=['GET', 'DELETE', 'PUT'])
@bp_notes.route("/notes/<int:note_id>/delete", methods=["GET", "DELETE", "PUT"])
@login_required
@check_default_profile
def delete(note_id):
note = Note.query.filter(Note.user_id == current_user.id, Note.id == note_id).first()
if not note:
flash("Note not found", 'error')
return redirect(url_for('bp_notes.notes'))
flash("Note not found", "error")
return redirect(url_for("bp_notes.notes"))

db.session.delete(note)
db.session.commit()

return redirect(url_for('bp_notes.notes'))
return redirect(url_for("bp_notes.notes"))

+ 533
- 394
controllers/qsos.py
File diff suppressed because it is too large
View File


+ 119
- 108
controllers/tools.py View File

@@ -12,19 +12,19 @@ from models import db, Log, Mode, User, Logbook, Band
from utils import check_default_profile, InvalidUsage
from libjambon import ADIF_FIELDS, coordinates2adif, adif2coordinates

bp_tools = Blueprint('bp_tools', __name__)
bp_tools = Blueprint("bp_tools", __name__)


@bp_tools.route('/tools/adif/import', methods=['GET'])
@bp_tools.route("/tools/adif/import", methods=["GET"])
@login_required
@check_default_profile
def adif_import():
pcfg = {"title": "Import ADIF"}
form = AdifParse()
return render_template('tools/adif_import.jinja2', pcfg=pcfg, form=form)
return render_template("tools/adif_import.jinja2", pcfg=pcfg, form=form)


@bp_tools.route('/logbooks/adif/import', methods=['POST'])
@bp_tools.route("/logbooks/adif/import", methods=["POST"])
@login_required
@check_default_profile
def adif_import_file():
@@ -48,12 +48,18 @@ def adif_import_file():

for log in parsed_adif:
# First check if duplicate
_date = "{0} {1}".format(log['qso_date'], log['time_on'])
_date = "{0} {1}".format(log["qso_date"], log["time_on"])
_date_wo_tz = datetime.datetime.strptime(_date, "%Y%m%d %H%M%S")
duplicates_count = db.session.query(Log.id).filter(Log.user_id == current_user.id,
Log.call == log['call'],
Log.time_on == _date_wo_tz,
Log.logbook_id == _logbook.id).count()
duplicates_count = (
db.session.query(Log.id)
.filter(
Log.user_id == current_user.id,
Log.call == log["call"],
Log.time_on == _date_wo_tz,
Log.logbook_id == _logbook.id,
)
.count()
)
if duplicates_count > 0:
duplicates += 1
continue # duplicate found, skip record
@@ -63,9 +69,9 @@ def adif_import_file():
if key not in log:
continue

if key == 'swl':
val = 1 if log[key] == 'Y' else 0
elif key == 'lon' or key == 'lat':
if key == "swl":
val = 1 if log[key] == "Y" else 0
elif key == "lon" or key == "lat":
val = adif2coordinates(log[key])
else:
val = log[key]
@@ -73,60 +79,56 @@ def adif_import_file():
setattr(l, key, val)

# other fields to manage specifically
if 'class' in log:
l.klass = log['class']
if 'band' in log:
band = Band.query.filter(Band.name == log['band'],
Band.start.is_(None),
Band.modes.is_(None)).first()
if "class" in log:
l.klass = log["class"]
if "band" in log:
band = Band.query.filter(Band.name == log["band"], Band.start.is_(None), Band.modes.is_(None)).first()
if not band:
band = Band.query.filter(Band.name == 'SSB',
Band.start.is_(None),
Band.modes.is_(None)).first()
band = Band.query.filter(Band.name == "SSB", Band.start.is_(None), Band.modes.is_(None)).first()
if not l.notes:
l.notes = ""
l.notes += "\r\nBand automatically set to 40m because not found in ADIF"
l.band_id = band.id
if 'freq' in log:
l.freq = int(float(log['freq']) * 1000000) # ADIF stores in MHz, we store in Hertz
if 'freq_rx' in log:
l.freq_rx = int(float(log['freq_rx']) * 1000000) # Same as freq
if 'mode' in log:
mode = Mode.query.filter(Mode.mode == log['mode']).first()
if "freq" in log:
l.freq = int(float(log["freq"]) * 1000000) # ADIF stores in MHz, we store in Hertz
if "freq_rx" in log:
l.freq_rx = int(float(log["freq_rx"]) * 1000000) # Same as freq
if "mode" in log:
mode = Mode.query.filter(Mode.mode == log["mode"]).first()
if not mode:
mode = Mode.query.filter(Mode.mode == 'SSB').first()
mode = Mode.query.filter(Mode.mode == "SSB").first()
if not l.notes:
l.notes = ""
l.notes += "\r\nMode automatically set to SSB because not found in ADIF"
l.mode_id = mode.id
# Reminder : ADIF is in UTC, we store in UTC, no TZ conversion necessary
# TIME_ON
if 'qso_date' in log and 'time_on':
date = "{0} {1}".format(log['qso_date'], log['time_on'])
if "qso_date" in log and "time_on":
date = "{0} {1}".format(log["qso_date"], log["time_on"])
date_wo_tz = datetime.datetime.strptime(date, "%Y%m%d %H%M%S")
l.time_on = date_wo_tz
else:
date_w_tz = datetime.datetime.utcnow()
l.time_on = date_w_tz.astimezone(pytz.timezone('UTC'))
l.time_on = date_w_tz.astimezone(pytz.timezone("UTC"))
if not l.notes:
l.notes = ""
l.notes = "\r\nDate and time_on set to the import date because not found in ADIF"
# TIME_OFF
if 'qso_date' in log and 'time_off':
date = "{0} {1}".format(log['qso_date'], log['time_off'])
if "qso_date" in log and "time_off":
date = "{0} {1}".format(log["qso_date"], log["time_off"])
date_wo_tz = datetime.datetime.strptime(date, "%Y%m%d %H%M%S")
l.time_off = date_wo_tz
else:
date_w_tz = datetime.datetime.utcnow()
l.time_off = date_w_tz.astimezone(pytz.timezone('UTC'))
l.time_off = date_w_tz.astimezone(pytz.timezone("UTC"))
if not l.notes:
l.notes = ""
l.notes = "\r\nDate and time_off set to the import date because not found in ADIF"

if 'comment' in log:
l.comment = log['comment']
if 'comment_intl' in log:
l.comment = log['comment_intl']
if "comment" in log:
l.comment = log["comment"]
if "comment_intl" in log:
l.comment = log["comment_intl"]
l.user = current_user # oops dont miss it
l.logbook_id = _logbook.id

@@ -134,14 +136,14 @@ def adif_import_file():
count += 1 # One more in the stack
db.session.commit()

flash('Imported {0} ({1} duplicates) QSOs from {2}'.format(count, duplicates, filename), 'info')
flash("Imported {0} ({1} duplicates) QSOs from {2}".format(count, duplicates, filename), "info")
else:
return render_template('tools/adif_import.jinja2', pcfg=pcfg, form=form, flash='Error with the file')
return render_template("tools/adif_import.jinja2", pcfg=pcfg, form=form, flash="Error with the file")

return redirect(url_for('bp_qsos.logbook', username=current_user.name, logbook_slug=_logbook.slug))
return redirect(url_for("bp_qsos.logbook", username=current_user.name, logbook_slug=_logbook.slug))


@bp_tools.route('/user/<string:username>/logbook/<string:logbook_slug>/adif/export', methods=['GET'])
@bp_tools.route("/user/<string:username>/logbook/<string:logbook_slug>/adif/export", methods=["GET"])
@login_required
def adif_export_dl(username, logbook_slug):
""" http://hamclubs.info/adif-validator/ """
@@ -157,56 +159,56 @@ def adif_export_dl(username, logbook_slug):

def a(k, v):
v = str(v)
return u"<{0}:{1}>{2} ".format(k, len(v), v)
return "<{0}:{1}>{2} ".format(k, len(v), v)

def ab(k, t, v):
v = str(v)
return u"<{0}:{1}:{2}>{3} ".format(k, len(v), t, v)
return "<{0}:{1}:{2}>{3} ".format(k, len(v), t, v)

def generate():
yield 'ADIF Export by AHRL\r\n'
yield '\r\n'
yield '<adif_ver:5>3.0.4\r\n'
yield '<programid:4>AHRL\r\n'
vers = '{0} git {1}'.format(g.cfg['AHRL_VERSION_VER'], g.cfg['AHRL_VERSION_GIT'])
yield a('programversion', vers) + '\r\n'
ct = datetime.datetime.utcnow().strftime('%Y%m%d %H%M%S')
yield a('created_timestamp', ct) + '\r\n'
yield ab('app_ahrl_station_callsign', 'S', user.callsign) + '\r\n'
yield ab('app_ahrl_operator', 'S', user.callsign) + '\r\n'
yield ab('app_ahrl_logbook', 'I', logbook.name) + '\r\n'
yield ab('app_ahrl_logbook_swl', 'B', 'Y' if logbook.swl else 'N') + '\r\n'
yield ab('app_ahrl_logbook_private', 'B', 'N' if logbook.public else 'Y') + '\r\n'
yield '\r\n'
yield '<eoh>\r\n\r\n'
yield "ADIF Export by AHRL\r\n"
yield "\r\n"
yield "<adif_ver:5>3.0.4\r\n"
yield "<programid:4>AHRL\r\n"
vers = "{0} git {1}".format(g.cfg["AHRL_VERSION_VER"], g.cfg["AHRL_VERSION_GIT"])
yield a("programversion", vers) + "\r\n"
ct = datetime.datetime.utcnow().strftime("%Y%m%d %H%M%S")
yield a("created_timestamp", ct) + "\r\n"
yield ab("app_ahrl_station_callsign", "S", user.callsign) + "\r\n"
yield ab("app_ahrl_operator", "S", user.callsign) + "\r\n"
yield ab("app_ahrl_logbook", "I", logbook.name) + "\r\n"
yield ab("app_ahrl_logbook_swl", "B", "Y" if logbook.swl else "N") + "\r\n"
yield ab("app_ahrl_logbook_private", "B", "N" if logbook.public else "Y") + "\r\n"
yield "\r\n"
yield "<eoh>\r\n\r\n"

for log in logs:
counter = 0
for key in ADIF_FIELDS:
if counter == 4:
counter = 0
yield '\r\n'
yield "\r\n"

value = getattr(log, key)
if value:
if key == 'swl' or key == 'force_init':
if key == "swl" or key == "force_init":
if value == 1:
val = 'Y'
val = "Y"
elif value == 2:
val = 'N'
val = "N"
else:
val = 'Y'
elif key == 'lat':
val = coordinates2adif(value, 'Latitude')
elif key == 'lon':
val = coordinates2adif(value, 'Longitude')
elif key == 'notes':
val = ''.join([i
if (126 >= ord(i) >= 32) or (i == '\r') or (i == '\n')
else ' ' for i in value])
elif key == 'iota':
val = "Y"
elif key == "lat":
val = coordinates2adif(value, "Latitude")
elif key == "lon":
val = coordinates2adif(value, "Longitude")
elif key == "notes":
val = "".join(
[i if (126 >= ord(i) >= 32) or (i == "\r") or (i == "\n") else " " for i in value]
)
elif key == "iota":
if len(value) == 5:
val = "{0}-{1}".format(value[0:2], value[2:5])
else:
@@ -216,60 +218,69 @@ def adif_export_dl(username, logbook_slug):
yield a(key, val)
counter += 1

yield '\r\n'
yield "\r\n"
# Manual ones
if log.freq:
yield a('freq', log.freq / 1000000.0)
yield a("freq", log.freq / 1000000.0)
if log.freq_rx:
yield a('freq', log.freq_rx / 1000000.0)
yield a("freq", log.freq_rx / 1000000.0)
if log.mode:
yield a('mode', log.mode.mode)
yield a("mode", log.mode.mode)
if log.time_on:
yield a('qso_date', log.time_on.strftime('%Y%m%d'))
yield a('time_on', log.time_on.strftime('%H%M%S'))
yield a("qso_date", log.time_on.strftime("%Y%m%d"))
yield a("time_on", log.time_on.strftime("%H%M%S"))
if log.time_off:
yield a('time_off', log.time_off.strftime('%H%M%S'))
yield a("time_off", log.time_off.strftime("%H%M%S"))
if log.klass:
yield a('class', log.klass)
yield a("class", log.klass)
if log.band:
yield a('band', log.band.name)
yield a("band", log.band.name)
if log.comment:
yield a('comment_intl', log.comment)
yield '\r\n<eor>\r\n\r\n'
yield a("comment_intl", log.comment)
yield "\r\n<eor>\r\n\r\n"

return Response(stream_with_context(generate()), mimetype="text/plain",
headers={"Content-Disposition": "attachment;filename=qsos-{0}.adi".format(user.name)})
return Response(
stream_with_context(generate()),
mimetype="text/plain",
headers={"Content-Disposition": "attachment;filename=qsos-{0}.adi".format(user.name)},
)


@bp_tools.route('/tools/bands/plan', methods=['GET'])
@bp_tools.route("/tools/bands/plan", methods=["GET"])
def bands_plan():
pcfg = {"title": "IARU Band Plans"}

bands = {
'iaru1': {
'name': 'IARU Zone 1',
'slug': 'iaru1',
'bands': db.session.query(Band.lower, Band.upper, Band.name).filter(
Band.modes.is_(None), Band.start.is_(None), Band.zone == 'iaru1').order_by(Band.lower.asc()).all()
"iaru1": {
"name": "IARU Zone 1",
"slug": "iaru1",
"bands": db.session.query(Band.lower, Band.upper, Band.name)
.filter(Band.modes.is_(None), Band.start.is_(None), Band.zone == "iaru1")
.order_by(Band.lower.asc())
.all(),
},
'iaru2': {
'name': 'IARU Zone 2',
'slug': 'iaru2',
'bands': db.session.query(Band.lower, Band.upper, Band.name).filter(
Band.modes.is_(None), Band.start.is_(None), Band.zone == 'iaru2').order_by(Band.lower.asc()).all()
"iaru2": {
"name": "IARU Zone 2",
"slug": "iaru2",
"bands": db.session.query(Band.lower, Band.upper, Band.name)
.filter(Band.modes.is_(None), Band.start.is_(None), Band.zone == "iaru2")
.order_by(Band.lower.asc())
.all(),
},
"iaru3": {
"name": "IARU Zone 3",
"slug": "iaru3",
"bands": db.session.query(Band.lower, Band.upper, Band.name)
.filter(Band.modes.is_(None), Band.start.is_(None), Band.zone == "iaru3")
.order_by(Band.lower.asc())
.all(),
},
'iaru3': {
'name': 'IARU Zone 3',
'slug': 'iaru3',
'bands': db.session.query(Band.lower, Band.upper, Band.name).filter(
Band.modes.is_(None), Band.start.is_(None), Band.zone == 'iaru3').order_by(Band.lower.asc()).all()
}
}
return render_template('tools/bands_plan.jinja2', pcfg=pcfg, bands=bands)
return render_template("tools/bands_plan.jinja2", pcfg=pcfg, bands=bands)


@bp_tools.route('/tools/map', methods=['GET'])
@bp_tools.route("/tools/map", methods=["GET"])
@check_default_profile
def map():
pcfg = {"title": "It's a map"}
return render_template('tools/map.jinja2', pcfg=pcfg)
return render_template("tools/map.jinja2", pcfg=pcfg)

+ 38
- 21
controllers/users.py View File

@@ -7,26 +7,33 @@ from forms import UserProfileForm
from models import db, User, Logbook, Log, UserLogging
from utils import check_default_profile

bp_users = Blueprint('bp_users', __name__)
bp_users = Blueprint("bp_users", __name__)


@bp_users.route('/user/logs', methods=['GET'])
@bp_users.route("/user/logs", methods=["GET"])
@login_required
@check_default_profile
def logs():
level = request.args.get('level')
level = request.args.get("level")
pcfg = {"title": "User Logs"}
if level:
_logs = UserLogging.query.filter(UserLogging.level == level.upper(),
UserLogging.user_id == current_user.id
).order_by(UserLogging.timestamp.desc()).limit(100).all()
_logs = (
UserLogging.query.filter(UserLogging.level == level.upper(), UserLogging.user_id == current_user.id)
.order_by(UserLogging.timestamp.desc())
.limit(100)
.all()
)
else:
_logs = UserLogging.query.filter(UserLogging.user_id == current_user.id
).order_by(UserLogging.timestamp.desc()).limit(100).all()
return render_template('users/user_logs.jinja2', pcfg=pcfg, logs=_logs)
_logs = (
UserLogging.query.filter(UserLogging.user_id == current_user.id)
.order_by(UserLogging.timestamp.desc())
.limit(100)
.all()
)
return render_template("users/user_logs.jinja2", pcfg=pcfg, logs=_logs)


@bp_users.route('/user', methods=['GET'])
@bp_users.route("/user", methods=["GET"])
@login_required
@check_default_profile
def profile():
@@ -34,22 +41,27 @@ def profile():

user = User.query.filter(User.id == current_user.id).first()
if not user:
flash("User not found", 'error')
flash("User not found", "error")
return redirect(url_for("bp_main.home"))

logbooks = db.session.query(Logbook.id, Logbook.name, func.count(Log.id)).join(
Log).filter(Logbook.user_id == current_user.id).group_by(Logbook.id).all()
return render_template('users/profile.jinja2', pcfg=pcfg, user=user, logbooks=logbooks)
logbooks = (
db.session.query(Logbook.id, Logbook.name, func.count(Log.id))
.join(Log)
.filter(Logbook.user_id == current_user.id)
.group_by(Logbook.id)
.all()
)
return render_template("users/profile.jinja2", pcfg=pcfg, user=user, logbooks=logbooks)


@bp_users.route('/user/edit', methods=['GET', 'POST'])
@bp_users.route("/user/edit", methods=["GET", "POST"])
@login_required
def edit():
pcfg = {"title": "Edit my profile"}

user = User.query.filter(User.id == current_user.id).first()
if not user:
flash("User not found", 'error')
flash("User not found", "error")
return redirect(url_for("bp_main.home"))

form = UserProfileForm(request.form, obj=user)
@@ -75,8 +87,13 @@ def edit():
user.zone = form.zone.data

db.session.commit()
return redirect(url_for('bp_users.profile'))

logbooks = db.session.query(Logbook.id, Logbook.name, func.count(Log.id)).join(
Log).filter(Logbook.user_id == current_user.id).group_by(Logbook.id).all()
return render_template('users/edit.jinja2', pcfg=pcfg, form=form, user=user, logbooks=logbooks)
return redirect(url_for("bp_users.profile"))

logbooks = (
db.session.query(Logbook.id, Logbook.name, func.count(Log.id))
.join(Log)
.filter(Logbook.user_id == current_user.id)
.group_by(Logbook.id)
.all()
)
return render_template("users/edit.jinja2", pcfg=pcfg, form=form, user=user, logbooks=logbooks)

+ 106
- 103
crons.py View File

@@ -41,30 +41,30 @@ def update_qsos_from_hamqth():
err.user_id = log.user.id
err.log_id = log.id
err.logbook_id = log.logbook.id
err.category = 'HamQTH'
err.level = 'ERROR'
err.message = 'Query failed or call not found: {0}'.format(e)
err.category = "HamQTH"
err.level = "ERROR"
err.message = "Query failed or call not found: {0}".format(e)
db.session.add(err)
log.consolidated_hamqth = True
continue

if 'nick' in _csd and not log.name:
log.name = _csd['nick']
if 'qth' in _csd and not log.qth:
log.qth = _csd['qth']
if 'grid' in _csd and not log.gridsquare:
log.gridsquare = _csd['grid']
if "nick" in _csd and not log.name:
log.name = _csd["nick"]
if "qth" in _csd and not log.qth:
log.qth = _csd["qth"]
if "grid" in _csd and not log.gridsquare:
log.gridsquare = _csd["grid"]
# if 'country' in _csd and not log.country:
# log.country = _csd['country']
# We must leave country filled by DXCC Clublog or Database I think finally
if 'latitude' in _csd and not log.lat:
log.lat = _csd['latitude']
if 'longitude' in _csd and not log.lon:
log.lon = _csd['longitude']
if 'web' in _csd and not log.web:
log.web = _csd['web']
if 'iota' in _csd and not log.iota:
log.iota = _csd['iota']
if "latitude" in _csd and not log.lat:
log.lat = _csd["latitude"]
if "longitude" in _csd and not log.lon:
log.lon = _csd["longitude"]
if "web" in _csd and not log.web:
log.web = _csd["web"]
if "iota" in _csd and not log.iota:
log.iota = _csd["iota"]

log.consolidated_hamqth = True
updated += 1
@@ -81,10 +81,10 @@ def update_qsos_without_countries():
if not log.call:
continue
dxcc = get_dxcc_from_clublog_or_database(log.call)
log.dxcc = dxcc['DXCC']
log.cqz = dxcc['CQZ']
log.country = dxcc['Name']
log.cont = dxcc['Continent']
log.dxcc = dxcc["DXCC"]
log.cqz = dxcc["CQZ"]
log.country = dxcc["Name"]
log.cont = dxcc["Continent"]
db.session.commit()
updated += 1
print("Updated {0} QSOs".format(updated))
@@ -98,7 +98,7 @@ def populate_logs_gridsquare_cache():
if not qth:
print("!! country_grid_coords() for log {0} returned None, please check !!".format(log.id))
continue
log.cache_gridsquare = qth['qth']
log.cache_gridsquare = qth["qth"]
updated += 1
db.session.commit()
print("-- Updated {0} QSOs".format(updated))
@@ -107,13 +107,13 @@ def populate_logs_gridsquare_cache():
def update_dxcc_from_cty_xml(_file=None, silent=False):
if not silent:
print("--- Updating DXCC tables (prefixes, entities, exceptions) from cty.xml")
fname = os.path.join(current_app.config['TEMP_DOWNLOAD_FOLDER'], 'cty.xml')
fname = os.path.join(current_app.config["TEMP_DOWNLOAD_FOLDER"], "cty.xml")

config = Config.query.first()
if not config:
if not silent:
print("!!! Error: config not found")
add_log(category='CONFIG', level='ERROR', message='Config not found')
add_log(category="CONFIG", level="ERROR", message="Config not found")
return

if os.path.isfile(fname):
@@ -127,12 +127,12 @@ def update_dxcc_from_cty_xml(_file=None, silent=False):
if not config.clublog_api_key:
if not silent:
print("!! Clublog API Key not defined")
add_log(category='CRONS', level='ERROR', message='Clublog API Key not defined')
add_log(category="CRONS", level="ERROR", message="Clublog API Key not defined")
return
url = "https://secure.clublog.org/cty.php?api={0}".format(config.clublog_api_key)

try:
with urllib.request.urlopen(url) as response, open(fname, 'wb') as out_file:
with urllib.request.urlopen(url) as response, open(fname, "wb") as out_file:
with gzip.GzipFile(fileobj=response) as uncompressed:
shutil.copyfileobj(uncompressed, out_file)
except urllib.error.URLError as err:
@@ -169,28 +169,28 @@ def update_dxcc_from_cty_xml(_file=None, silent=False):
root = tree.getroot()

for element in root:
if element.tag == '{http://www.clublog.org/cty/v1.0}entities':
if element.tag == "{http://www.clublog.org/cty/v1.0}entities":
if not silent:
print('++ Parsing {0}'.format(element.tag))
print("++ Parsing {0}".format(element.tag))
rmed = DxccEntities.query.delete()
if not silent:
print('-- Cleaned {0} old entries'.format(rmed))
print("-- Cleaned {0} old entries".format(rmed))
parse_element(element, silent)

elif element.tag == '{http://www.clublog.org/cty/v1.0}exceptions':
elif element.tag == "{http://www.clublog.org/cty/v1.0}exceptions":
if not silent:
print('++ Parsing {0}'.format(element.tag))
print("++ Parsing {0}".format(element.tag))
rmed = DxccExceptions.query.delete()
if not silent:
print('-- Cleaned {0} old entries'.format(rmed))
print("-- Cleaned {0} old entries".format(rmed))
parse_element(element, silent)

elif element.tag == '{http://www.clublog.org/cty/v1.0}prefixes':
elif element.tag == "{http://www.clublog.org/cty/v1.0}prefixes":
if not silent:
print('++ Parsing {0}'.format(element.tag))
print("++ Parsing {0}".format(element.tag))
rmed = DxccPrefixes.query.delete()
if not silent:
print('-- Cleaned {0} old entries'.format(rmed))
print("-- Cleaned {0} old entries".format(rmed))
parse_element(element, silent)


@@ -199,43 +199,43 @@ def parse_element(element, silent=False):
for child in element:
skip = False

if element.tag == '{http://www.clublog.org/cty/v1.0}entities':
if element.tag == "{http://www.clublog.org/cty/v1.0}entities":
_obj = DxccEntities()
_obj.ituz = 999 # We don't have ITUZ in cty.xml so we put 999 in it
elif element.tag == '{http://www.clublog.org/cty/v1.0}exceptions':
elif element.tag == "{http://www.clublog.org/cty/v1.0}exceptions":
_obj = DxccExceptions()
elif element.tag == '{http://www.clublog.org/cty/v1.0}prefixes':
elif element.tag == "{http://www.clublog.org/cty/v1.0}prefixes":
_obj = DxccPrefixes()
else:
return

if 'record' in child.attrib:
_obj.record = child.attrib['record']
if "record" in child.attrib:
_obj.record = child.attrib["record"]

for attr in child:
if attr.tag == '{http://www.clublog.org/cty/v1.0}call':
if attr.tag == "{http://www.clublog.org/cty/v1.0}call":
_obj.call = attr.text
elif attr.tag == '{http://www.clublog.org/cty/v1.0}name':
elif attr.tag == "{http://www.clublog.org/cty/v1.0}name":
_obj.name = attr.text
elif attr.tag == '{http://www.clublog.org/cty/v1.0}prefix':
elif attr.tag == "{http://www.clublog.org/cty/v1.0}prefix":
_obj.prefix = attr.text
elif attr.tag == '{http://www.clublog.org/cty/v1.0}entity':
if attr.text == 'INVALID':
elif attr.tag == "{http://www.clublog.org/cty/v1.0}entity":
if attr.text == "INVALID":
skip = True
_obj.entity = attr.text
elif attr.tag == '{http://www.clublog.org/cty/v1.0}adif':
elif attr.tag == "{http://www.clublog.org/cty/v1.0}adif":
_obj.adif = int(attr.text)
elif attr.tag == '{http://www.clublog.org/cty/v1.0}cqz':
elif attr.tag == "{http://www.clublog.org/cty/v1.0}cqz":
_obj.cqz = float(attr.text)
elif attr.tag == '{http://www.clublog.org/cty/v1.0}cont':
elif attr.tag == "{http://www.clublog.org/cty/v1.0}cont":
_obj.cont = attr.text
elif attr.tag == '{http://www.clublog.org/cty/v1.0}long':
elif attr.tag == "{http://www.clublog.org/cty/v1.0}long":
_obj.long = float(attr.text)
elif attr.tag == '{http://www.clublog.org/cty/v1.0}lat':
elif attr.tag == "{http://www.clublog.org/cty/v1.0}lat":
_obj.lat = float(attr.text)
elif attr.tag == '{http://www.clublog.org/cty/v1.0}start':
elif attr.tag == "{http://www.clublog.org/cty/v1.0}start":
_obj.start = parser.parse(attr.text)
elif attr.tag == '{http://www.clublog.org/cty/v1.0}end':
elif attr.tag == "{http://www.clublog.org/cty/v1.0}end":
_obj.start = parser.parse(attr.text)

if not _obj.adif:
@@ -250,7 +250,7 @@ def parse_element(element, silent=False):
elements += 1
db.session.commit()
if not silent:
print('-- Committed {0} new elements'.format(elements))
print("-- Committed {0} new elements".format(elements))


def cron_sync_from_eqsl(dry_run=False):
@@ -274,16 +274,18 @@ def cron_sync_from_eqsl(dry_run=False):
config = Config.query.first()
if not config:
print("!!! Error: config not found")
add_log(category='CONFIG', level='ERROR', message='Config not found')
add_log(category="CONFIG", level="ERROR", message="Config not found")
return

print("-- Working on logbook [{0}] {1}".format(logbook.id, logbook.name))

_payload = urllib.parse.urlencode({
"UserName": logbook.user.eqsl_name,
"Password": logbook.user.eqsl_password,
"QTHNickname": logbook.eqsl_qth_nickname
})
_payload = urllib.parse.urlencode(
{
"UserName": logbook.user.eqsl_name,
"Password": logbook.user.eqsl_password,
"QTHNickname": logbook.eqsl_qth_nickname,
}
)

_url = "{0}?{1}".format(config.eqsl_download_url, _payload)

@@ -293,21 +295,21 @@ def cron_sync_from_eqsl(dry_run=False):
err_fetch = UserLogging()
err_fetch.user_id = logbook.user.id
err_fetch.logbook_id = logbook.id
err_fetch.category = 'EQSL FETCH'
err_fetch.category = "EQSL FETCH"

try:
with urllib.request.urlopen(_req) as f:
_text = f.read().decode('UTF-8')
_text = f.read().decode("UTF-8")
except urllib.error.URLError as e:
err_fetch.level = 'ERROR'
err_fetch.message = 'Error fetching from eQSL: {0}'.format(e)
err_fetch.level = "ERROR"
err_fetch.message = "Error fetching from eQSL: {0}".format(e)
db.session.add(err_fetch)
db.session.commit()
continue # skip to next

if not _text:
err_fetch.level = 'ERROR'
err_fetch.message = 'Error fetching from EQSL, _text undefined'
err_fetch.level = "ERROR"
err_fetch.message = "Error fetching from EQSL, _text undefined"
db.session.add(err_fetch)
db.session.commit()
continue # skip to next
@@ -326,17 +328,17 @@ def cron_sync_from_eqsl(dry_run=False):
print("-- Fetching ADIF {0}".format(_url))
with urllib.request.urlopen(_req) as f:
# eQSL returns a file encoded in ISO8859-1 so decode it then re-encode it in UTF-8
_text = f.read().decode('ISO8859-1').encode('UTF-8')
_text = f.read().decode("ISO8859-1").encode("UTF-8")
except urllib.error.URLError as e:
err_fetch.level = 'ERROR'
err_fetch.message = 'Error fetching from eQSL: {0}'.format(e)
err_fetch.level = "ERROR"
err_fetch.message = "Error fetching from eQSL: {0}".format(e)
db.session.add(err_fetch)
db.session.commit()
continue # skip to next

if not _text:
err_fetch.level = 'ERROR'
err_fetch.message = 'Error fetching from EQSL, _text for final URL undefined'
err_fetch.level = "ERROR"
err_fetch.message = "Error fetching from EQSL, _text for final URL undefined"
db.session.add(err_fetch)
db.session.commit()
continue # skip to next
@@ -347,36 +349,37 @@ def cron_sync_from_eqsl(dry_run=False):
err_log = UserLogging()
err_log.user_id = logbook.user.id
err_log.logbook_id = logbook.id
err_log.category = 'EQSL LOG'
err_log.category = "EQSL LOG"

_date = "{0} {1}".format(log["qso_date"], log["time_on"])
_date_first = datetime.datetime.strptime(_date + "00", "%Y%m%d %H%M%S")
_date_second = datetime.datetime.strptime(_date + "59", "%Y%m%d %H%M%S")
# Try to find a matching log entry
qso = Log.query.filter(Log.logbook_id == logbook.id,
Log.user_id == logbook.user.id,
Log.call == log['call'].upper(),
Log.time_on.between(_date_first, _date_second)).first()
qso = Log.query.filter(
Log.logbook_id == logbook.id,
Log.user_id == logbook.user.id,
Log.call == log["call"].upper(),
Log.time_on.between(_date_first, _date_second),
).first()
if qso:
if qso.eqsl_qsl_rcvd == 'Y':
if qso.eqsl_qsl_rcvd == "Y":
continue # this eQSL have already been matched
print("-- Matching log found for {0} on {1} : ID {2}".format(log['call'],
_date, qso.id))
print("-- Matching log found for {0} on {1} : ID {2}".format(log["call"], _date, qso.id))
if not dry_run:
qso.eqsl_qsl_rcvd = 'Y'
qso.eqsl_qsl_rcvd = "Y"
err_log.log_id = qso.id
err_log.level = 'INFO'
err_log.message = 'QSO from eQSL by {0} on {1} received and updated'.format(log['call'], _date)
err_log.level = "INFO"
err_log.message = "QSO from eQSL by {0} on {1} received and updated".format(log["call"], _date)
else:
print("-- No matching log found for {0} on {1}".format(log['call'], _date))
err_log.level = 'INFO'
err_log.message = 'QSO from eQSL by {0} on {1} not found in database'.format(log['call'], _date)
print("-- No matching log found for {0} on {1}".format(log["call"], _date))
err_log.level = "INFO"
err_log.message = "QSO from eQSL by {0} on {1} not found in database".format(log["call"], _date)
if not dry_run:
db.session.add(err_log)
db.session.commit()
else:
err_fetch.level = 'ERROR'
err_fetch.message = 'Error fetching from EQSL, link not found in body'
err_fetch.level = "ERROR"
err_fetch.message = "Error fetching from EQSL, link not found in body"
db.session.add(err_fetch)
db.session.commit()

@@ -388,11 +391,11 @@ def cron_sync_eqsl(dry_run=False):
print("--- [DRY RUN] Sending logs to eQSL when requested")
else:
print("--- Sending logs to eQSL when requested")
logs = Log.query.filter(Log.eqsl_qsl_sent == 'R').all()
logs = Log.query.filter(Log.eqsl_qsl_sent == "R").all()
config = Config.query.first()
if not config:
print("!!! Error: config not found")
add_log(category='CONFIG', level='ERROR', message='Config not found')
add_log(category="CONFIG", level="ERROR", message="Config not found")
return

for log in logs:
@@ -403,26 +406,26 @@ def cron_sync_eqsl(dry_run=False):
err.user_id = log.user.id
err.log_id = log.id
err.logbook_id = log.logbook.id
err.category = 'EQSL'
if status['state'] == 'error':
err.level = 'ERROR'
print("!! Error uploading QSO {0} to eQSL: {1}".format(log.id, status['message']))
elif status['state'] == 'rejected':
log.eqsl_qsl_sent = 'I'
print("!! Rejected uploading QSO {0} to eQSL: {1}".format(log.id, status['message']))
err.category = "EQSL"
if status["state"] == "error":
err.level = "ERROR"
print("!! Error uploading QSO {0} to eQSL: {1}".format(log.id, status["message"]))
elif status["state"] == "rejected":
log.eqsl_qsl_sent = "I"
print("!! Rejected uploading QSO {0} to eQSL: {1}".format(log.id, status["message"]))
else:
err.level = 'INFO'
err.level = "INFO"

err.message = status['message'] + '\r\n'
err.message = status["message"] + "\r\n"

if 'msgs' in status:
for i in status['msgs']:
if "msgs" in status:
for i in status["msgs"]:
print("!! {0}: {1}".format(i[0], i[1]))
err.message += '{0}: {1}\r\n'.format(i[0], i[1])
err.message += "{0}: {1}\r\n".format(i[0], i[1])

if status['state'] == 'success':
log.eqsl_qsl_sent = 'Y'
if status["state"] == "success":
log.eqsl_qsl_sent = "Y"

print(status)


+ 137
- 134
dbseed.py View File

@@ -17,163 +17,166 @@ def make_db_seed(db):
def seed_users(db):
print("++ Seeding users")
role_usr = Role()
role_usr.name = 'user'
role_usr.description = 'Simple user'
role_usr.name = "user"
role_usr.description = "Simple user"

role_adm = Role()
role_adm.name = 'admin'
role_adm.description = 'Admin user'
role_adm.name = "admin"
role_adm.description = "Admin user"

db.session.add(role_usr)
db.session.add(role_adm)

user_datastore.create_user(
email='dashie@sigpipe.me',
password='fluttershy',
name='toto',
timezone='UTC',
email="dashie@sigpipe.me",
password="fluttershy",
name="toto",
timezone="UTC",
roles=[role_adm],
callsign='N0C4LL',
locator='JN'
callsign="N0C4LL",
locator="JN",
)
db.session.commit()
return


def seed_config(db):
a = Config(lotw_download_url='https://p1k.arrl.org/lotwuser/lotwreport.adi',
lotw_upload_url='https://p1k.arrl.org/lotwuser/upload',
lotw_rcvd_mark='Y',
lotw_login_url='https://p1k.arrl.org/lotwuser/default',
eqsl_download_url='https://www.eqsl.cc/qslcard/DownloadInBox.cfm',
eqsl_upload_url='https://www.eqsl.cc/qslcard/ImportADIF.cfm',
eqsl_rcvd_mark='Y')
a = Config(
lotw_download_url="https://p1k.arrl.org/lotwuser/lotwreport.adi",
lotw_upload_url="https://p1k.arrl.org/lotwuser/upload",
lotw_rcvd_mark="Y",
lotw_login_url="https://p1k.arrl.org/lotwuser/default",
eqsl_download_url="https://www.eqsl.cc/qslcard/DownloadInBox.cfm",
eqsl_upload_url="https://www.eqsl.cc/qslcard/ImportADIF.cfm",
eqsl_rcvd_mark="Y",
)
db.session.add(a)
db.session.commit()
db.session.commit()
# Bug, two commit necessary


#### Only used by tests
def seed_bands(db):
db.session.add(Band(name='2222m', zone='iaru1', lower=135700, upper=137800))
db.session.add(Band(name='630m', zone='iaru1', lower=472000, upper=476000))
db.session.add(Band(name='160m', zone='iaru1', lower=1810000, upper=1850000))
db.session.add(Band(name='80m', zone='iaru1', lower=3500000, upper=3800000))
db.session.add(Band(name='60m', zone='iaru1', lower=5351500, upper=5366500))
db.session.add(Band(name='40m', zone='iaru1', lower=7000000, upper=7200000))
db.session.add(Band(name='30m', zone='iaru1', lower=10100000, upper=10150000))
db.session.add(Band(name='20m', zone='iaru1', lower=14000000, upper=14350000))
db.session.add(Band(name='17m', zone='iaru1', lower=18068000, upper=18168000))
db.session.add(Band(name='15m', zone='iaru1', lower=21000000, upper=21450000))
db.session.add(Band(name='12m', zone='iaru1', lower=24890000, upper=24990000))
db.session.add(Band(name='10m', zone='iaru1', lower=28000000, upper=29700000))
db.session.add(Band(name='6m', zone='iaru1', lower=50000000, upper=52000000))
db.session.add(Band(name='2m', zone='iaru1', lower=144000000, upper=146000000))
db.session.add(Band(name='70cm', zone='iaru1', lower=430000000, upper=440000000))
db.session.add(Band(name='23cm', zone='iaru1', lower=1240000000, upper=1300000000))
db.session.add(Band(name='13cm', zone='iaru1', lower=2300000000, upper=2450000000))
db.session.add(Band(name='5cm', zone='iaru1', lower=5650000000, upper=5850000000))
db.session.add(Band(name='3cm', zone='iaru1', lower=10000000000, upper=10500000000))
db.session.add(Band(name='1,2cm', zone='iaru1', lower=24000000000, upper=24250000000))
db.session.add(Band(name='6mm', zone='iaru1', lower=47000000000, upper=47200000000))
db.session.add(Band(name='4mm', zone='iaru1', lower=76000000000, upper=81500000000))
db.session.add(Band(name='2,4mm', zone='iaru1', lower=122250000000, upper=123000000000))
db.session.add(Band(name='2mm', zone='iaru1', lower=134000000000, upper=141000000000))
db.session.add(Band(name='1,2mm', zone='iaru1', lower=241000000000, upper=250000000000))
db.session.add(Band(name="2222m", zone="iaru1", lower=135700, upper=137800))
db.session.add(Band(name="630m", zone="iaru1", lower=472000, upper=476000))
db.session.add(Band(name="160m", zone="iaru1", lower=1810000, upper=1850000))
db.session.add(Band(name="80m", zone="iaru1", lower=3500000, upper=3800000))
db.session.add(Band(name="60m", zone="iaru1", lower=5351500, upper=5366500))
db.session.add(Band(name="40m", zone="iaru1", lower=7000000, upper=7200000))
db.session.add(Band(name="30m", zone="iaru1", lower=10100000, upper=10150000))
db.session.add(Band(name="20m", zone="iaru1", lower=14000000, upper=14350000))
db.session.add(Band(name="17m", zone="iaru1", lower=18068000, upper=18168000))
db.session.add(Band(name="15m", zone="iaru1", lower=21000000, upper=21450000))
db.session.add(Band(name="12m", zone="iaru1", lower=24890000, upper=24990000))
db.session.add(Band(name="10m", zone="iaru1", lower=28000000, upper=29700000))
db.session.add(Band(name="6m", zone="iaru1", lower=50000000, upper=52000000))
db.session.add(Band(name="2m", zone="iaru1", lower=144000000, upper=146000000))
db.session.add(Band(name="70cm", zone="iaru1", lower=430000000, upper=440000000))
db.session.add(Band(name="23cm", zone="iaru1", lower=1240000000, upper=1300000000))
db.session.add(Band(name="13cm", zone="iaru1", lower=2300000000, upper=2450000000))
db.session.add(Band(name="5cm", zone="iaru1", lower=5650000000, upper=5850000000))
db.session.add(Band(name="3cm", zone="iaru1", lower=10000000000, upper=10500000000))
db.session.add(Band(name="1,2cm", zone="iaru1", lower=24000000000, upper=24250000000))
db.session.add(Band(name="6mm", zone="iaru1", lower=47000000000, upper=47200000000))
db.session.add(Band(name="4mm", zone="iaru1", lower=76000000000, upper=81500000000))
db.session.add(Band(name="2,4mm", zone="iaru1", lower=122250000000, upper=123000000000))
db.session.add(Band(name="2mm", zone="iaru1", lower=134000000000, upper=141000000000))
db.session.add(Band(name="1,2mm", zone="iaru1", lower=241000000000, upper=250000000000))

# Modes plans for IARU1
# Only CW on 2222m
db.session.add(Band(name='2222m', zone='iaru1', modes=None, start=135700))
db.session.add(Band(name='160m', zone='iaru1', modes="CW", start=1810000))
db.session.add(Band(name='160m', zone='iaru1', modes="RTTY,PSK31,PSK63,SSTV", start=1838000))
db.session.add(Band(name='160m', zone='iaru1', modes="SSB,AM", start=1838000))
db.session.add(Band(name='160m', zone='iaru1', modes=None, start=1810000))
db.session.add(Band(name='80m', zone='iaru1', modes="CW", start=3500000))
db.session.add(Band(name='80m', zone='iaru1', modes="RTTY,PSK31,PSK63,SSTV", start=3580000))
db.session.add(Band(name='80m', zone='iaru1', modes="SSB,AM", start=3600000))
db.session.add(Band(name='80m', zone='iaru1', modes=None, start=3500000))
db.session.add(Band(name='40m', zone='iaru1', modes="CW", start=7000000))
db.session.add(Band(name='40m', zone='iaru1', modes="RTTY,PSK31,PSK63,SSTV", start=7040000))
db.session.add(Band(name='40m', zone='iaru1', modes="SSB,AM", start=7060000))
db.session.add(Band(name='40m', zone='iaru1', modes=None, start=7000000))
db.session.add(Band(name='30m', zone='iaru1', modes="CW", start=10100000))
db.session.add(Band(name='30m', zone='iaru1', modes="RTTY,PSK31,PSK63", start=10140000))
db.session.add(Band(name='30m', zone='iaru1', modes=None, start=10100000))
db.session.add(Band(name='20m', zone='iaru1', modes="CW", start=14000000))
db.session.add(Band(name='20m', zone='iaru1', modes="RTTY,PSK31,PSK63", start=14070000))
db.session.add(Band(name='20m', zone='iaru1', modes="SSB,AM", start=14101000))
db.session.add(Band(name='20m', zone='iaru1', modes=None, start=14000000))
db.session.add(Band(name='17m', zone='iaru1', modes="CW", start=18068000))
db.session.add(Band(name='17m', zone='iaru1', modes="RTTY,PSK31,PSK63", start=18095000))
db.session.add(Band(name='17m', zone='iaru1', modes="SSB,AM", start=18111000))
db.session.add(Band(name='17m', zone='iaru1', modes=None, start=18068000))
db.session.add(Band(name='15m', zone='iaru1', modes="CW", start=21000000))
db.session.add(Band(name='15m', zone='iaru1', modes="RTTY,PSK31,PSK63", start=21070000))
db.session.add(Band(name='15m', zone='iaru1', modes="SSB,AM,SSTV", start=21151000))
db.session.add(Band(name='15m', zone='iaru1', modes=None, start=21000000))
db.session.add(Band(name='12m', zone='iaru1', modes="CW", start=24890000))
db.session.add(Band(name='12m', zone='iaru1', modes="RTTY,PSK31,PSK63", start=24915000))
db.session.add(Band(name='12m', zone='iaru1', modes="SSB,AM,SSTV", start=24931000))
db.session.add(Band(name='12m', zone='iaru1', modes=None, start=24890000))
db.session.add(Band(name='10m', zone='iaru1', modes="CW", start=28000000))
db.session.add(Band(name='10m', zone='iaru1', modes="RTTY,PSK31,PSK63", start=28070000))
db.session.add(Band(name='10m', zone='iaru1', modes="SSB,AM,SSTV", start=28225000))
db.session.add(Band(name='10m', zone='iaru1', modes="FM,PKT", start=29200000))
db.session.add(Band(name='10m', zone='iaru1', modes=None, start=28000000))
db.session.add(Band(name='6m', zone='iaru1', modes="CW", start=50000000))
db.session.add(Band(name='6m', zone='iaru1', modes="SSB", start=50110000))
db.session.add(Band(name='6m', zone='iaru1', modes="SSTV", start=50510000))
db.session.add(Band(name='6m', zone='iaru1', modes="FM", start=50520000))
db.session.add(Band(name='6m', zone='iaru1', modes="RTTY", start=50600000))
db.session.add(Band(name='6m', zone='iaru1', modes=None, start=50000000))
db.session.add(Band(name='2m', zone='iaru1', modes="CW,SSB", start=144025000))
db.session.add(Band(name='2m', zone='iaru1', modes="FM", start=145000000))
db.session.add(Band(name='2m', zone='iaru1', modes=None, start=144000000))
db.session.add(Band(name='70cm', zone='iaru1', modes="FM", start=430250000))
db.session.add(Band(name='70cm', zone='iaru1', modes="CW,SSB", start=432000000))
db.session.add(Band(name='70cm', zone='iaru1', modes="PSK31", start=432088000))
db.session.add(Band(name='70cm', zone='iaru1', modes="SSTV", start=432500000))
db.session.add(Band(name='70cm', zone='iaru1', modes="RTTY", start=432562500))
db.session.add(Band(name='70cm', zone='iaru1', modes=None, start=430000000))
db.session.add(Band(name='23cm', zone='iaru1', modes="FM,SSB,CW", start=1260000000))
db.session.add(Band(name='23cm', zone='iaru1', modes="RTTY", start=1270000000))
db.session.add(Band(name='23cm', zone='iaru1', modes="PSK31", start=1296138000))
db.session.add(Band(name='23cm', zone='iaru1', modes=None, start=1240000000))
db.session.add(Band(name='13cm', zone='iaru1', modes="CW", start=2320025000))
db.session.add(Band(name='13cm', zone='iaru1', modes="PSK31", start=2320138000))
db.session.add(Band(name='13cm', zone='iaru1', modes="LSB", start=2320150000))
db.session.add(Band(name='13cm', zone='iaru1', modes="FM", start=2321000000))
db.session.add(Band(name='13cm', zone='iaru1', modes=None, start=2300000000))
db.session.add(Band(name='5cm', zone='iaru1', modes=None, start=5650000000))
db.session.add(Band(name='3cm', zone='iaru1', modes="CW,SSB", start=10368000000))
db.session.add(Band(name='3cm', zone='iaru1', modes=None, start=10000000000))
db.session.add(Band(name='1,2cm', zone='iaru1', modes=None, start=24000000000))
db.session.add(Band(name='6mm', zone='iaru1', modes=None, start=47000000000))
db.session.add(Band(name='4mm', zone='iaru1', modes=None, start=76000000000))
db.session.add(Band(name='2,4mm', zone='iaru1', modes=None, start=122250000000))
db.session.add(Band(name='2mm', zone='iaru1', modes=None, start=134000000000))
<