feature/pricelist #15

Merged
ferfissimo merged 11 commits from feature/pricelist into develop 2021-11-15 09:55:11 +00:00
7 changed files with 95 additions and 114 deletions
Showing only changes of commit ff1a0544f8 - Show all commits

View File

@ -9,7 +9,7 @@ from flaschengeist import _module_path, logger
# Default config: # Default config:
config = {"DATABASE": {"port": 3306}} config = {"DATABASE": {"engine": "mysql", "port": 3306}}
def update_dict(d, u): def update_dict(d, u):
@ -65,17 +65,35 @@ def configure_app(app, test_config=None):
else: else:
app.config["SECRET_KEY"] = config["FLASCHENGEIST"]["secret_key"] app.config["SECRET_KEY"] = config["FLASCHENGEIST"]["secret_key"]
if test_config is None: if test_config is not None:
app.config["SQLALCHEMY_DATABASE_URI"] = "mysql{driver}://{user}:{passwd}@{host}:{port}/{database}".format( config["DATABASE"]["engine"] = "sqlite"
driver="+pymysql" if os.name == "nt" else "",
if config["DATABASE"]["engine"] == "mysql":
engine = "mysql"
try:
# Try mysqlclient first
from MySQLdb import _mysql
except ModuleNotFoundError:
engine += "+pymysql"
options = "?charset=utf8mb4"
elif config["DATABASE"]["engine"] == "postgres":
engine = "postgresql+psycopg2"
options = "?client_encoding=utf8"
elif config["DATABASE"]["engine"] == "sqlite":
engine = "sqlite"
options = ""
host = ""
else:
logger.error(f"Invalid database engine configured. >{config['DATABASE']['engine']}< is unknown")
raise Exception
if config["DATABASE"]["engine"] in ["mysql", "postgresql"]:
host = "{user}:{password}@{host}:{port}".format(
user=config["DATABASE"]["user"], user=config["DATABASE"]["user"],
passwd=config["DATABASE"]["password"], password=config["DATABASE"]["password"],
host=config["DATABASE"]["host"], host=config["DATABASE"]["host"],
database=config["DATABASE"]["database"],
port=config["DATABASE"]["port"], port=config["DATABASE"]["port"],
) )
else: app.config["SQLALCHEMY_DATABASE_URI"] = f"{engine}://{host}/{config['DATABASE']['database']}{options}"
app.config["SQLALCHEMY_DATABASE_URI"] = f"sqlite+pysqlite://{config['DATABASE']['file_path']}"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
if "root" in config["FLASCHENGEIST"]: if "root" in config["FLASCHENGEIST"]:

View File

@ -20,6 +20,7 @@ secret_key = "V3ryS3cr3t"
level = "WARNING" level = "WARNING"
[DATABASE] [DATABASE]
# engine = "mysql" (default)
# user = "user" # user = "user"
# host = "127.0.0.1" # host = "127.0.0.1"
# password = "password" # password = "password"

View File

@ -2,7 +2,7 @@ import sys
import datetime import datetime
from sqlalchemy import BigInteger from sqlalchemy import BigInteger
from sqlalchemy.dialects import mysql from sqlalchemy.dialects import mysql, sqlite
from sqlalchemy.types import DateTime, TypeDecorator from sqlalchemy.types import DateTime, TypeDecorator
@ -44,7 +44,7 @@ class ModelSerializeMixin:
class Serial(TypeDecorator): class Serial(TypeDecorator):
"""Same as MariaDB Serial used for IDs""" """Same as MariaDB Serial used for IDs"""
impl = BigInteger().with_variant(mysql.BIGINT(unsigned=True), "mysql") impl = BigInteger().with_variant(mysql.BIGINT(unsigned=True), "mysql").with_variant(sqlite.INTEGER, "sqlite")
class UtcDateTime(TypeDecorator): class UtcDateTime(TypeDecorator):

View File

