/
app.py
184 lines (133 loc) · 5.04 KB
/
app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
import os
import os.path as op
from pathlib import Path
from werkzeug.utils import secure_filename
from sqlalchemy import event
from flask import Flask, request, render_template
from flask_sqlalchemy import SQLAlchemy
from wtforms import fields
import flask_admin as admin
from flask_admin.contrib.sqla.ajax import QueryAjaxModelLoader
from flask_admin.form import RenderTemplateWidget
from flask_admin.model.form import InlineFormAdmin
from flask_admin.contrib.sqla import ModelView
from flask_admin.contrib.sqla.form import InlineModelConverter
from flask_admin.contrib.sqla.fields import InlineModelFormList
# Create application
app = Flask(__name__)
# Create dummy secret key so we can use sessions
app.config['SECRET_KEY'] = '123456790'
# Create in-memory database
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.sqlite'
app.config['SQLALCHEMY_ECHO'] = True
db = SQLAlchemy(app)
# Figure out base upload path
base_path = op.join(op.dirname(__file__), 'static')
# Create models
class Location(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.Unicode(64))
class ImageType(db.Model):
"""
Just so the LocationImage can have another foreign key,
so we can test the "form_ajax_refs" inside the "inline_models"
"""
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(64))
def __repr__(self) -> str:
"""
Represent this model as a string
(e.g. in the Image Type list dropdown when creating an inline model)
"""
return self.name
class LocationImage(db.Model):
id = db.Column(db.Integer, primary_key=True)
alt = db.Column(db.Unicode(128))
path = db.Column(db.String(64))
location_id = db.Column(db.Integer, db.ForeignKey(Location.id))
location = db.relation(Location, backref='images')
image_type_id = db.Column(db.Integer, db.ForeignKey(ImageType.id))
image_type = db.relation(ImageType, backref='images')
# Register after_delete handler which will delete image file after model gets deleted
@event.listens_for(LocationImage, 'after_delete')
def _handle_image_delete(mapper, conn, target):
try:
if target.path:
os.remove(op.join(base_path, target.path))
except:
pass
# This widget uses custom template for inline field list
class CustomInlineFieldListWidget(RenderTemplateWidget):
def __init__(self):
super(CustomInlineFieldListWidget, self).__init__('field_list.html')
# This InlineModelFormList will use our custom widget and hide row controls
class CustomInlineModelFormList(InlineModelFormList):
widget = CustomInlineFieldListWidget()
def display_row_controls(self, field):
return False
# Create custom InlineModelConverter and tell it to use our InlineModelFormList
class CustomInlineModelConverter(InlineModelConverter):
inline_field_list_type = CustomInlineModelFormList
# Customized inline form handler
class LocationImageInlineModelForm(InlineFormAdmin):
form_excluded_columns = ('path',)
form_label = 'Image'
# Setup AJAX lazy-loading for the ImageType inside the inline model
form_ajax_refs = {
"image_type": QueryAjaxModelLoader(
name="image_type",
session=db.session,
model=ImageType,
fields=("name",),
order_by="name",
placeholder="Please use an AJAX query to select an image type for the image",
minimum_input_length=0,
)
}
def __init__(self):
return super(LocationImageInlineModelForm, self).__init__(LocationImage)
def postprocess_form(self, form_class):
form_class.upload = fields.FileField('Image')
return form_class
def on_model_change(self, form, model):
file_data = request.files.get(form.upload.name)
if file_data:
model.path = secure_filename(file_data.filename)
file_data.save(op.join(base_path, model.path))
# Administrative class
class LocationAdmin(ModelView):
inline_model_form_converter = CustomInlineModelConverter
inline_models = (LocationImageInlineModelForm(),)
def __init__(self):
super(LocationAdmin, self).__init__(Location, db.session, name='Locations')
# Simple page to show images
@app.route('/')
def index():
locations = db.session.query(Location).all()
return render_template('locations.html', locations=locations)
def first_time_setup():
"""Run this to setup the database for the first time"""
# Create DB
db.drop_all()
db.create_all()
# Add some image types for the form_ajax_refs inside the inline_model
image_types = ("JPEG", "PNG", "GIF")
for image_type in image_types:
model = ImageType(name=image_type)
db.session.add(model)
db.session.commit()
return
# if __name__ == '__main__':
# Create upload directory
try:
os.mkdir(base_path)
except OSError:
pass
# Create admin
admin = admin.Admin(app, name='Example: Inline Models')
# Add views
admin.add_view(LocationAdmin())
# Create DB
first_time_setup()
# Start app
app.run(debug=True)