Skip to content

Commit

Permalink
Docs and example for composite keys
Browse files Browse the repository at this point in the history
  • Loading branch information
dpgaspar committed Dec 10, 2017
1 parent 5e50dde commit 9d2a24e
Show file tree
Hide file tree
Showing 12 changed files with 329 additions and 2 deletions.
13 changes: 11 additions & 2 deletions docs/relations.rst
@@ -1,5 +1,5 @@
Model Relations
===============
Model Relations/Composite keys
==============================

On this chapter we are going to show how to setup model relationships and their
view integration on the framework
Expand Down Expand Up @@ -218,3 +218,12 @@ on the employee detail view::

Take a look and run the example on `Employees example <https://github.com/dpgaspar/Flask-AppBuilder/tree/master/examples/employees>`_
It includes extra functionality like readonly fields, pre and post update logic, etc...

Composite Keys
--------------

Composite keys is supported for SQLAlchemy only, you can reference them using SQLAlchemy 'relationship',
and use them on combo boxes and/or related views, take a look at the
`example <https://github.com/dpgaspar/Flask-AppBuilder/tree/master/examples/composite_keys>`_

.. note:: This feature is only supported since 1.9.6
16 changes: 16 additions & 0 deletions examples/composite_keys/README.rst
@@ -0,0 +1,16 @@
Composite keys Example
----------------------

Simple application showing the use of SQLAlchemy composite keys.

Create an Admin user::

$ fabmanager create-admin

Insert test data::

$ python testdata.py

Run it::

$ fabmanager run
28 changes: 28 additions & 0 deletions examples/composite_keys/app/__init__.py
@@ -0,0 +1,28 @@
import logging

from flask import Flask

from flask_appbuilder import SQLA, AppBuilder

#from sqlalchemy.engine import Engine
#from sqlalchemy import event

logging.basicConfig(format='%(asctime)s:%(levelname)s:%(name)s:%(message)s')
logging.getLogger().setLevel(logging.DEBUG)

app = Flask(__name__)
app.config.from_object('config')
db = SQLA(app)
appbuilder = AppBuilder(app, db.session)

"""
Only include this for SQLLite constraints
@event.listens_for(Engine, "connect")
def set_sqlite_pragma(dbapi_connection, connection_record):
cursor = dbapi_connection.cursor()
cursor.execute("PRAGMA foreign_keys=ON")
cursor.close()
"""

from app import models, views
41 changes: 41 additions & 0 deletions examples/composite_keys/app/models.py
@@ -0,0 +1,41 @@
import datetime
from sqlalchemy import Column, Integer, String, ForeignKey, Date, Enum, Float, ForeignKeyConstraint
from sqlalchemy.orm import relationship
from flask_appbuilder import Model

mindate = datetime.date(datetime.MINYEAR, 1, 1)


class Item(Model):
serial_number = Column(String, primary_key=True)
model = Column(String(150), nullable=False)

def __repr__(self):
return "%s (%s)" % (self.model, self.serial_number)


class Datacenter(Model):
id = Column(Integer, primary_key=True)
name = Column(String(150), unique = True, nullable=False)
address = Column(String(564))

def __repr__(self):
return self.name


class Rack(Model):
num = Column(Integer, primary_key=True)
datacenter_id = Column(Integer, ForeignKey('datacenter.id'), primary_key=True, nullable=False)
datacenter = relationship("Datacenter")

def __repr__(self):
return "%d-%s" % (self.num, self.datacenter)


class Inventory(Model):
item_serial_number = Column(Integer, ForeignKey('item.serial_number'), primary_key=True, nullable=False)
item = relationship("Item")
rack_num = Column(Integer, ForeignKey('rack.num'), primary_key=True, nullable=False)
rack_datacenter_id = Column(Integer, ForeignKey('rack.datacenter_id'), primary_key=True, nullable=False)
rack = relationship('Rack',
primaryjoin="and_(Inventory.rack_num==Rack.num, Inventory.rack_datacenter_id==Rack.datacenter_id)")
Binary file not shown.
@@ -0,0 +1,27 @@
# Portuguese translations for PROJECT.
# Copyright (C) 2014 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2014.
#
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2014-01-13 00:29+0000\n"
"PO-Revision-Date: 2014-01-13 00:18+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: pt <LL@li.org>\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 1.3\n"