@ -567,6 +567,7 @@ def get_columns(userid, current_session):
userController.persist() userController.persist()
return no_content() return no_content()
@PriceListPlugin.blueprint.route("/users/<userid>/pricecalc_columns_order", methods=["GET", "PUT"]) @PriceListPlugin.blueprint.route("/users/<userid>/pricecalc_columns_order", methods=["GET", "PUT"])
@login_required() @login_required()
def get_columns_order(userid, current_session): def get_columns_order(userid, current_session):

View File

@ -231,3 +231,21 @@ def notifications(current_session):
def remove_notifications(nid, current_session): def remove_notifications(nid, current_session):
userController.delete_notification(nid, current_session.user_) userController.delete_notification(nid, current_session.user_)
return no_content() return no_content()
@UsersPlugin.blueprint.route("/users/<userid>/shortcuts", methods=["GET", "PUT"])
@login_required()
def shortcuts(userid, current_session):
if userid != current_session.user_.userid:
raise Forbidden
user = userController.get_user(userid)
if request.method == "GET":
return jsonify(user.get_attribute("users_link_shortcuts", []))
else:
data = request.get_json()
if not isinstance(data, list) or not all(isinstance(n, dict) for n in data):
raise BadRequest
user.set_attribute("users_link_shortcuts", data)
userController.persist()
return no_content()

114
readme.md
View File

