diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 382e8897..7947b1a4 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -14,6 +14,7 @@ Fixed - Fix the generation of the supervisord template (#309) - Fix the validation of the hashed password (#310) - Fix infinite loop that happened when accessing / (#358) +- Fix email validation when sending invites Added ===== diff --git a/Makefile b/Makefile index e273b80b..8c48ed4c 100644 --- a/Makefile +++ b/Makefile @@ -7,52 +7,71 @@ DOC_STAMP = $(VENV)/.doc_env_installed.stamp INSTALL_STAMP = $(VENV)/.install.stamp TEMPDIR := $(shell mktemp -d) -all: install -install: virtualenv $(INSTALL_STAMP) +.PHONY: all +all: install ## Alias for install +.PHONY: install +install: virtualenv $(INSTALL_STAMP) ## Install dependencies $(INSTALL_STAMP): $(VENV)/bin/pip install -U pip $(VENV)/bin/pip install -r requirements.txt touch $(INSTALL_STAMP) +.PHONY: virtualenv virtualenv: $(PYTHON) $(PYTHON): $(VIRTUALENV) $(VENV) -install-dev: $(INSTALL_STAMP) $(DEV_STAMP) +.PHONY: install-dev +install-dev: $(INSTALL_STAMP) $(DEV_STAMP) ## Install development dependencies $(DEV_STAMP): $(PYTHON) dev-requirements.txt $(VENV)/bin/pip install -Ur dev-requirements.txt touch $(DEV_STAMP) +.PHONY: remove-install-stamp remove-install-stamp: rm $(INSTALL_STAMP) -update: remove-install-stamp install +.PHONY: update +update: remove-install-stamp install ## Update the dependencies -serve: install +.PHONY: serve +serve: install ## Run the ihatemoney server + @echo 'Running ihatemoney on http://localhost:5000' $(PYTHON) -m ihatemoney.manage runserver -test: $(DEV_STAMP) +.PHONY: test +test: $(DEV_STAMP) ## Run the tests $(VENV)/bin/tox -release: $(DEV_STAMP) +.PHONY: release +release: $(DEV_STAMP) ## Release a new version (see https://ihatemoney.readthedocs.io/en/latest/contributing.html#how-to-release) $(VENV)/bin/fullrelease -build-translations: +.PHONY: build-translations +build-translations: ## Build the translations $(VENV)/bin/pybabel compile -d ihatemoney/translations -update-translations: +.PHONY: update-translations +update-translations: ## Extract new translations from source code $(VENV)/bin/pybabel extract --strip-comments --omit-header --no-location --mapping-file ihatemoney/babel.cfg -o ihatemoney/messages.pot ihatemoney $(VENV)/bin/pybabel update -i ihatemoney/messages.pot -d ihatemoney/translations/ -create-database-revision: +.PHONY: create-database-revision +create-database-revision: ## Create a new database revision @read -p "Please enter a message describing this revision: " rev_message; \ $(PYTHON) -m ihatemoney.manage db migrate -d ihatemoney/migrations -m "$${rev_message}" -build-requirements: +.PHONY: build-requirements +build-requirements: ## Save currently installed packages to requirements.txt $(VIRTUALENV) $(TEMPDIR) $(TEMPDIR)/bin/pip install -U pip $(TEMPDIR)/bin/pip install -Ue "." $(TEMPDIR)/bin/pip freeze | grep -v -- '-e' > requirements.txt -clean: +.PHONY: clean +clean: ## Destroy the virtual environment rm -rf .venv + +.PHONY: help +help: ## Show the help indications + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' diff --git a/README.rst b/README.rst index 6ecdf9c8..31104d9d 100644 --- a/README.rst +++ b/README.rst @@ -21,3 +21,10 @@ Requirements * **Python**: 2.7, 3.4, 3.5, 3.6. * **Backends**: MySQL, PostgreSQL, SQLite, Memory. + +Contributing +============ + +Do you wish to contribute to IHateMoney? Fantastic! There's a lot of very +useful help on the official `contributing +`_ page. diff --git a/conf/confandrun.sh b/conf/confandrun.sh index e37a5739..0e105b2c 100755 --- a/conf/confandrun.sh +++ b/conf/confandrun.sh @@ -13,7 +13,7 @@ MAIL_USERNAME = "$MAIL_USERNAME" MAIL_PASSWORD = "$MAIL_PASSWORD" MAIL_DEFAULT_SENDER = "$MAIL_DEFAULT_SENDER" ACTIVATE_DEMO_PROJECT = $ACTIVATE_DEMO_PROJECT -ADMIN_PASSWORD = "$ADMIN_PASSWORD" +ADMIN_PASSWORD = '$ADMIN_PASSWORD' ALLOW_PUBLIC_PROJECT_CREATION = $ALLOW_PUBLIC_PROJECT_CREATION ACTIVATE_ADMIN_DASHBOARD = $ACTIVATE_ADMIN_DASHBOARD EOF diff --git a/docs/installation.rst b/docs/installation.rst index 4829c5de..f4744ae7 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -189,52 +189,98 @@ deployed instance, simply look at your *ihatemoney.cfg*. Production values are recommended values for use in production. +`SQLALCHEMY_DATABASE_URI` +------------------------- -+-------------------------------+---------------------------------+----------------------------------------------------------------------------------------+ -| Setting name | Default | What does it do? | -+===============================+=================================+========================================================================================+ -| SQLALCHEMY_DATABASE_URI | ``sqlite:///tmp/ihatemoney.db`` | Specifies the type of backend to use and its location. More information on the | -| | | format used can be found on `the SQLAlchemy documentation`_. | -| | | | -| | | **Production value:** Set it to some path on your disk. Typically | -| | | ``sqlite:///home/ihatemoney/ihatemoney.db``. Do *not* store it under ``/tmp`` as this | -| | | folder is cleared at each boot. | -+-------------------------------+---------------------------------+----------------------------------------------------------------------------------------+ -| SECRET_KEY | ``tralala`` | The secret key used to encrypt the cookies. | -| | | | -| | | **Production value:** `ihatemoney conf-example ihatemoney.cfg` sets it to something | -| | | random, which is good. | -+-------------------------------+---------------------------------+----------------------------------------------------------------------------------------+ -| MAIL_DEFAULT_SENDER | ``("Budget manager", | A python tuple describing the name and email address to use when sending | -| | "budget@notmyidea.org")`` | emails. | -| | | | -| | | **Production value:** Any tuple you want. | -+-------------------------------+---------------------------------+----------------------------------------------------------------------------------------+ -| ACTIVATE_DEMO_PROJECT | ``True`` | If set to `True`, a demo project will be available on the frontpage. | -| | | | -| | | **Production value:** Usually, you will want to set it to ``False`` for a private | -| | | instance. | -+-------------------------------+---------------------------------+----------------------------------------------------------------------------------------+ -| | | Hashed password to access protected endpoints. If left empty, all administrative | -| ADMIN_PASSWORD | ``""`` | tasks are disabled. | -| | | | -| | | **Production value:** To generate the proper password HASH, use | -| | | ``ihatemoney generate_password_hash`` and copy the output into the value of | -| | | *ADMIN_PASSWORD*. | -+-------------------------------+---------------------------------+----------------------------------------------------------------------------------------+ -| ALLOW_PUBLIC_PROJECT_CREATION | ``True`` | If set to ``True``, everyone can create a project without entering the admin password | -| | | If set to ``False``, the password needs to be entered (and as such, defined in the | -| | | settings). | -+-------------------------------+---------------------------------+----------------------------------------------------------------------------------------+ -| ACTIVATE_ADMIN_DASHBOARD | ``False`` | If set to `True`, the dashboard will become accessible entering the admin password | -| | | If set to `True`, a non empty ADMIN_PASSWORD needs to be set | -+-------------------------------+---------------------------------+----------------------------------------------------------------------------------------+ -| APPLICATION_ROOT | ``""`` | If empty, ihatemoney will be served at domain root (e.g: *http://domain.tld*), if set | -| | | to ``"foo"``, it will be served from a "folder" (e.g: *http://domain.tld/foo*) | -+-------------------------------+---------------------------------+----------------------------------------------------------------------------------------+ +Specifies the type of backend to use and its location. More information on the +format used can be found on `the SQLAlchemy documentation`_. + +- **default value:** ``sqlite:///tmp/ihatemoney.db`` +- **Production value:** Set it to some path on your disk. Typically + ``sqlite:///home/ihatemoney/ihatemoney.db``. Do *not* store it under + ``/tmp`` as this folder is cleared at each boot. + +`SECRET_KEY` +------------ + +The secret key used to encrypt the cookies. + +- **Production value:** `ihatemoney conf-example ihatemoney.cfg` sets it to + something random, which is good. + +`MAIL_DEFAULT_SENDER` +--------------------- + +A python tuple describing the name and email address to use when sending emails. + +- **Default value:** ``("Budget manager", "budget@notmyidea.org")`` +- **Production value:** Any tuple you want. + +`ACTIVATE_DEMO_PROJECT` +----------------------- + +If set to `True`, a demo project will be available on the frontpage. + +- **Default value:** ``True`` +- **Production value:** Usually, you will want to set it to ``False`` for a + private instance. + +`ADMIN_PASSWORD` +---------------- + +Hashed password to access protected endpoints. If left empty, all administrative +tasks are disabled. + +- **Default value:** ``""`` (empty string) +- **Production value:** To generate the proper password HASH, use + ``ihatemoney generate_password_hash`` and copy the output into the value of + *ADMIN_PASSWORD*. + +`ALLOW_PUBLIC_PROJECT_CREATION` +------------------------------- + +If set to ``True``, everyone can create a project without entering the admin +password. If set to ``False``, the password needs to be entered (and as such, +defined in the settings). + +- **Default value:** : ``True``. + + +`ACTIVATE_ADMIN_DASHBOARD` +-------------------------- + +If set to `True`, the dashboard will become accessible entering the admin +password, if set to `True`, a non empty ADMIN_PASSWORD needs to be set. + +- **Default value**: ``False`` + +`APPLICATION_ROOT` +------------------ + +If empty, ihatemoney will be served at domain root (e.g: *http://domain.tld*), +if set to ``"somestring"``, it will be served from a "folder" +(e.g: *http://domain.tld/somestring*). + +- **Default value:** ``""`` (empty string) .. _the SQLAlchemy documentation: http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls +Configuring emails sending +-------------------------- + +By default, Ihatemoney sends emails using a local SMTP server, but it's +possible to configure it to act differently, thanks to the great +`Flask-Mail project `_ + +* **MAIL_SERVER** : default **'localhost'** +* **MAIL_PORT** : default **25** +* **MAIL_USE_TLS** : default **False** +* **MAIL_USE_SSL** : default **False** +* **MAIL_DEBUG** : default **app.debug** +* **MAIL_USERNAME** : default **None** +* **MAIL_PASSWORD** : default **None** +* **DEFAULT_MAIL_SENDER** : default **None** + Using an alternate settings path -------------------------------- diff --git a/ihatemoney/forms.py b/ihatemoney/forms.py index 3966891c..de9004d5 100644 --- a/ihatemoney/forms.py +++ b/ihatemoney/forms.py @@ -10,6 +10,8 @@ from werkzeug.security import generate_password_hash from datetime import datetime from jinja2 import Markup +import email_validator + from ihatemoney.models import Project, Person from ihatemoney.utils import slugify @@ -184,9 +186,10 @@ class InviteForm(FlaskForm): submit = SubmitField(_("Send invites")) def validate_emails(form, field): - validator = Email() for email in [email.strip() for email in form.emails.data.split(",")]: - if not validator.regex.match(email): + try: + email_validator.validate_email(email) + except email_validator.EmailNotValidError as e: raise ValidationError(_("The email %(email)s is not valid", email=email)) diff --git a/ihatemoney/manage.py b/ihatemoney/manage.py index 3207b558..a9eca0f3 100755 --- a/ihatemoney/manage.py +++ b/ihatemoney/manage.py @@ -10,7 +10,7 @@ from flask_migrate import Migrate, MigrateCommand from werkzeug.security import generate_password_hash from ihatemoney.run import create_app -from ihatemoney.models import db +from ihatemoney.models import db, Project from ihatemoney.utils import create_jinja_env @@ -57,6 +57,13 @@ class GenerateConfig(Command): )) +class DeleteProject(Command): + def run(self, project_name): + demo_project = Project.query.get(project_name) + db.session.delete(demo_project) + db.session.commit() + + def main(): QUIET_COMMANDS = ('generate_password_hash', 'generate-config') @@ -76,6 +83,7 @@ def main(): manager.add_command('db', MigrateCommand) manager.add_command('generate_password_hash', GeneratePasswordHash) manager.add_command('generate-config', GenerateConfig) + manager.add_command('delete-project', DeleteProject) manager.run() diff --git a/ihatemoney/messages.pot b/ihatemoney/messages.pot index bc8a8f39..bd4dcc49 100644 --- a/ihatemoney/messages.pot +++ b/ihatemoney/messages.pot @@ -182,6 +182,12 @@ msgstr "" msgid "The bill has been modified" msgstr "" +msgid "Sorry, we were unable to find the page you've asked for." +msgstr "" + +msgid "The best thing to do is probably to get back to the main page." +msgstr "" + msgid "Back to the list" msgstr "" diff --git a/ihatemoney/run.py b/ihatemoney/run.py index 6dac2330..e9b3ce16 100644 --- a/ihatemoney/run.py +++ b/ihatemoney/run.py @@ -2,11 +2,10 @@ import os import os.path import warnings -from flask import Flask, g, request, session +from flask import Flask, g, request, session, render_template from flask_babel import Babel from flask_mail import Mail from flask_migrate import Migrate, upgrade, stamp -from raven.contrib.flask import Sentry from werkzeug.contrib.fixers import ProxyFix from ihatemoney.api import api @@ -103,6 +102,10 @@ def validate_configuration(app): ) +def page_not_found(e): + return render_template('404.html', root="main"), 404 + + def create_app(configuration=None, instance_path='/etc/ihatemoney', instance_relative_config=True): app = Flask( @@ -122,17 +125,15 @@ def create_app(configuration=None, instance_path='/etc/ihatemoney', validate_configuration(app) app.register_blueprint(web_interface) app.register_blueprint(api) + app.register_error_handler(404, page_not_found) - # Configure the application + # Configure the a, root="main"pplication setup_database(app) mail = Mail() mail.init_app(app) app.mail = mail - # Error reporting - Sentry(app) - # Jinja filters app.jinja_env.filters['minimal_round'] = minimal_round diff --git a/ihatemoney/static/css/main.css b/ihatemoney/static/css/main.css index 94ca4bd8..6c8b57f9 100644 --- a/ihatemoney/static/css/main.css +++ b/ihatemoney/static/css/main.css @@ -224,6 +224,10 @@ tr.payer_line .balance-name{ padding: 0 0 0 20px; } +.action button:hover { + text-decoration: underline; +} + .delete button, .delete button:hover { background: url('../images/deleter.png') left no-repeat; color: red; diff --git a/ihatemoney/static/favicon.ico b/ihatemoney/static/favicon.ico new file mode 100644 index 00000000..9db04f25 Binary files /dev/null and b/ihatemoney/static/favicon.ico differ diff --git a/ihatemoney/static/js/ihatemoney.js b/ihatemoney/static/js/ihatemoney.js index 24e82b78..c240dc1a 100644 --- a/ihatemoney/static/js/ihatemoney.js +++ b/ihatemoney/static/js/ihatemoney.js @@ -1,18 +1,7 @@ - // Add scripts to select all or non of the checkboxes in the add_bill form -function selectall() - { - var els = document.getElementsByName('payed_for'); - for(var i =0;i +
+

{{ _("Sorry, we were unable to find the page you've asked for.") }}

+
+ +
+ +{% endblock %} diff --git a/ihatemoney/templates/display_errors.html b/ihatemoney/templates/display_errors.html index 9e196058..265fa251 100644 --- a/ihatemoney/templates/display_errors.html +++ b/ihatemoney/templates/display_errors.html @@ -1,5 +1,5 @@ {% for field_name, field_errors in form.errors.items() if field_errors %} {% for error in field_errors %} -

{{ form[field_name].label.text }}: {{ error }}

+

{{ form[field_name].label.text }}: {{ error|escape }}

{% endfor %} {% endfor %} diff --git a/ihatemoney/templates/forms.html b/ihatemoney/templates/forms.html index 7b8c1c9a..d67a7ea2 100644 --- a/ihatemoney/templates/forms.html +++ b/ihatemoney/templates/forms.html @@ -100,7 +100,7 @@
diff --git a/ihatemoney/tests/tests.py b/ihatemoney/tests/tests.py index fd72a8da..63a73942 100644 --- a/ihatemoney/tests/tests.py +++ b/ihatemoney/tests/tests.py @@ -20,7 +20,8 @@ from flask import session from flask_testing import TestCase from ihatemoney.run import create_app, db, load_configuration -from ihatemoney.manage import GenerateConfig, GeneratePasswordHash +from ihatemoney.manage import ( + GenerateConfig, GeneratePasswordHash, DeleteProject) from ihatemoney import models from ihatemoney import utils @@ -1472,6 +1473,15 @@ class CommandTestCase(BaseTestCase): print(stdout.getvalue()) self.assertEqual(len(stdout.getvalue().strip()), 187) + def test_demo_project_deletion(self): + self.create_project('demo') + self.assertEquals(models.Project.query.get('demo').name, 'demo') + + cmd = DeleteProject() + cmd.run('demo') + + self.assertEqual(len(models.Project.query.all()), 0) + if __name__ == "__main__": unittest.main() diff --git a/ihatemoney/translations/fr/LC_MESSAGES/messages.mo b/ihatemoney/translations/fr/LC_MESSAGES/messages.mo index 3fa8d8f4..ab8a8316 100644 Binary files a/ihatemoney/translations/fr/LC_MESSAGES/messages.mo and b/ihatemoney/translations/fr/LC_MESSAGES/messages.mo differ diff --git a/ihatemoney/translations/fr/LC_MESSAGES/messages.po b/ihatemoney/translations/fr/LC_MESSAGES/messages.po index ccab28b1..b3e2fdbe 100644 --- a/ihatemoney/translations/fr/LC_MESSAGES/messages.po +++ b/ihatemoney/translations/fr/LC_MESSAGES/messages.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2018-07-16 23:26+0200\n" +"POT-Creation-Date: 2018-08-05 23:41+0200\n" "PO-Revision-Date: 2018-05-15 22:00+0200\n" "Last-Translator: Adrien CLERC <>\n" "Language: fr\n" @@ -16,7 +16,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Generated-By: Babel 2.5.3\n" +"Generated-By: Babel 2.6.0\n" msgid "Project name" msgstr "Nom de projet" @@ -210,6 +210,12 @@ msgstr "La facture a été supprimée" msgid "The bill has been modified" msgstr "La facture a été modifiée" +msgid "Sorry, we were unable to find the page you've asked for." +msgstr "Navré, nous ne trouvons pas la page que vous avez demandé." + +msgid "The best thing to do is probably to get back to the main page." +msgstr "Votre meilleure piste est probablement la page d'accueil." + msgid "Back to the list" msgstr "Retourner à la liste" @@ -488,4 +494,3 @@ msgstr "Solde" #~ msgid "Invite" #~ msgstr "Invitez" - diff --git a/ihatemoney/web.py b/ihatemoney/web.py index 6e876c03..f6f04af5 100644 --- a/ihatemoney/web.py +++ b/ihatemoney/web.py @@ -9,9 +9,10 @@ some shortcuts to make your life better when coding (see `pull_project` and `add_project_id` for a quick overview) """ +import os from flask import ( Blueprint, current_app, flash, g, redirect, render_template, request, - session, url_for, send_file + session, url_for, send_file, send_from_directory ) from flask_mail import Message from flask_babel import get_locale, gettext as _ @@ -513,7 +514,7 @@ def delete_bill(bill_id): # fixme: everyone is able to delete a bill bill = Bill.query.get(g.project, bill_id) if not bill: - raise NotFound() + return redirect(url_for('.list_bills')) db.session.delete(bill) db.session.commit() @@ -582,3 +583,9 @@ def dashboard(): projects=Project.query.all(), is_admin_dashboard_activated=is_admin_dashboard_activated ) + + +@main.route('/favicon.ico') +def favicon(): + return send_from_directory(os.path.join(main.root_path, 'static'), + 'favicon.ico', mimetype='image/vnd.microsoft.icon') diff --git a/requirements.txt b/requirements.txt index c2fe5348..f61c9b93 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ Flask-script flask-babel flask-restful>=0.3.6 jinja2>=2.6 -raven blinker six>=1.10 itsdangerous>=0.24 +email_validator>=1.0