#: app/views.py:42
msgid "List Groups"
msgstr "Lista de Grupos"

#: app/views.py:43
msgid "List Contacts"
msgstr ""

51 changes: 51 additions & 0 deletions examples/composite_keys/app/views.py
@@ -0,0 +1,51 @@
import calendar
from flask_appbuilder import ModelView
from flask_appbuilder.models.sqla.interface import SQLAInterface
from flask_appbuilder.charts.views import GroupByChartView
from flask_appbuilder.models.group import aggregate_count
from flask_appbuilder.widgets import FormHorizontalWidget, FormInlineWidget, FormVerticalWidget
from flask_babel import lazy_gettext as _


from app import db, appbuilder
from .models import Datacenter, Rack, Item, Inventory


class RackModelView(ModelView):
datamodel = SQLAInterface(Rack)
list_columns = ["num", "datacenter.name"]
add_columns = ["num", "datacenter"]
edit_columns = ["num", "datacenter"]
show_columns = ["num", "datacenter"]


class DatacenterModelView(ModelView):
datamodel = SQLAInterface(Datacenter)
list_columns = ["name", "address"]
add_columns = ["name", "address"]
edit_columns = ["name", "address"]
show_columns = ["name", "address"]
related_views = [RackModelView]


class ItemModelView(ModelView):
datamodel = SQLAInterface(Item)
list_columns = ["serial_number", "model"]
add_columns = ["serial_number", "model"]
edit_columns = ["serial_number", "model"]
show_columns = ["serial_number", "model"]


class InventoryModelView(ModelView):
datamodel = SQLAInterface(Inventory)
list_columns = ["item", "rack"]
add_columns = ["item", "rack"]
edit_columns = ["item", "rack"]
show_columns = ["item", "rack"]