@ -1,9 +1,16 @@
# Flaschengeist # Flaschengeist
This is the backend of the Flaschengeist.
## Installation ## Installation
### Requirements ### Requirements
- mysql or mariadb - `mysql` or `mariadb`
- python 3.6+ - maybe `libmariadb` development files[1]
- python 3.7+
[1] By default Flaschengeist uses mysql as database backend, if you are on Windows Flaschengeist uses `PyMySQL`, but on
Linux / Mac the faster `mysqlclient` is used, if it is not already installed installing from pypi requires the
development files for `libmariadb` to be present on your system.
### Install python files ### Install python files
pip3 install --user . pip3 install --user .
or with ldap support or with ldap support
@ -30,9 +37,21 @@ Configuration is done within the a `flaschengeist.toml`file, you can copy the on
1. `~/.config/` 1. `~/.config/`
2. A custom path and set environment variable `FLASCHENGEIST_CONF` 2. A custom path and set environment variable `FLASCHENGEIST_CONF`
Change at least the database parameters! Uncomment and change at least all the database parameters!
### Database installation ### Database installation
The user needs to have full permissions to the database.
If not you need to create user and database manually do (or similar on Windows):
(
echo "CREATE DATABASE flaschengeist;"
echo "CREATE USER 'flaschengeist'@'localhost' IDENTIFIED BY 'flaschengeist';"
echo "GRANT ALL PRIVILEGES ON 'flaschengeist'.* TO 'flaschengeist'@'localhost';"
echo "FLUSH PRIVILEGES;"
) | sudo mysql
Then you can install the database tables and initial entries:
run_flaschengeist install run_flaschengeist install
### Run ### Run
@ -41,6 +60,8 @@ or with debug messages:
run_flaschengeist run --debug run_flaschengeist run --debug
This will run the backend on http://localhost:5000
## Tests ## Tests
$ pip install '.[test]' $ pip install '.[test]'
$ pytest $ pytest
@ -53,89 +74,4 @@ Or with html output (open `htmlcov/index.html` in a browser):
$ coverage html $ coverage html
## Development ## Development
### Code Style Please refer to our [development wiki](https://flaschengeist.dev/Flaschengeist/flaschengeist/wiki/Development).
We enforce you to use PEP 8 code style with a line length of 120 as used by Black.
See also [Black Code Style](https://github.com/psf/black/blob/master/docs/the_black_code_style.md).
#### Code formatting
We use [Black](https://github.com/psf/black) as the code formatter.
Installation:
pip install black
Usage:
black -l 120 DIRECTORY_OR_FILE
### Misc
#### Git blame
When using `git blame` use this to ignore the code formatting commits:
$ git blame FILE.py --ignore-revs-file .git-blame-ignore-revs
Or if you just want to use `git blame`, configure git like this:
$ git config blame.ignoreRevsFile .git-blame-ignore-revs
#### Ignore changes on config
git update-index --assume-unchanged flaschengeist/flaschengeist.toml
## Plugin Development
### File Structure
flaschengeist-example-plugin
|> __init__.py
|> model.py
|> setup.py
### Files
#### \_\_init\_\_.py
from flask import Blueprint
from flaschengeist.modules import Plugin
example_bp = Blueprint("example", __name__, url_prefix="/example")
permissions = ["example_hello"]
class PluginExample(Plugin):
def __init__(self, conf):
super().__init__(blueprint=example_bp, permissions=permissions)
def install(self):
from flaschengeist.system.database import db
import .model
db.create_all()
db.session.commit()
@example_bp.route("/hello", methods=['GET'])
@login_required(roles=['example_hello'])
def __hello(id, **kwargs):
return "Hello"
#### model.py
Optional, only needed if you need your own models (database)
from flaschengeist.system.database import db
class ExampleModel(db.Model):
"""Example Model"""
__tablename__ = 'example'
id = db.Column(db.Integer, primary_key=True)
description = db.Column(db.String(240))
#### setup.py
from setuptools import setup, find_packages
setup(
name="flaschengeist-example-plugin",
version="0.0.0-dev",
packages=find_packages(),
install_requires=[
"flaschengeist >= 2",
],
entry_points={
"flaschengeist.plugin": [
"example = flaschengeist-example-plugin:ExampleModel"
]
},
)

View File

@ -17,7 +17,7 @@ class PrefixMiddleware(object):
def __call__(self, environ, start_response): def __call__(self, environ, start_response):
if environ["PATH_INFO"].startswith(self.prefix): if environ["PATH_INFO"].startswith(self.prefix):
environ["PATH_INFO"] = environ["PATH_INFO"][len(self.prefix):] environ["PATH_INFO"] = environ["PATH_INFO"][len(self.prefix) :]
environ["SCRIPT_NAME"] = self.prefix environ["SCRIPT_NAME"] = self.prefix
return self.app(environ, start_response) return self.app(environ, start_response)
else: else:
@ -156,9 +156,11 @@ def export(arguments):
app = create_app() app = create_app()
with app.app_context(): with app.app_context():
gen = InterfaceGenerator(arguments.namespace, arguments.file) gen = InterfaceGenerator(arguments.namespace, arguments.file)
if not arguments.no_core:
gen.run(models) gen.run(models)
if arguments.plugins: if arguments.plugins is not None:
for entry_point in pkg_resources.iter_entry_points("flaschengeist.plugin"): for entry_point in pkg_resources.iter_entry_points("flaschengeist.plugin"):
if len(arguments.plugins) == 0 or entry_point.name in arguments.plugins:
plg = entry_point.load() plg = entry_point.load()
if hasattr(plg, "models") and plg.models is not None: if hasattr(plg, "models") and plg.models is not None:
gen.run(plg.models) gen.run(plg.models)
@ -183,7 +185,12 @@ if __name__ == "__main__":
parser_export.set_defaults(func=export) parser_export.set_defaults(func=export)
parser_export.add_argument("--file", help="Filename where to save", default="flaschengeist.d.ts") parser_export.add_argument("--file", help="Filename where to save", default="flaschengeist.d.ts")
parser_export.add_argument("--namespace", help="Namespace of declarations", default="FG") parser_export.add_argument("--namespace", help="Namespace of declarations", default="FG")
parser_export.add_argument("--plugins", help="Also export plugins", action="store_true") parser_export.add_argument(
"--no-core",
help="Do not export core declarations (only useful in conjunction with --plugins)",
action="store_true",
)
parser_export.add_argument("--plugins", help="Also export plugins (none means all)", nargs="*")
args = parser.parse_args() args = parser.parse_args()
args.func(args) args.func(args)