Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add consumptions model, view, actions and overview-page #83

Merged
merged 28 commits into from
Mar 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
d6d81cb
ADD invoice workspace
Feb 17, 2023
605aa2b
REMOVE duplicate workspace files
Feb 17, 2023
a5de558
FIX format
Feb 17, 2023
adeb225
ADD page entry in backend and vue file for the frontend
Feb 17, 2023
441ee1e
REMOVE thepermission because its not needed
Feb 17, 2023
b8be852
Merge remote-tracking branch 'refs/remotes/origin/md_add_invoice_work…
Feb 17, 2023
a9751c5
ADD consumption-log model
Feb 17, 2023
ab03ab2
ADD specific name for table
Feb 19, 2023
ce92684
ADD alembic environment and base migration
Feb 19, 2023
2418f7f
IMPROVE env.py to get workspace name automatically
Feb 19, 2023
cca42b6
FIX format
Feb 19, 2023
9b71829
FIX format
Feb 19, 2023
6f0b213
IMPROVE naming for guest to consume
Mar 2, 2023
08d0609
MERGE parent
Mar 2, 2023
1c2886a
FIX migration script
Mar 2, 2023
2a6f527
ADD an action to add a consumption log
Mar 2, 2023
b42c518
IMPROVE action
Mar 2, 2023
de39d8b
ADD custom response
Mar 2, 2023
a5eb954
ADD consume log view
Mar 3, 2023
954421f
Merge remote-tracking branch 'refs/remotes/origin/starter_tutorial'
Mar 6, 2023
f53c419
Merge branch 'md_add_invoice_workspace'
Mar 6, 2023
cf444c7
Merge branch 'md_add_consumption_view'
Mar 6, 2023
3b171f3
Merge remote-tracking branch 'refs/remotes/origin/md_add_consumption_…
Mar 6, 2023
efe6911
Merge remote-tracking branch 'refs/remotes/origin/md_add_model_migrat…
Mar 6, 2023
47a89b5
Merge remote-tracking branch 'refs/remotes/origin/md_add_consume_log_…
Mar 6, 2023
a63f999
ADD data request at mount and parsing after load
Mar 6, 2023
30ef854
ADD actions to display
Mar 6, 2023
2869ee2
ADD data-table
Mar 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
53 changes: 53 additions & 0 deletions backend/workspaces/Invoices/actions/createConsumptionLog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""
The roseguarden project

Copyright (C) 2018-2020 Marcus Drobisch,

This program is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program. If not, see <http://www.gnu.org/licenses/>.
"""

__authors__ = ["Marcus Drobisch"]
__contact__ = "[email protected]"
__credits__ = []
__license__ = "GPLv3"

from core.actions.action import Action
from core.logs import logManager
from core.actions import webclientActions
from core.users import userManager
from core.messages import send_mail, send_message
from core.actions import generateActionLink

from workspaces.Invoices.models import ConsumptionLog


class CreateConsumptionLog(Action):
def __init__(self, app):
# logManager.info("Register of type Action created")
super().__init__(app, uri="createConsumptionLog")

def handle(self, action, user, workspace, actionManager):
replyActions = []

consumption_log = ConsumptionLog()
consumption_log.consumed_as_guest = action.get("consumedAsGuest", True)
consumption_log.guest_email = action.get("guestEmail", "")
consumption_log.guest_is_member = action.get("guestIsMember", False)
consumption_log.service_area = action.get("serviceAreas", "")
consumption_log.service_name = action.get("serviceName", "")
consumption_log.service_quantity = action.get("serviceQuantity", 0.0)
consumption_log.service_unit = action.get("serviceUnit", "")

workspace.db.session.add(consumption_log)

replyActions.append(webclientActions.NotificationAction.generate("Consumption log created", "success"))
return "success", replyActions, {"custom": "customResponse"}
Empty file.
25 changes: 25 additions & 0 deletions backend/workspaces/Invoices/models/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# General

Place your models in this folder.

The core loads all models imported in `__init__.py`.

# Migrations

You might use the alembic command lines to setup migration here.

Run the command in the specific `models` folder

## Create a new revision in `versions`:

`alembic revision -m "name" --autogenerate --rev-id REV_ID`

Please use the actual naming convention. E.g. `-m "name" --autogenerate --rev-id 000001`.

## Upgrade to a new revision:

`alembic upgrade REV_ID`

## Stamp to an actual version (may be needed before upgrade or create a new revision):

`alembic stamp --purge REV_ID`
1 change: 1 addition & 0 deletions backend/workspaces/Invoices/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .consumption_log import * # noqa: F401, F403
83 changes: 83 additions & 0 deletions backend/workspaces/Invoices/models/alembic.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# A generic, single database configuration.

[alembic]
# path to migration scripts
script_location = migrations

# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s

# timezone to use when rendering the date
# within the migration file as well as the filename.
# string value is passed to dateutil.tz.gettz()
# leave blank for localtime
# timezone =

# max length of characters to apply to the
# "slug" field
# truncate_slug_length = 40

# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false

# set to 'true' to allow .pyc and .pyo files without
# a source .py file to be detected as revisions in the
# versions/ directory
# sourceless = false

# version location specification; this defaults
# to migrations/versions. When using multiple version
# directories, initial revisions must be specified with --version-path
# version_locations = %(here)s/bar %(here)s/bat migrations/versions

# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8


[post_write_hooks]
# post_write_hooks defines scripts or Python functions that are run
# on newly generated revision scripts. See the documentation for further
# detail and examples

# format using "black" - use the console_scripts runner, against the "black" entrypoint
# hooks=black
# black.type=console_scripts
# black.entrypoint=black
# black.options=-l 79

# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic

[handlers]
keys = console

[formatters]
keys = generic

[logger_root]
level = WARN
handlers = console
qualname =

[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine

[logger_alembic]
level = INFO
handlers =
qualname = alembic

[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic

[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
49 changes: 49 additions & 0 deletions backend/workspaces/Invoices/models/consumption_log.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""
The roseguarden project

Copyright (C) 2018-2020 Marcus Drobisch,

This program is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with
this program. If not, see <http://www.gnu.org/licenses/>.
"""