db.create_all()
appbuilder.add_view(DatacenterModelView, "List Datacenters", icon="fa-folder-open-o", category="Datacenters", category_icon='fa-envelope')
appbuilder.add_view(RackModelView, "List Racks", icon="fa-envelope", category="Datacenters")
appbuilder.add_view(ItemModelView, "List Items", icon="fa-folder-open-o", category="Datacenters", category_icon='fa-envelope')
appbuilder.add_view(InventoryModelView, "List Inventory", icon="fa-envelope", category="Datacenters")
3 changes: 3 additions & 0 deletions examples/composite_keys/babel/babel.cfg
@@ -0,0 +1,3 @@
[python: **.py]
[jinja2: **/templates/**.html]
encoding = utf-8
27 changes: 27 additions & 0 deletions examples/composite_keys/babel/messages.pot
@@ -0,0 +1,27 @@
# Translations template for PROJECT.
# Copyright (C) 2014 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2014.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2014-01-13 00:29+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: Babel 1.3\n"

#: app/views.py:42
msgid "List Groups"
msgstr ""

#: app/views.py:43
msgid "List Contacts"
msgstr ""

65 changes: 65 additions & 0 deletions examples/composite_keys/config.py
@@ -0,0 +1,65 @@
import os

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

CSRF_ENABLED = True
SECRET_KEY = '\2\1thisismyscretkey\1\2\e\y\y\h'

OPENID_PROVIDERS = [
{'name': 'Google', 'url': 'https://www.google.com/accounts/o8/id'},
{'name': 'Yahoo', 'url': 'https://me.yahoo.com'},
{'name': 'AOL', 'url': 'http://openid.aol.com/<username>'},
{'name': 'Flickr', 'url': 'http://www.flickr.com/<username>'},
{'name': 'MyOpenID', 'url': 'https://www.myopenid.com'}]

SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'app.db')
#SQLALCHEMY_DATABASE_URI = 'mysql://username:password@mysqlserver.local/quickhowto'
#SQLALCHEMY_DATABASE_URI = 'postgresql://scott:tiger@localhost:5432/myapp'
#SQLALCHEMY_ECHO = True
SQLALCHEMY_POOL_RECYCLE = 3

BABEL_DEFAULT_LOCALE = 'en'
BABEL_DEFAULT_FOLDER = 'translations'
LANGUAGES = {
'en': {'flag': 'gb', 'name': 'English'},
'pt': {'flag': 'pt', 'name': 'Portuguese'},
'pt_BR': {'flag':'br', 'name': 'Pt Brazil'},
'es': {'flag': 'es', 'name': 'Spanish'},
'de': {'flag': 'de', 'name': 'German'},
'zh': {'flag': 'cn', 'name': 'Chinese'},
'ru': {'flag': 'ru', 'name': 'Russian'},
'pl': {'flag': 'pl', 'name': 'Polish'},
'ja_JP': {'flag': 'jp', 'name': 'Japanese'}
}


#------------------------------
# GLOBALS FOR GENERAL APP's
#------------------------------
UPLOAD_FOLDER = basedir + '/app/static/uploads/'
IMG_UPLOAD_FOLDER = basedir + '/app/static/uploads/'
IMG_UPLOAD_URL = '/static/uploads/'
AUTH_TYPE = 1
#AUTH_LDAP_SERVER = "ldap://dc.domain.net"
AUTH_ROLE_ADMIN = 'Admin'
AUTH_ROLE_PUBLIC = 'Public'
APP_NAME = "F.A.B. Example"
APP_THEME = "" # default
#APP_THEME = "cerulean.css" # COOL
#APP_THEME = "amelia.css"
#APP_THEME = "cosmo.css"
#APP_THEME = "cyborg.css" # COOL
#APP_THEME = "flatly.css"
#APP_THEME = "journal.css"
#APP_THEME = "readable.css"
#APP_THEME = "simplex.css"
#APP_THEME = "slate.css" # COOL
#APP_THEME = "spacelab.css" # NICE
#APP_THEME = "united.css"
#APP_THEME = "darkly.css"
#APP_THEME = "lumen.css"
#APP_THEME = "paper.css"
#APP_THEME = "sandstone.css"
#APP_THEME = "solar.css"
#APP_THEME = "superhero.css"

3 changes: 3 additions & 0 deletions examples/composite_keys/run.py
@@ -0,0 +1,3 @@
from app import app

app.run(host='0.0.0.0', port=8080, debug=True)
57 changes: 57 additions & 0 deletions examples/composite_keys/testdata.py
@@ -0,0 +1,57 @@
import logging
from app import db
from app.models import Inventory, Datacenter, Rack, Item
import random
import string
from datetime import datetime

log = logging.getLogger(__name__)

DC_RACK_MAX = 20
ITEM_MAX = 1000

cities = ['Lisbon', 'Porto', 'Madrid', 'Barcelona', 'Frankfurt', 'London']

models = ['Server MX', 'Server MY', 'Server DL380', 'Server x440', 'Server x460']

datacenters = list()


def get_random_name(names_list, size=1):
return names_list[random.randrange(0, len(names_list))]

def serial_generator(size = 6, chars = string.ascii_uppercase + string.digits):
return ''.join(random.choice(chars) for _ in range(size))


for city in cities:
datacenter = Datacenter()
datacenter.name = "DC %s" % city
datacenter.address = city
datacenters.append(datacenter)
db.session.add(datacenter)
log.info(datacenter)
try:
db.session.commit()
for num in range(1, DC_RACK_MAX):
rack = Rack()
rack.num = num
rack.datacenter = datacenter
db.session.add(rack)

except Exception as e:
log.error("Creating Datacenter: %s", e)
db.session.rollback()

for i in range(1, ITEM_MAX):
item = Item()
item.serial_number = serial_generator()
item.model = get_random_name(models)
db.session.add(item)
log.info(item)
try:
db.session.commit()
except Exception as e:
log.error("Creating Item: %s", e)
db.session.rollback()

0 comments on commit 9d2a24e

Please sign in to comment.