Exemple #1
0
class EditProjectForm(FlaskForm):
    name = StringField(_("Project name"), validators=[DataRequired()])
    password = StringField(_("Private code"), validators=[DataRequired()])
    contact_email = StringField(_("Email"),
                                validators=[DataRequired(),
                                            Email()])
    project_history = BooleanField(_("Enable project history"))
    ip_recording = BooleanField(_("Use IP tracking for project history"))
    currency_helper = CurrencyConverter()
    default_currency = SelectField(_("Default Currency"),
                                   validators=[DataRequired()])

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.default_currency.choices = [
            (currency_name,
             render_localized_currency(currency_name, detailed=True))
            for currency_name in self.currency_helper.get_currencies()
        ]

    @property
    def logging_preference(self):
        """Get the LoggingMode object corresponding to current form data."""
        if not self.project_history.data:
            return LoggingMode.DISABLED
        else:
            if self.ip_recording.data:
                return LoggingMode.RECORD_IP
            else:
                return LoggingMode.ENABLED

    def save(self):
        """Create a new project with the information given by this form.

        Returns the created instance
        """
        project = Project(
            name=self.name.data,
            id=self.id.data,
            password=generate_password_hash(self.password.data),
            contact_email=self.contact_email.data,
            logging_preference=self.logging_preference,
            default_currency=self.default_currency.data,
        )
        return project

    def update(self, project):
        """Update the project with the information from the form"""
        project.name = self.name.data

        # Only update password if changed to prevent spurious log entries
        if not check_password_hash(project.password, self.password.data):
            project.password = generate_password_hash(self.password.data)

        project.contact_email = self.contact_email.data
        project.logging_preference = self.logging_preference
        project.default_currency = self.default_currency.data

        return project
Exemple #2
0
def create_app(
    configuration=None, instance_path="/etc/ihatemoney", instance_relative_config=True
):
    app = Flask(
        __name__,
        instance_path=instance_path,
        instance_relative_config=instance_relative_config,
    )

    # If a configuration object is passed, use it. Otherwise try to find one.
    load_configuration(app, configuration)
    app.wsgi_app = PrefixedWSGI(app)

    # Get client's real IP
    # Note(0livd): When running in a non-proxy setup, is vulnerable to requests
    # with a forged X-FORWARDED-FOR header
    app.wsgi_app = ProxyFix(app.wsgi_app)

    validate_configuration(app)
    app.register_blueprint(web_interface)
    app.register_blueprint(apiv1)
    app.register_error_handler(404, page_not_found)

    # Configure the a, root="main"pplication
    setup_database(app)

    # Setup Currency Cache
    CurrencyConverter()

    mail = Mail()
    mail.init_app(app)
    app.mail = mail

    # Jinja filters
    app.jinja_env.globals["static_include"] = static_include
    app.jinja_env.globals["locale_from_iso"] = locale_from_iso
    app.jinja_env.filters["minimal_round"] = minimal_round

    # Translations
    babel = Babel(app)

    @babel.localeselector
    def get_locale():
        # get the lang from the session if defined, fallback on the browser "accept
        # languages" header.
        lang = session.get(
            "lang",
            request.accept_languages.best_match(app.config["SUPPORTED_LANGUAGES"]),
        )
        setattr(g, "lang", lang)
        return lang

    return app
Exemple #3
0
def edit_project():
    edit_form = EditProjectForm()
    import_form = UploadForm()
    # Import form
    if import_form.validate_on_submit():
        try:
            import_project(import_form.file.data.stream, g.project)
            flash(_("Project successfully uploaded"))

            return redirect(url_for("main.list_bills"))
        except ValueError:
            flash(_("Invalid JSON"), category="danger")

    # Edit form
    if edit_form.validate_on_submit():
        project = edit_form.update(g.project)
        # Update converted currency
        if project.default_currency != CurrencyConverter.no_currency:
            for bill in project.get_bills():

                if bill.original_currency == CurrencyConverter.no_currency:
                    bill.original_currency = project.default_currency

                bill.converted_amount = CurrencyConverter().exchange_currency(
                    bill.amount, bill.original_currency,
                    project.default_currency)
                db.session.add(bill)

        db.session.add(project)
        db.session.commit()

        return redirect(url_for("main.list_bills"))
    else:
        edit_form.name.data = g.project.name

        if g.project.logging_preference != LoggingMode.DISABLED:
            edit_form.project_history.data = True
            if g.project.logging_preference == LoggingMode.RECORD_IP:
                edit_form.ip_recording.data = True

        edit_form.contact_email.data = g.project.contact_email
        edit_form.default_currency.data = g.project.default_currency

    return render_template(
        "edit_project.html",
        edit_form=edit_form,
        import_form=import_form,
        current_view="edit_project",
    )
Exemple #4
0
class TestCurrencyConverter(unittest.TestCase):
    converter = CurrencyConverter()
    mock_data = {"USD": 1, "EUR": 0.8115}
    converter.get_rates = MagicMock(return_value=mock_data)

    def test_only_one_instance(self):
        one = id(CurrencyConverter())
        two = id(CurrencyConverter())
        self.assertEqual(one, two)

    def test_get_currencies(self):
        self.assertCountEqual(self.converter.get_currencies(), ["USD", "EUR"])

    def test_exchange_currency(self):
        result = self.converter.exchange_currency(100, "USD", "EUR")
        self.assertEqual(result, 81.15)