__authors__ = ["Marcus Drobisch"]
__contact__ = "[email protected]"
__credits__ = []
__license__ = "GPLv3"

from sqlalchemy_utils import ArrowType
import arrow

from core import db
from core.users.models import User


class ConsumptionLog(db.Model):
__tablename__ = "invoice_consumption_log"
id = db.Column(db.Integer, primary_key=True, index=True, autoincrement=True, unique=True)
consumed_as_guest = db.Column(db.Boolean, default=None)
linked_user_id = db.Column(db.Integer, db.ForeignKey(User.id, ondelete="SET NULL"))
linked_user = db.relationship("User", backref="consumptions", foreign_keys=linked_user_id)
guest_is_member = db.Column(db.Boolean, default=None)
guest_email = db.Column(db.String(128), default=None)

consumed_at = db.Column(ArrowType, default=arrow.utcnow)
created_at = db.Column(ArrowType, default=arrow.utcnow)

service_area = db.Column(db.String(128), default=None)
service_name = db.Column(db.String(128), default=None)
service_quantity = db.Column(db.Float, default=None)
service_unit = db.Column(db.String(128), default=None)

project_name = db.Column(db.String(128), default=None)
project_purpose = db.Column(db.String(128), default=None)
project_details = db.Column(db.String(128), default=None)
Empty file.
156 changes: 156 additions & 0 deletions backend/workspaces/Invoices/models/migrations/env.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
from logging.config import fileConfig
import logging
from sqlalchemy import engine_from_config
from sqlalchemy import pool
import sqlalchemy_utils # noqa: F401

from alembic import context

# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config

# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
logger = logging.getLogger("alembic.env")

al_logger = logging.getLogger("alembic.runtime.migration")
al_logger.setLevel(logging.ERROR)

# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
import os
import sys
from pathlib import Path


file = Path(__file__).resolve()
parent, top, root = file.parent, file.parents[4], file.parents[5]

sys.path.append(str(top))
sys.path.append(str(file.parents[2]))

logger.info(f"cwd {os.getcwd()} {top} {root} {file.parents[2]}")

# fixme: set this per workspace
alembic_datatable_name = "migration_version_" + (str(file.parents[2].name)).lower()
print(alembic_datatable_name)
from core import db
import models
from config import configure_app, load_config

target_metadata = db.Model.metadata

# fixme: this is probably fine for our use case, but in configure_app()
# we also allow setting from os.environ.get('DATABASE_URL')
app_config = load_config("config.ini")
database_path = app_config["SYSTEM"].get("database_path", None)
basedir = file.parents[4]

config.set_main_option(
"sqlalchemy.url", database_path or os.environ.get("DATABASE_URL") or "sqlite:///" + os.path.join(basedir, "app.db")
)

# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.


def run_migrations_offline():
"""Run migrations in 'offline' mode.

This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.

Calls to context.execute() here emit the given string to the
script output.

"""
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
)

with context.begin_transaction():
context.run_migrations()


def include_object(obj, name, type_, reflected, compare_to):
"""
Should you include this table or not?
"""
if reflected is False:
if type(obj) == type(compare_to):
logger.info(f"No changes on {name}")
else:
logger.info(f"Changes found for {name}")
return True
return False


def render_item(type_, obj, autogen_context):
# custom render for sqalchemy_utils ChoiceType column and params
# May be better to use: https://stackoverflow.com/questions/30132370/trouble-when-using-alembic-with-sqlalchemy-utils
print(type(obj))
if type_ == "type" and type(obj).__name__ == "IntEnum":
col_type = "sa.Integer(), default=0"
return col_type
if type_ == "type" and type(obj).__name__ == "IntFlag":
col_type = "sa.Integer(), default=0"
return col_type

# default rendering for other objects
return False


def run_migrations_online():
"""Run migrations in 'online' mode.

In this scenario we need to create an Engine
and associate a connection with the context.

"""

# this callback is used to prevent an auto-migration from being generated
# when there are no changes to the schema
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
def process_revision_directives(context, revision, directives):
if getattr(config.cmd_opts, "autogenerate", False):
script = directives[0]
if script.upgrade_ops.is_empty():
directives[:] = []
logger.info("No changes in schema detected.")

connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)

with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata,
render_item=render_item,
include_object=include_object,
version_table=alembic_datatable_name,
process_revision_directives=process_revision_directives,
)

with context.begin_transaction():
context.run_migrations()


if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()
25 changes: 25 additions & 0 deletions backend/workspaces/Invoices/models/migrations/script.py.mako
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""${message}

Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}

"""
from alembic import op
import sqlalchemy as sa
import sqlalchemy_utils
${imports if imports else ""}

# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}


def upgrade():
${upgrades if upgrades else "pass"}


def downgrade():
${downgrades if downgrades else "pass"}
Loading