Giter Club home page Giter Club logo

gino-admin's Introduction

Gino-Admin

(Unsupported, I don't work on this library anymore right now - feel free to use & do anything if you want)

Gino-Admin Logo

ko-fi

Docs (state: in process): Gino-Admin docs

Play with Demo (current master 0.2.3) >>>> Gino-Admin demo <<<< (login: admin, pass: 1234)

badge1 badge2 badge3

Admin Panel for PostgreSQL DB with Gino ORM and Sanic

Table view Load Presets

How to install

    pip install gino-admin==0.3.0

How to use

You can find several code examples in examples/ folder.

Supported features

  • Auth by login/pass with cookie check
  • Create(Add new) item by one for the Model
  • Delete all rows/per element
  • Copy existed element (data table row)
  • Edit existed data (table row)
  • Search/sort in tables
  • Deepcopy element (recursive copy all rows/objects that depend on chosen as ForeignKey)
  • Upload/export data from/to CSV
  • SQL-Runner (execute SQL-queries)
  • Presets: Define order and Load to DB bunch of CSV-files
  • Init DB (Full clean up behavior: Drop tables & Recreate)
  • Composite CSV: Load multiple relative tables in one CSV-file
  • History logs on changes (log for admin panel actions - edit, delete, add, init_db, load presets and etc)
  • Support multiple users for Admin panel (add, edit, remove users from 'Admin Users' page)
  • UI Colors customizing

TODO:

  • Add possible to add new Presets from GUI
  • Select multiple rows for delete
  • Copy/deepcopy multiple items
  • Edit multiple items (?)
  • Roles for Admin Panel users (split accessess)
  • Filters in Table's columns
  • Other staff on Gino Project Dashboard

Supported Data Types

  • JSONB, JSON
  • Time, DateTime, Date
  • Boolean, String, Decimal, Numeric, Float and etc.

To see the full list of supported types take a look here: gino_admin/types.py

If you don't see type that you need - open the github issue with request and I will add it https://github.com/xnuinside/gino-admin/issues. Or you can open PR by yourself and I will be glad to review it.

How to run Gino-Admin

Run with Cli

    gino-admin run #module_name_with_models -d postgresql://%(DB_USER):%(DB_PASSWORD)@%(DB_HOST):%(DB_PORT)/%(DB)

    gino-admin run --help # use to get cli help
    Optional params:
        -d --db
            Expected format: postgresql://%(DB_USER):%(DB_PASSWORD)@%(DB_HOST):%(DB_PORT)/%(DB)
            Example: postgresql://gino:gino@%gino:5432/gino (based on DB settings in examples/)
            Notice: DB credentials can be set up as  env variables with 'SANIC_' prefix
        -h --host
        -p --port
        -c --config Example:  -c "presets_folder=examples/base_example/src/csv_to_upload;some_property=1"
                    Notice: all fields that not supported in config will be ignored, like 'some_property' in example
        --no-auth  Run Admin Panel without Auth in UI
        -u --user Admin User login & password
            Expected format: login:password
            Example: admin:1234
            Notice: user also can be defined from env variable with 'SANIC_' prefix - check Auth section example

Example:

    gino-admin run examples/run_from_cli/src/db.py --db postgresql://gino:gino@localhost:5432/gino -u admin:1234

Run Admin Panel as Standalone App (no matter that framework you use in main app)

You can use Gino Admin as stand alone web app. Does not matter what Framework used for your main App and that Gino Ext used to init Gino().

Code example in: examples/fastapi_as_main_app How to run example in: examples/fastapi_as_main_app/how_to_run_example.txt

You need to create admin.py (for example, you can use any name) to run admin panel:

import os

from gino_admin import create_admin_app
# import module with your models
import models 

# gino admin uses Sanic as a framework, so you can define most params as environment variables with 'SANIC_' prefix
# in example used this way to define DB credentials & login-password to admin panel

# but you can use 'db_uri' in config to define creds for Database
# check examples/colored_ui/src/app.py as example 

os.environ["SANIC_DB_HOST"] = os.getenv("DB_HOST", "localhost")
os.environ["SANIC_DB_DATABASE"] = "gino"
os.environ["SANIC_DB_USER"] = "gino"
os.environ["SANIC_DB_PASSWORD"] = "gino"


os.environ["SANIC_ADMIN_USER"] = "admin"
os.environ["SANIC_ADMIN_PASSWORD"] = "1234"

current_path = os.path.dirname(os.path.abspath(__file__))


if __name__ == "__main__":
    # host & port - will be used to up on them admin app
    # config - Gino Admin configuration - check docs to see all possible properties,
    # that allow set path to presets folder or custom_hash_method, optional parameter
    # db_models - list of db.Models classes (tables) that you want to see in Admin Panel
    create_admin_app(
        host="0.0.0.0",
        port=os.getenv("PORT", 5000),
        db=models.db,
        db_models=[models.User, models.City, models.GiftCard, models.Country],
        config={
            "presets_folder": os.path.join(current_path, "csv_to_upload")},
    )

All environment variables you can move to define in docker or .env files as you wish, they not needed to be define in '.py', this is just for example shortness.

Add Admin Panel to existed Sanic application as '/admin' route

Create in your project 'admin.py' file and use add_admin_panel from from gino_admin import add_admin_panel

Code example in: examples/base_example How to run example in: examples/base_example/how_to_run_example.txt

Example:

    from from gino_admin import add_admin_panel


    # your app code

    
    add_admin_panel(
        app, db, [User, Place, City, GiftCard], custom_hash_method=custom_hash_method
    )
        

Where:

  • 'app': your Sanic application
  • 'db' : from gino.ext.sanic import Gino; db = Gino() and
  • [User, Place, City, GiftCard] - list of models that you want to add in Admin Panel to maintain
  • custom_hash_method - optional parameter to define you own hash method to encrypt all '_hash' columns of your Models.

In admin panel _hash fields will be displayed without '_hash' prefix and fields values will be hidden like '******'

Presets

Load multiple CSV to DB in order by one click. Presets described that CSV-s files and in that order need to be loaded in DB.

Read the docs: Presets

Composite CSV to Upload

Composite CSV - one file that contains data for several relative tables.

Read the docs: Composite CSV to Upload

Config Gino Admin

Read the docs: Config

Init DB

Init DB feature used for doing full clean up DB - it drop all tables & create them after Drop for all models in Admin Panel.

Upload from CSV

Files-samples for example project can be found here: examples/base_example/src/csv_to_upload

Authorization

Read in docs: Authorization

Limitations

In current version, for correct work of Deepcopy feature in Admin Panel model MUST contain at least one unique or primary_key Column (field).

Screens:

Check in docs: UI Screens

Changelog

v0.3.0

New Features:

  1. Added support for 'hide_columns' argument in admin panel configuration, example: examples/base_example/src/app.py

Argument removes those columns from admin panel 'edit' section. You can pass column directly for one model, or global hide by name, for example:

hide_columns = [ModelName.id, 'pk']

This mean that for the model ModelName on 'edit' page column 'id' will be hided. And globally for all models will be hided columns with name 'pk'.

Improvements

  1. Dependencies was updated

v0.2.5

  1. Added favicon.ico
  2. Updated Sanic version (by the way, I plan to move admin panel to FastAPI in next major release)
  3. Other dependencies also updated
  4. Fixed issue with stop admin panel server

v0.2.4

  1. Fixed issue with loop from #37

gino-admin's People

Contributors

dependabot[bot] avatar ehborisov avatar xnuinside avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

gino-admin's Issues

Customize add/edit page

How can I customize the edit page? Id field available for editing, no default values for fields.
Lib version: 0.2.5

Simple model:

class User(db.Model):
    __tablename__ = 'users'

    pk = Column(db.String(), primary_key=True, unique=True)

    telegram_id = Column(Integer())
    nickname = Column(String(length=33), default='-')

    def __str__(self):
        return f'{self.nickname} - {self.telegram_id}'

image

Unable to run with uvloop

Hey there! This is shaping up to be super useful for me. Thanks for your hard work!

I tried to add this to my Sanic app and then to start it up with uvloop. It gives me an error because gino_admin.core.add_admin_panel tries to run it's own asyncio loop. I noticed that the function that it calls doesn't seem to have any async components. Would you accept a patch to remove the asyncio call and change gino_admin.users.add_users_model to a synchronous function?

jinja2.exceptions.TemplateAssertionError: no test named 'boolean'

I hit an jina2 parsing issue when using gino-admin.

For information, my db is PostgreSQL.

When going on one of model page or gino_admin_users, I get the following error:

jinja2.exceptions.TemplateAssertionError: no test named 'boolean'

I did find a few similar issues on different libraries but no solutions so far, do you have an idea how to solve this ?

See trace bellow:

Traceback (most recent call last):
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/sanic/app.py", line 939, in handle_request
    response = await response
  File "/usr/local/lib/python3.7/site-packages/gino_admin/auth.py", line 37, in validate
    return await route(request, *args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/gino_admin/routes/crud.py", line 25, in model_view_table
    return await render_model_view(request, model_id)
  File "/usr/local/lib/python3.7/site-packages/gino_admin/routes/logic.py", line 60, in render_model_view
    unique=cfg.models[model_id]["identity"],
  File "/usr/local/lib/python3.7/site-packages/gino_admin/config.py", line 36, in render_with_updated_context
    self.render_string(template, request, **context),
  File "/usr/local/lib/python3.7/site-packages/sanic_jinja2/__init__.py", line 141, in render_string
    return self.env.get_template(template).render(**context)
  File "/usr/local/lib/python3.7/site-packages/jinja2/asyncsupport.py", line 76, in render
    return original_render(self, *args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/jinja2/environment.py", line 1008, in render
    return self.environment.handle_exception(exc_info, True)
  File "/usr/local/lib/python3.7/site-packages/jinja2/environment.py", line 780, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/local/lib/python3.7/site-packages/jinja2/_compat.py", line 37, in reraise
    raise value.with_traceback(tb)
  File "/usr/local/lib/python3.7/site-packages/gino_admin/templates/data_table.html", line 23, in template
    {% if field is boolean %}
  File "/usr/local/lib/python3.7/site-packages/jinja2/environment.py", line 1005, in render
    return concat(self.root_render_func(self.new_context(vars)))
  File "/usr/local/lib/python3.7/site-packages/gino_admin/templates/model_view.html", line 14, in root
    </div>
  File "/usr/local/lib/python3.7/site-packages/gino_admin/templates/base.html", line 14, in root
  File "/usr/local/lib/python3.7/site-packages/gino_admin/templates/app.html", line 22, in root
    <script src="https://cdn.rawgit.com/mdehoog/Semantic-UI/6e6d051d47b598ebab05857545f242caf2b4b48c/dist/semantic.min.js"></script>
  File "/usr/local/lib/python3.7/site-packages/gino_admin/templates/base.html", line 31, in block_content
  File "/usr/local/lib/python3.7/site-packages/gino_admin/templates/model_view.html", line 41, in block_body
    <div class="eight wide column left aligned content" id="grid_footer_left"><br>
  File "/usr/local/lib/python3.7/site-packages/jinja2/environment.py", line 830, in get_template
    return self._load_template(name, self.make_globals(globals))
  File "/usr/local/lib/python3.7/site-packages/jinja2/environment.py", line 804, in _load_template
    template = self.loader.load(self, name, globals)
  File "/usr/local/lib/python3.7/site-packages/jinja2/loaders.py", line 125, in load
    code = environment.compile(source, name, filename)
  File "/usr/local/lib/python3.7/site-packages/jinja2/environment.py", line 591, in compile
    self.handle_exception(exc_info, source_hint=source_hint)
  File "/usr/local/lib/python3.7/site-packages/jinja2/environment.py", line 780, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/usr/local/lib/python3.7/site-packages/jinja2/_compat.py", line 37, in reraise
    raise value.with_traceback(tb)
  File "/usr/local/lib/python3.7/site-packages/gino_admin/templates/data_table.html", line 23, in template
    {% if field is boolean %}
  File "/usr/local/lib/python3.7/site-packages/jinja2/environment.py", line 543, in _generate
    optimized=self.optimized)
  File "/usr/local/lib/python3.7/site-packages/jinja2/compiler.py", line 82, in generate
    generator.visit(node)
  File "/usr/local/lib/python3.7/site-packages/jinja2/visitor.py", line 38, in visit
    return f(node, *args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/jinja2/compiler.py", line 754, in visit_Template
    self.blockvisit(node.body, frame)
  File "/usr/local/lib/python3.7/site-packages/jinja2/compiler.py", line 378, in blockvisit
    self.visit(node, frame)
  File "/usr/local/lib/python3.7/site-packages/jinja2/visitor.py", line 38, in visit
    return f(node, *args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/jinja2/compiler.py", line 1122, in visit_For
    self.blockvisit(node.body, loop_frame)
  File "/usr/local/lib/python3.7/site-packages/jinja2/compiler.py", line 378, in blockvisit
    self.visit(node, frame)
  File "/usr/local/lib/python3.7/site-packages/jinja2/visitor.py", line 38, in visit
    return f(node, *args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/jinja2/compiler.py", line 1122, in visit_For
    self.blockvisit(node.body, loop_frame)
  File "/usr/local/lib/python3.7/site-packages/jinja2/compiler.py", line 378, in blockvisit
    self.visit(node, frame)
  File "/usr/local/lib/python3.7/site-packages/jinja2/visitor.py", line 38, in visit
    return f(node, *args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/jinja2/compiler.py", line 1160, in visit_If
    self.blockvisit(node.body, if_frame)
  File "/usr/local/lib/python3.7/site-packages/jinja2/compiler.py", line 378, in blockvisit
    self.visit(node, frame)
  File "/usr/local/lib/python3.7/site-packages/jinja2/visitor.py", line 38, in visit
    return f(node, *args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/jinja2/compiler.py", line 1157, in visit_If
    self.visit(node.test, if_frame)
  File "/usr/local/lib/python3.7/site-packages/jinja2/visitor.py", line 38, in visit
    return f(node, *args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/jinja2/compiler.py", line 70, in new_func
    return f(self, node, frame, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/jinja2/compiler.py", line 1607, in visit_Test
    self.fail('no test named %r' % node.name, node.lineno)
  File "/usr/local/lib/python3.7/site-packages/jinja2/compiler.py", line 315, in fail
    raise TemplateAssertionError(msg, lineno, self.name, self.filename)

SQL-runner improvements

  • Add controls to table with results (pagination & search)
  • Store last queries in session (for the quick re-run)
  • Add columns to result output

Add Deepcopy feature.

Logic:

Copy chosen element and all data that depend on it in chain.

For example,
exist table user
exist table restaurants
exist table dishes
exist table drinks

user.id ForeignKey for Place as owner_id

user (one) to (many) Places

Place (one) to (many) dishes & drinks

So when we do 'Deepcopy' of 'user'

We copy all his Places, with all dishes and drinks that chained to Place

TypeError: 'tuple' object is not callable - while trying to delete the category

Got the following error while trying to delete data:
delete_action_gino_admin

500_internal_server_error

[2021-05-24 20:16:18 +0400] - (sanic.access)[INFO][127.0.0.1:45260]: GET http://0.0.0.0:5000/admin/category/  200 15340
[2021-05-24 20:16:24 +0400] [29850] [DEBUG] KeepAlive Timeout. Closing connection.
[2021-05-24 20:16:42 +0400] [29850] [ERROR] Exception occurred while handling uri: 'http://0.0.0.0:5000/admin/category/delete/'
Traceback (most recent call last):
  File "/home/shako/REPOS/Learning_FastAPI/Djackets/.venv/lib/python3.9/site-packages/sanic/app.py", line 724, in handle_request
    response = await response
  File "/home/shako/REPOS/Learning_FastAPI/Djackets/.venv/lib/python3.9/site-packages/gino_admin/auth.py", line 37, in validate
    return await route(request, *args, **kwargs)
  File "/home/shako/REPOS/Learning_FastAPI/Djackets/.venv/lib/python3.9/site-packages/gino_admin/routes/crud.py", line 146, in model_delete
    return await model_view_table(request, model_id, flash_message)
TypeError: 'tuple' object is not callable
[2021-05-24 20:16:42 +0400] - (sanic.access)[INFO][127.0.0.1:45296]: POST http://0.0.0.0:5000/admin/category/delete/  500 1837
[2021-05-24 20:16:42 +0400] - (sanic.access)[INFO][127.0.0.1:45296]: GET http://0.0.0.0:5000/favicon.ico  404 673

ValueError: invalid literal for int() with base 10: '6, 2'

If in the models you define Numeric type with precision and scale:

class Product(db.Model):
    __tablename__ = 'product'

    id = db.Column(db.BigInteger(), primary_key=True)
    category = db.Column(db.BigInteger(), db.ForeignKey('category.id'))
    name = db.Column(db.String(), unique=True, nullable=False)
    slug = db.Column(db.String(), unique=True, nullable=False)
    description = db.Column(db.Unicode(), nullable=True)
    price = db.Column(db.Numeric(6, 2), nullable=False)
    image = db.Column(db.String(), nullable=True)
    thumbnail = db.Column(db.String(), nullable=True)
    date_added = db.Column(db.DateTime(), nullable=False)

We will get:

Traceback (most recent call last):
  File "/home/shako/REPOS/Learning_FastAPI/Djackets/backend/app/admin.py", line 17, in <module>
    create_admin_app(
  File "/home/shako/REPOS/Learning_FastAPI/Djackets/.venv/lib/python3.9/site-packages/gino_admin/core.py", line 185, in create_admin_app
    return init_admin_app(host, port, db, db_models, config)
  File "/home/shako/REPOS/Learning_FastAPI/Djackets/.venv/lib/python3.9/site-packages/gino_admin/core.py", line 196, in init_admin_app
    add_admin_panel(app, db, db_models, **config)
  File "/home/shako/REPOS/Learning_FastAPI/Djackets/.venv/lib/python3.9/site-packages/gino_admin/core.py", line 129, in add_admin_panel
    extract_models_metadata(db, db_models)
  File "/home/shako/REPOS/Learning_FastAPI/Djackets/.venv/lib/python3.9/site-packages/gino_admin/core.py", line 97, in extract_models_metadata
    column_details = extract_column_data(model_id)
  File "/home/shako/REPOS/Learning_FastAPI/Djackets/.venv/lib/python3.9/site-packages/gino_admin/core.py", line 45, in extract_column_data
    len_ = int(str(column.type).split("(")[1].split(")")[0])
ValueError: invalid literal for int() with base 10: '6, 2'

AttributeError: 'NoneType' object has no attribute 'isoformat'

I got this error when I tried to open the model in the admin panel
File "..../venv/lib/python3.8/site-packages/gino_admin/utils.py", line 304, in get_obj_id_from_row
result[x] = row[x].isoformat()
AttributeError: 'NoneType' object has no attribute 'isoformat'

model

class Notification(db.Model):
......
created_at = db.Column(
sa.DateTime,
nullable=False,
server_default=sa.func.now(),
)

read_at = db.Column(sa.DateTime, nullable=True)

The error occurs for the read_at field, since it can be None

result[x] = row[x].isoformat()

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.