Exemple #5
0
def create_app(configuration=None,
               instance_path="/etc/ihatemoney",
               instance_relative_config=True):
    app = Flask(
        __name__,
        instance_path=instance_path,
        instance_relative_config=instance_relative_config,
    )

    # If a configuration object is passed, use it. Otherwise try to find one.
    load_configuration(app, configuration)
    app.wsgi_app = PrefixedWSGI(app)

    # Get client's real IP
    # Note(0livd): When running in a non-proxy setup, is vulnerable to requests
    # with a forged X-FORWARDED-FOR header
    app.wsgi_app = ProxyFix(app.wsgi_app)

    validate_configuration(app)
    app.register_blueprint(web_interface)
    app.register_blueprint(apiv1)
    app.register_error_handler(404, page_not_found)

    # Configure the a, root="main"pplication
    setup_database(app)

    # Setup Currency Cache
    CurrencyConverter()

    mail = Mail()
    mail.init_app(app)
    app.mail = mail

    # Jinja filters
    app.jinja_env.globals["static_include"] = static_include
    app.jinja_env.globals["locale_from_iso"] = locale_from_iso
    app.jinja_env.filters["minimal_round"] = minimal_round

    # Translations
    babel = Babel(app)

    # Undocumented currencyformat filter from flask_babel is forwarding to Babel format_currency
    # We overwrite it to remove the currency sign ¤ when there is no currency
    def currencyformat_nc(number, currency, *args, **kwargs):
        """
        Same as flask_babel.Babel.currencyformat, but without the "no currency ¤" sign
        when there is no currency.
        """
        return format_currency(
            number,
            currency if currency != CurrencyConverter.no_currency else "",
            *args, **kwargs).strip()

    app.jinja_env.filters["currencyformat_nc"] = currencyformat_nc

    @babel.localeselector
    def get_locale():
        # get the lang from the session if defined, fallback on the browser "accept
        # languages" header.
        lang = session.get(
            "lang",
            request.accept_languages.best_match(
                app.config["SUPPORTED_LANGUAGES"]),
        )
        setattr(g, "lang", lang)
        return lang

    return app
Exemple #6
0
class BillForm(FlaskForm):
    date = DateField(_("Date"),
                     validators=[DataRequired()],
                     default=datetime.now)
    what = StringField(_("What?"), validators=[DataRequired()])
    payer = SelectField(_("Payer"), validators=[DataRequired()], coerce=int)
    amount = CalculatorStringField(_("Amount paid"),
                                   validators=[DataRequired()])
    currency_helper = CurrencyConverter()
    original_currency = SelectField(_("Currency"), validators=[DataRequired()])
    external_link = URLField(
        _("External link"),
        validators=[Optional()],
        description=_("A link to an external document, related to this bill"),
    )
    payed_for = SelectMultipleField(_("For whom?"),
                                    validators=[DataRequired()],
                                    coerce=int)
    submit = SubmitField(_("Submit"))
    submit2 = SubmitField(_("Submit and add a new one"))

    def save(self, bill, project):
        bill.payer_id = self.payer.data
        bill.amount = self.amount.data
        bill.what = self.what.data
        bill.external_link = self.external_link.data
        bill.date = self.date.data
        bill.owers = [
            Person.query.get(ower, project) for ower in self.payed_for.data
        ]
        bill.original_currency = self.original_currency.data
        bill.converted_amount = self.currency_helper.exchange_currency(
            bill.amount, bill.original_currency, project.default_currency)
        return bill

    def fake_form(self, bill, project):
        bill.payer_id = self.payer
        bill.amount = self.amount
        bill.what = self.what
        bill.external_link = ""
        bill.date = self.date
        bill.owers = [
            Person.query.get(ower, project) for ower in self.payed_for
        ]
        bill.original_currency = CurrencyConverter.no_currency
        bill.converted_amount = self.currency_helper.exchange_currency(
            bill.amount, bill.original_currency, project.default_currency)

        return bill

    def fill(self, bill, project):
        self.payer.data = bill.payer_id
        self.amount.data = bill.amount
        self.what.data = bill.what
        self.external_link.data = bill.external_link
        self.original_currency.data = bill.original_currency
        self.date.data = bill.date
        self.payed_for.data = [int(ower.id) for ower in bill.owers]

        self.original_currency.label = Label("original_currency",
                                             _("Currency"))
        self.original_currency.description = _(
            "Project default: %(currency)s",
            currency=render_localized_currency(project.default_currency,
                                               detailed=False),
        )

    def set_default(self):
        self.payed_for.data = self.payed_for.default

    def validate_amount(self, field):
        if field.data == 0:
            raise ValidationError(_("Bills can't be null"))
Exemple #7
0
 def test_only_one_instance(self):
     one = id(CurrencyConverter())
     two = id(CurrencyConverter())
     self.assertEqual(one, two)