Skip to content

Commit

Permalink
Redesigned most views. Added translations. Separated views and other …
Browse files Browse the repository at this point in the history
…functionality. Switched to d3 for graphs.
  • Loading branch information
TaaviE committed Dec 1, 2018
1 parent 845eddd commit 438e019
Show file tree
Hide file tree
Showing 66 changed files with 3,166 additions and 1,848 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ venv
generated_graphs/
.directory
remind_*
*.swp
.bash_history

# IntelliJ and PyCharm
.idea
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ shopping lists and gifting constraints (avoid gifts between family members for e
1. Install requirements (`pip3 install -r requirements.txt`)
2. Set up PostgresSQL with the script in `init_db.sql`
3. Set up uwsgi/gunicorn and nginx as you wish (to provide TLS)
4. You're done. Use `/profile` or `/settings` to configure your account.
4. Update configuration and sitemap to your own URL
5. You're done. Use `/profile` or `/settings` to configure your account.

## Wishlists

Expand Down
15 changes: 15 additions & 0 deletions babelmanagement.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# Extract translations to file
pybabel extract -F translations/babel.cf -k lazy_gettext -o translations/messages.pot .

# Initialize a translation
pybabel init -i translations/messages.pot -d translations -l ee

# Compile translations
pybabel compile -d translations

# Update translations
pybabel update -i translations/messages.pot -d translations

# Update individual translations
pybabel update -i translations/messages.pot -d translations -l en
170 changes: 170 additions & 0 deletions background.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
# Background scheduling task
from main import mail
from models.wishlist_model import NoteState
from utility import *


def remind_to_add(rate_limit=True):
info("Started sending adding reminders")
now = datetime.datetime.now()
try:
with open("remind_to_add", "r+") as timer_file:
lastexec = timer_file.read()
lastexec = datetime.datetime(*map(int, reversed(lastexec.split("/"))))

if now - lastexec < datetime.timedelta(days=30):
info(" Adding reminders were rate-limited")
if rate_limit:
return
else:
timer_file.seek(0)
timer_file.write(get_timestamp_string(now))
except Exception:
info(" Adding reminders rate-limit file was not found")
with open("remind_to_add", "w") as timer_file:
timer_file.write(get_timestamp_string(now))

for user in User.query:
if user.last_activity_at:
if now - datetime.datetime(*map(int, user.last_activity_at.split("/"))) < datetime.timedelta(days=15):
continue

email_to_send = "Tere,\n"
email_to_send += "Tegemist on väikese meeldetuletusega enda nimekirja koostamisele hakata mõtlema\n"
email_to_send += "\n"
email_to_send += "Kirja saavad kõik kasutajad kes ei ole vähemalt 15 päeva sisse loginud\n"
email_to_send += "Jõulurakendus 🎄"

mail.send_message(subject="Meeldetuletus kinkide kohta",
body=email_to_send,
recipients=[user.email])

info(" Finished sending adding reminders")


def remind_to_buy(rate_limit=True):
info(" Started sending purchase reminders")
now = datetime.datetime.now()
try:
with open("remind_to_buy", "r+") as timer_file:
lastexec = timer_file.read()
lastexec = datetime.datetime(*map(int, reversed(lastexec.split("/"))))

if now - lastexec < datetime.timedelta(days=15):
info(" Buying reminders were rate-limited")
if rate_limit:
return
else:
timer_file.seek(0)
timer_file.write(get_timestamp_string(now))
except Exception:
info(" Reminder to buy timer file was not found")
with open("remind_to_buy", "w") as timer_file:
timer_file.write(get_timestamp_string(now))

for user in User.query:
marked_entries = get_person_marked(user.id)
items_to_purchase = []
for entry in marked_entries:
if entry.status == NoteState.PLANNING_TO_PURCHASE.value["id"] or entry.status == NoteState.MODIFIED.value[
"id"]:
items_to_purchase.append((entry.item, get_person_name(entry.user_id)))

if len(items_to_purchase) == 0:
continue

email_to_send = "Tere,\n"
email_to_send += "Olete märkinud, et plaanite osta allpool loetletud kingitused kuid ei ole vastavate kingituste staatust uuendanud vähemalt viisteist päeva eelnevast meeldetuletusest:\n"
email_to_send += "\n"
email_to_send += "Kingitus | Kellele\n"
for item in items_to_purchase:
email_to_send += "\""
email_to_send += item[0]
email_to_send += "\" - "
email_to_send += item[1]
email_to_send += "\n"
email_to_send += "\n"
email_to_send += "Palume mitte unustada, ebameeldivad üllatused ei ole need, mida jõuludeks teistele teha soovime\n"
email_to_send += "Jõulurakendus 🎄"

mail.send_message(subject="Meeldetuletus kinkide kohta",
body=email_to_send,
recipients=[user.email])

info(" Finished sending purchase reminders")


def get_timestamp_string(now):
return str(now.hour) + "/" + str(now.day) + "/" + str(now.month) + "/" + str(now.year)


def remind_about_change(rate_limit=True):
info(" Started sending change reminders")
now = datetime.datetime.now()
try:
with open("remind_about_change", "r+") as timer_file:
lastexec = timer_file.read()
lastexec = datetime.datetime(*map(int, reversed(lastexec.split("/"))))

if now - lastexec < datetime.timedelta(hours=6):
info(" Changing reminders were rate-limited")
if rate_limit:
return
else:
timer_file.seek(0)
timer_file.write(get_timestamp_string(now))
except Exception:
info(" Change reminder timer file was not found")
with open("remind_about_change", "w") as timer_file:
timer_file.write(get_timestamp_string(now))

for user in User.query:
marked_entries = get_person_marked(user.id)
items_to_purchase = []
for entry in marked_entries:
if entry.status == NoteState.MODIFIED.value["id"]:
items_to_purchase.append((entry.item, get_person_name(entry.user_id)))

if len(items_to_purchase) == 0:
continue

email_to_send = "Tere,\n"
email_to_send += "Viimase päeva jooksul on muudetud allpool loetletud soove, on oluline, et otsustaksite kas soovite ikka kinki osta või vabastate selle teistele:\n"
email_to_send += "\n"
email_to_send += "Kingitus | Kellele\n"
for item in items_to_purchase:
email_to_send += "\""
email_to_send += item[0]
email_to_send += "\" - "
email_to_send += item[1]
email_to_send += "\n"
email_to_send += "\n"
email_to_send += "Palume päeva jooksul enda otsus uuesti süsteemi sisestada\n"
email_to_send += "Jõulurakendus 🎄"

mail.send_message(subject="Meeldetuletus kinkide kohta",
body=email_to_send,
recipients=[user.email])

info(" Finished sending change reminders")


scheduler = BackgroundScheduler(daemon=True)
scheduler.add_job(remind_to_add,
trigger=IntervalTrigger(days=30),
name="Addition reminder",
id="add_reminder",
replace_existing=True)
scheduler.add_job(remind_to_buy,
trigger=IntervalTrigger(days=15),
name="Buying reminder",
id="buy_reminder",
replace_existing=True)
scheduler.add_job(remind_about_change,
trigger=IntervalTrigger(minutes=720),
name="Change reminder",
id="cng_reminder",
replace_existing=True)

atexit.register(lambda: scheduler.shutdown())
scheduler.start()
89 changes: 34 additions & 55 deletions example_config.py
Original file line number Diff line number Diff line change
@@ -1,75 +1,54 @@
# coding=utf-8
import logging


class Config(object):
"""DEVELOPMENT =
DEBUG =
TESTING = """

CSRF_ENABLED = True
WTF_CSRF_ENABLED = True

SECRET_KEY = ""
SQLALCHEMY_DATABASE_URI = "postgresql://"
ENV = "debug" # TODO: Replace with "production"
FLASK_DEBUG = True # TODO: Disable in production
DEVELOPMENT = True # TODO: Disable in production
DEBUG = True # TODO: Disable in production
TESTING = True # TODO: Disable in production

CSRF_ENABLED = False # TODO: Enable in production
WTF_CSRF_ENABLED = False # TODO: Enable in production

SENTRY_URL = "" # TODO: Add sentry URL
SENTRY_USER_ATTRS = ["username",
"id",
"email"]

AES_KEY = b"yFrstBQ&iKi1OwugdJ?EnIfPWundVBPO"
SECRET_KEY = "" # TODO: Update secret key info
SQLALCHEMY_DATABASE_URI = "" # TODO: Add database URI
SQLALCHEMY_TRACK_MODIFICATIONS = False
MAIL_SERVER = ""
MAIL_SERVER = "localhost"
MAIL_PORT = 25
MAIL_USE_SSL = False
MAIL_USERNAME = ""
MAIL_PASSWORD = ""
MAIL_DEFAULT_SENDER = ("Name", "[email protected]")
SECURITY_EMAIL_SENDER = ("Name", "[email protected]")
MAIL_SUPPRESS_SEND = False
MAIL_PASSWORD = "" # TODO: Add email server info
MAIL_DEFAULT_SENDER = ("Pretty mailer name", "place@holder") # TODO: Update email sender info
MAIL_SUPPRESS_SEND = True # TODO: Do not suppress send in production

RECAPTCHA_PUBLIC_KEY = ""
RECAPTCHA_PUBLIC_KEY = "" # TODO: Add captcha keys
RECAPTCHA_PRIVATE_KEY = ""

SECURITY_PASSWORD_HASH = ""
SECURITY_PASSWORD_SALT = ""
SECURITY_EMAIL_SENDER = ("Pretty mailer name", "place@holder") # TODO: Update email sender info
SECURITY_PASSWORD_HASH = "pbkdf2_sha512"
SECURITY_PASSWORD_SALT = None # TODO: Add salt!

SECURITY_REGISTERABLE = True
SECURITY_RECOVERABLE = True
SECURITY_CONFIRMABLE = True
SECURITY_CHANGEABLE = True
SECURITY_TRACKABLE = True
SESSION_COOKIE_SECURE = True
REMEMBER_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
REMEMBER_COOKIE_HTTPONLY = True


class ProductionConfig(Config):
DEBUG = False
TESTING = False
DEVELOPMENT = False
CSRF_ENABLED = True
WTF_CSRF_ENABLED = True


class StagingConfig(Config):
DEVELOPMENT = True
DEBUG = True


class DevelopmentConfig(Config):
DEVELOPMENT = True
DEBUG = True


class TestingConfig(Config):
TESTING = True


import os
USER_AFTER_REGISTER_ENDPOINT = "/login"

from flask import Flask
from flask_mail import Mail
from flask_sqlalchemy import SQLAlchemy
SESSION_COOKIE_SECURE = False # TODO: Enable in production
REMEMBER_COOKIE_SECURE = False # TODO: Enable in production
SESSION_COOKIE_HTTPONLY = False # TODO: Enable in production
REMEMBER_COOKIE_HTTPONLY = False # TODO: Enable in production

basedir = os.path.abspath(os.path.dirname(__file__))
app = Flask(__name__)
app.config.from_object("config.DevelopmentConfig")
from werkzeug.contrib.fixers import ProxyFix
LOGLEVEL = logging.DEBUG

ProxyFix(app, num_proxies=1)
db = SQLAlchemy(app)
mail = Mail(app)
BABEL_DEFAULT_LOCALE = "ee" # TODO: You might want to change this to "en" if you want to default to English
20 changes: 20 additions & 0 deletions forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from flask_security.forms import StringField, Required, RegisterForm, ResetPasswordForm, SendConfirmationForm, \
ForgotPasswordForm
from flask_wtf import RecaptchaField


class ExtendedRegisterForm(RegisterForm):
username = StringField("Eesnimi", [Required()]) # TODO: Translate I think
recaptcha = RecaptchaField("Captcha")


class ExtendedResetForm(ResetPasswordForm):
recaptcha = RecaptchaField("Captcha")


class ExtendedConfirmationForm(SendConfirmationForm):
recaptcha = RecaptchaField("Captcha")


class ExtendedForgotPasswordForm(ForgotPasswordForm):
recaptcha = RecaptchaField("Captcha")
Loading

0 comments on commit 438e019

Please sign in to comment.