Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
flask-admin
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
JIRA
JIRA
Merge Requests
0
Merge Requests
0
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Commits
Issue Boards
Open sidebar
Python-Dev
flask-admin
Commits
2845e4b2
Commit
2845e4b2
authored
Aug 26, 2012
by
Serge S. Koval
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Added inline model editing support for peewee backend
parent
81555678
Changes
12
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
256 additions
and
53 deletions
+256
-53
simple.py
examples/peewee/simple.py
+20
-1
simple.py
examples/sqla/simple.py
+1
-4
form.py
flask_admin/contrib/peeweemodel/form.py
+127
-2
tools.py
flask_admin/contrib/peeweemodel/tools.py
+7
-0
view.py
flask_admin/contrib/peeweemodel/view.py
+30
-12
fields.py
flask_admin/contrib/sqlamodel/fields.py
+1
-1
form.py
flask_admin/contrib/sqlamodel/form.py
+34
-23
view.py
flask_admin/contrib/sqlamodel/view.py
+3
-6
fields.py
flask_admin/model/fields.py
+29
-0
form.py
flask_admin/model/form.py
+2
-2
lib.html
flask_admin/templates/admin/lib.html
+1
-1
inline_form_list.html
flask_admin/templates/admin/model/inline_form_list.html
+1
-1
No files found.
examples/peewee/simple.py
View file @
2845e4b2
...
@@ -25,6 +25,16 @@ class User(BaseModel):
...
@@ -25,6 +25,16 @@ class User(BaseModel):
return
self
.
username
return
self
.
username
class
UserInfo
(
BaseModel
):
key
=
peewee
.
CharField
(
max_length
=
64
)
value
=
peewee
.
CharField
(
max_length
=
64
)
user
=
peewee
.
ForeignKeyField
(
User
)
def
__unicode__
(
self
):
return
'
%
s -
%
s'
%
(
self
.
key
,
self
.
value
)
class
Post
(
BaseModel
):
class
Post
(
BaseModel
):
title
=
peewee
.
CharField
(
max_length
=
120
)
title
=
peewee
.
CharField
(
max_length
=
120
)
text
=
peewee
.
TextField
(
null
=
False
)
text
=
peewee
.
TextField
(
null
=
False
)
...
@@ -36,6 +46,10 @@ class Post(BaseModel):
...
@@ -36,6 +46,10 @@ class Post(BaseModel):
return
self
.
title
return
self
.
title
class
UserAdmin
(
peeweemodel
.
ModelView
):
inline_models
=
(
UserInfo
,)
class
PostAdmin
(
peeweemodel
.
ModelView
):
class
PostAdmin
(
peeweemodel
.
ModelView
):
# Visible columns in the list view
# Visible columns in the list view
#list_columns = ('title', 'user')
#list_columns = ('title', 'user')
...
@@ -60,13 +74,18 @@ def index():
...
@@ -60,13 +74,18 @@ def index():
if
__name__
==
'__main__'
:
if
__name__
==
'__main__'
:
import
logging
logging
.
basicConfig
()
logging
.
getLogger
()
.
setLevel
(
logging
.
DEBUG
)
admin
=
admin
.
Admin
(
app
,
'Peewee Models'
)
admin
=
admin
.
Admin
(
app
,
'Peewee Models'
)
admin
.
add_view
(
peeweemodel
.
ModelView
(
User
))
admin
.
add_view
(
UserAdmin
(
User
))
admin
.
add_view
(
PostAdmin
(
Post
))
admin
.
add_view
(
PostAdmin
(
Post
))
try
:
try
:
User
.
create_table
()
User
.
create_table
()
UserInfo
.
create_table
()
Post
.
create_table
()
Post
.
create_table
()
except
:
except
:
pass
pass
...
...
examples/sqla/simple.py
View file @
2845e4b2
...
@@ -64,9 +64,6 @@ class UserInfo(db.Model):
...
@@ -64,9 +64,6 @@ class UserInfo(db.Model):
key
=
db
.
Column
(
db
.
String
(
64
),
nullable
=
False
)
key
=
db
.
Column
(
db
.
String
(
64
),
nullable
=
False
)
value
=
db
.
Column
(
db
.
String
(
64
))
value
=
db
.
Column
(
db
.
String
(
64
))
tag_id
=
db
.
Column
(
db
.
Integer
(),
db
.
ForeignKey
(
Tag
.
id
),
nullable
=
False
)
tags
=
db
.
relationship
(
Tag
,
backref
=
'userinfos'
)
user_id
=
db
.
Column
(
db
.
Integer
(),
db
.
ForeignKey
(
User
.
id
))
user_id
=
db
.
Column
(
db
.
Integer
(),
db
.
ForeignKey
(
User
.
id
))
user
=
db
.
relationship
(
User
,
backref
=
'info'
)
user
=
db
.
relationship
(
User
,
backref
=
'info'
)
...
@@ -82,7 +79,7 @@ def index():
...
@@ -82,7 +79,7 @@ def index():
# Customized User model admin
# Customized User model admin
class
UserAdmin
(
sqlamodel
.
ModelView
):
class
UserAdmin
(
sqlamodel
.
ModelView
):
inline_models
=
(
'info'
,)
inline_models
=
(
UserInfo
,)
# Customized Post model admin
# Customized Post model admin
...
...
flask_admin/contrib/peeweemodel/form.py
View file @
2845e4b2
from
wtforms
import
fields
from
wtforms
import
fields
from
peewee
import
DateTimeField
,
DateField
,
TimeField
from
peewee
import
DateTimeField
,
DateField
,
TimeField
,
BaseModel
,
ForeignKeyField
from
wtfpeewee.orm
import
ModelConverter
from
wtfpeewee.orm
import
ModelConverter
,
model_form
from
flask.ext.admin
import
form
from
flask.ext.admin
import
form
from
flask.ext.admin.model.form
import
InlineFormAdmin
from
flask.ext.admin.model.fields
import
InlineModelFormField
from
flask.ext.admin.model.widgets
import
InlineFormListWidget
from
.tools
import
get_primary_key
class
InlineModelFormList
(
fields
.
FieldList
):
widget
=
InlineFormListWidget
()
def
__init__
(
self
,
form
,
model
,
prop
,
**
kwargs
):
self
.
form
=
form
self
.
model
=
model
self
.
prop
=
prop
# TODO: Fix me
self
.
_pk
=
get_primary_key
(
model
)
super
(
InlineModelFormList
,
self
)
.
__init__
(
InlineModelFormField
(
form
,
self
.
_pk
),
**
kwargs
)
def
__call__
(
self
,
**
kwargs
):
return
self
.
widget
(
self
,
template
=
self
.
form
(),
**
kwargs
)
def
process
(
self
,
formdata
,
data
=
None
):
if
not
formdata
:
data
=
self
.
model
.
select
()
.
where
(
user
=
data
)
.
execute
()
else
:
data
=
None
return
super
(
InlineModelFormList
,
self
)
.
process
(
formdata
,
data
)
def
populate_obj
(
self
,
obj
,
name
):
pass
def
save_related
(
self
,
obj
):
model_id
=
getattr
(
obj
,
self
.
_pk
)
values
=
self
.
model
.
select
()
.
where
(
user
=
model_id
)
.
execute
()
pk_map
=
dict
((
str
(
getattr
(
v
,
self
.
_pk
)),
v
)
for
v
in
values
)
# Handle request data
for
field
in
self
.
entries
:
field_id
=
field
.
get_pk
()
if
field_id
in
pk_map
:
model
=
pk_map
[
field_id
]
if
field
.
should_delete
():
model
.
delete_instance
(
recursive
=
True
)
continue
else
:
model
=
self
.
model
()
field
.
populate_obj
(
model
,
None
)
# Force relation
setattr
(
model
,
self
.
prop
,
model_id
)
model
.
save
()
class
CustomModelConverter
(
ModelConverter
):
class
CustomModelConverter
(
ModelConverter
):
...
@@ -24,3 +84,68 @@ class CustomModelConverter(ModelConverter):
...
@@ -24,3 +84,68 @@ class CustomModelConverter(ModelConverter):
def
handle_time
(
self
,
model
,
field
,
**
kwargs
):
def
handle_time
(
self
,
model
,
field
,
**
kwargs
):
return
field
.
name
,
form
.
TimeField
(
**
kwargs
)
return
field
.
name
,
form
.
TimeField
(
**
kwargs
)
def
contribute_inline
(
model
,
form_class
,
inline_models
):
# Contribute columns
for
p
in
inline_models
:
# Figure out settings
if
isinstance
(
p
,
tuple
):
info
=
InlineFormAdmin
(
p
[
0
],
**
p
[
1
])
elif
isinstance
(
p
,
InlineFormAdmin
):
info
=
p
elif
isinstance
(
p
,
BaseModel
):
info
=
InlineFormAdmin
(
p
)
else
:
raise
Exception
(
'Unknown inline model admin:
%
s'
%
repr
(
p
))
# Find property from target model to current model
reverse_field
=
None
for
field
in
info
.
model
.
_meta
.
get_fields
():
field_type
=
type
(
field
)
if
field_type
==
ForeignKeyField
:
if
field
.
to
==
model
:
reverse_field
=
field
break
else
:
raise
Exception
(
'Cannot find reverse relation for model
%
s'
%
info
.
model
)
# Remove reverse property from the list
ignore
=
[
reverse_field
.
name
]
if
info
.
exclude
:
exclude
=
ignore
+
info
.
exclude
else
:
exclude
=
ignore
# Create field
converter
=
CustomModelConverter
()
child_form
=
model_form
(
info
.
model
,
base_class
=
form
.
BaseForm
,
only
=
info
.
include
,
exclude
=
exclude
,
allow_pk
=
True
,
converter
=
converter
)
prop_name
=
'fa_
%
s'
%
model
.
__name__
setattr
(
form_class
,
prop_name
,
InlineModelFormList
(
child_form
,
info
.
model
,
reverse_field
.
name
,
label
=
info
.
model
.
__name__
))
setattr
(
field
.
to
,
prop_name
,
property
(
lambda
self
:
self
.
id
))
return
form_class
def
save_inline
(
form
,
model
):
for
_
,
f
in
form
.
_fields
.
iteritems
():
if
f
.
type
==
'InlineModelFormList'
:
f
.
save_related
(
model
)
flask_admin/contrib/peeweemodel/tools.py
0 → 100644
View file @
2845e4b2
from
peewee
import
PrimaryKeyField
def
get_primary_key
(
model
):
for
n
,
f
in
model
.
_meta
.
get_sorted_fields
():
if
type
(
f
)
==
PrimaryKeyField
:
return
n
flask_admin/contrib/peeweemodel/view.py
View file @
2845e4b2
...
@@ -9,7 +9,8 @@ from wtfpeewee.orm import model_form
...
@@ -9,7 +9,8 @@ from wtfpeewee.orm import model_form
from
flask.ext.admin.actions
import
action
from
flask.ext.admin.actions
import
action
from
flask.ext.admin.contrib.peeweemodel
import
filters
from
flask.ext.admin.contrib.peeweemodel
import
filters
from
.form
import
CustomModelConverter
from
.form
import
CustomModelConverter
,
contribute_inline
,
save_inline
from
.tools
import
get_primary_key
class
ModelView
(
BaseModelView
):
class
ModelView
(
BaseModelView
):
...
@@ -49,6 +50,14 @@ class ModelView(BaseModelView):
...
@@ -49,6 +50,14 @@ class ModelView(BaseModelView):
for your model.
for your model.
"""
"""
inline_models
=
None
"""
Inline related-model editing for parent to child relation::
class MyModelView(ModelView):
inline_models = (Post,)
"""
def
__init__
(
self
,
model
,
name
=
None
,
def
__init__
(
self
,
model
,
name
=
None
,
category
=
None
,
endpoint
=
None
,
url
=
None
):
category
=
None
,
endpoint
=
None
,
url
=
None
):
self
.
_search_fields
=
[]
self
.
_search_fields
=
[]
...
@@ -64,11 +73,7 @@ class ModelView(BaseModelView):
...
@@ -64,11 +73,7 @@ class ModelView(BaseModelView):
return
model
.
_meta
.
get_sorted_fields
()
return
model
.
_meta
.
get_sorted_fields
()
def
scaffold_pk
(
self
):
def
scaffold_pk
(
self
):
for
n
,
f
in
self
.
_get_model_fields
():
return
get_primary_key
(
self
.
model
)
if
type
(
f
)
==
PrimaryKeyField
:
return
n
return
None
def
get_pk_value
(
self
,
model
):
def
get_pk_value
(
self
,
model
):
return
getattr
(
model
,
self
.
_primary_key
)
return
getattr
(
model
,
self
.
_primary_key
)
...
@@ -149,12 +154,17 @@ class ModelView(BaseModelView):
...
@@ -149,12 +154,17 @@ class ModelView(BaseModelView):
return
isinstance
(
filter
,
filters
.
BasePeeweeFilter
)
return
isinstance
(
filter
,
filters
.
BasePeeweeFilter
)
def
scaffold_form
(
self
):
def
scaffold_form
(
self
):
return
model_form
(
self
.
model
,
form_class
=
model_form
(
self
.
model
,
base_class
=
form
.
BaseForm
,
base_class
=
form
.
BaseForm
,
only
=
self
.
form_columns
,
only
=
self
.
form_columns
,
exclude
=
self
.
excluded_form_columns
,
exclude
=
self
.
excluded_form_columns
,
field_args
=
self
.
form_args
,
field_args
=
self
.
form_args
,
converter
=
CustomModelConverter
())
converter
=
CustomModelConverter
())
if
self
.
inline_models
:
form_class
=
contribute_inline
(
self
.
model
,
form_class
,
self
.
inline_models
)
return
form_class
def
_handle_join
(
self
,
query
,
field
,
joins
):
def
_handle_join
(
self
,
query
,
field
,
joins
):
if
field
.
model
!=
self
.
model
:
if
field
.
model
!=
self
.
model
:
...
@@ -237,6 +247,10 @@ class ModelView(BaseModelView):
...
@@ -237,6 +247,10 @@ class ModelView(BaseModelView):
model
=
self
.
model
()
model
=
self
.
model
()
form
.
populate_obj
(
model
)
form
.
populate_obj
(
model
)
model
.
save
()
model
.
save
()
# For peewee have to save inline forms after model was saved
save_inline
(
form
,
model
)
return
True
return
True
except
Exception
,
ex
:
except
Exception
,
ex
:
flash
(
gettext
(
'Failed to create model.
%(error)
s'
,
error
=
str
(
ex
)),
'error'
)
flash
(
gettext
(
'Failed to create model.
%(error)
s'
,
error
=
str
(
ex
)),
'error'
)
...
@@ -252,6 +266,10 @@ class ModelView(BaseModelView):
...
@@ -252,6 +266,10 @@ class ModelView(BaseModelView):
try
:
try
:
form
.
populate_obj
(
model
)
form
.
populate_obj
(
model
)
model
.
save
()
model
.
save
()
# For peewee have to save inline forms after model was saved
save_inline
(
form
,
model
)
return
True
return
True
except
Exception
,
ex
:
except
Exception
,
ex
:
flash
(
gettext
(
'Failed to update model.
%(error)
s'
,
error
=
str
(
ex
)),
'error'
)
flash
(
gettext
(
'Failed to update model.
%(error)
s'
,
error
=
str
(
ex
)),
'error'
)
...
...
flask_admin/contrib/sqlamodel/fields.py
View file @
2845e4b2
...
@@ -205,7 +205,7 @@ class InlineModelFormList(FieldList):
...
@@ -205,7 +205,7 @@ class InlineModelFormList(FieldList):
# Create primary key map
# Create primary key map
pk_map
=
dict
((
str
(
getattr
(
v
,
self
.
_pk
)),
v
)
for
v
in
values
)
pk_map
=
dict
((
str
(
getattr
(
v
,
self
.
_pk
)),
v
)
for
v
in
values
)
#
Create fake object to work around wtforms limitations
#
Handle request data
for
field
in
self
.
entries
:
for
field
in
self
.
entries
:
field_id
=
field
.
get_pk
()
field_id
=
field
.
get_pk
()
...
...
flask_admin/contrib/sqlamodel/form.py
View file @
2845e4b2
...
@@ -268,34 +268,42 @@ def contribute_inline(session, model, form_class, inline_models):
...
@@ -268,34 +268,42 @@ def contribute_inline(session, model, form_class, inline_models):
# Contribute columns
# Contribute columns
for
p
in
inline_models
:
for
p
in
inline_models
:
# Figure out
# Figure out settings
if
isinstance
(
p
,
basestring
):
if
isinstance
(
p
,
tuple
):
info
=
InlineFormAdmin
(
p
)
elif
isinstance
(
p
,
tuple
):
info
=
InlineFormAdmin
(
p
[
0
],
**
p
[
1
])
info
=
InlineFormAdmin
(
p
[
0
],
**
p
[
1
])
elif
isinstance
(
p
,
InlineFormAdmin
):
elif
isinstance
(
p
,
InlineFormAdmin
):
info
=
p
info
=
p
elif
hasattr
(
p
,
'_sa_class_manager'
):
info
=
InlineFormAdmin
(
p
)
else
:
else
:
raise
Exception
(
'Unknown inline model admin:
%
s'
%
repr
(
p
))
raise
Exception
(
'Unknown inline model admin:
%
s'
%
repr
(
p
))
prop
=
mapper
.
get_property
(
info
.
field
)
# Find property from target model to current model
if
prop
is
None
:
target_mapper
=
info
.
model
.
_sa_class_manager
.
mapper
raise
Exception
(
'Inline form property
%
s.
%
s was not found'
%
(
model
.
__name__
,
info
.
field
))
reverse_prop
=
None
if
not
hasattr
(
prop
,
'direction'
):
for
prop
in
target_mapper
.
iterate_properties
:
raise
Exception
(
'Failed to convert inline admin
%
s - only one-to-many relations are supported'
%
info
.
field
)
if
hasattr
(
prop
,
'direction'
)
and
prop
.
direction
.
name
==
'MANYTOONE'
:
if
prop
.
mapper
.
class_
==
model
:
reverse_prop
=
prop
break
else
:
raise
Exception
(
'Cannot find reverse relation for model
%
s'
%
info
.
model
)
if
prop
.
direction
.
name
!=
'ONETOMANY'
:
# Find forward property
raise
Exception
(
'Failed to convert inline admin
%
s - only one-to-many relations are supported'
%
info
.
field
)
forward_prop
=
None
# Find reverse relationship (to exlude from the list)
for
prop
in
mapper
.
iterate_properties
:
ignore
=
[]
if
hasattr
(
prop
,
'direction'
)
and
prop
.
direction
.
name
==
'ONETOMANY'
:
if
prop
.
mapper
.
class_
==
target_mapper
.
class_
:
forward_prop
=
prop
break
else
:
raise
Exception
(
'Cannot find forward relation for model
%
s'
%
info
.
model
)
for
remote_prop
in
prop
.
mapper
.
iterate_properties
:
# Remove reverse property from the list
if
hasattr
(
remote_prop
,
'direction'
)
and
remote_prop
.
direction
.
name
==
'MANYTOONE'
:
ignore
=
[
reverse_prop
.
key
]
if
remote_prop
.
mapper
.
class_
==
prop
.
parent
.
class_
:
ignore
.
append
(
remote_prop
.
key
)
if
info
.
exclude
:
if
info
.
exclude
:
exclude
=
ignore
+
info
.
exclude
exclude
=
ignore
+
info
.
exclude
...
@@ -303,15 +311,18 @@ def contribute_inline(session, model, form_class, inline_models):
...
@@ -303,15 +311,18 @@ def contribute_inline(session, model, form_class, inline_models):
exclude
=
ignore
exclude
=
ignore
# Create field
# Create field
remote_model
=
prop
.
mapper
.
class_
converter
=
AdminModelConverter
(
session
,
info
)
converter
=
AdminModelConverter
(
session
,
info
)
child_form
=
get_form
(
remote_model
,
converter
,
child_form
=
get_form
(
info
.
model
,
converter
,
only
=
info
.
include
,
only
=
info
.
include
,
exclude
=
exclude
,
exclude
=
exclude
,
hidden_pk
=
True
)
hidden_pk
=
True
)
setattr
(
form_class
,
p
,
setattr
(
form_class
,
InlineModelFormList
(
child_form
,
session
,
remote_model
,
p
))
forward_prop
.
key
,
InlineModelFormList
(
child_form
,
session
,
info
.
model
,
forward_prop
.
key
))
return
form_class
return
form_class
flask_admin/contrib/sqlamodel/view.py
View file @
2845e4b2
...
@@ -126,13 +126,10 @@ class ModelView(BaseModelView):
...
@@ -126,13 +126,10 @@ class ModelView(BaseModelView):
inline_models
=
None
inline_models
=
None
"""
"""
Inline related-model editing for parent to child relation
.
Inline related-model editing for parent to child relation
::
If you have child relation with name 'posts', you can generate inline
class MyModelView(ModelView):
administration interface by using this code::
inline_models = (Post,)
class MyModelView(BaseModelView):
inline_models = ('posts',)
"""
"""
def
__init__
(
self
,
model
,
session
,
def
__init__
(
self
,
model
,
session
,
...
...
flask_admin/model/fields.py
0 → 100644
View file @
2845e4b2
from
wtforms.fields
import
FormField
class
InlineModelFormField
(
FormField
):
def
__init__
(
self
,
form
,
pk
,
**
kwargs
):
super
(
InlineModelFormField
,
self
)
.
__init__
(
form
,
**
kwargs
)
self
.
_pk
=
pk
self
.
_should_delete
=
False
def
process
(
self
,
formdata
,
data
=
None
):
super
(
InlineModelFormField
,
self
)
.
process
(
formdata
,
data
)
# Grab delete key
if
formdata
:
key
=
'del-
%
s'
%
self
.
id
if
key
in
formdata
:
self
.
_should_delete
=
True
def
should_delete
(
self
):
return
self
.
_should_delete
def
get_pk
(
self
):
return
getattr
(
self
.
form
,
self
.
_pk
)
.
data
def
populate_obj
(
self
,
obj
,
name
):
for
name
,
field
in
self
.
form
.
_fields
.
iteritems
():
if
name
!=
self
.
_pk
:
field
.
populate_obj
(
obj
,
name
)
flask_admin/model/form.py
View file @
2845e4b2
...
@@ -11,8 +11,8 @@ def converts(*args):
...
@@ -11,8 +11,8 @@ def converts(*args):
class
InlineFormAdmin
(
object
):
class
InlineFormAdmin
(
object
):
def
__init__
(
self
,
field
,
**
kwargs
):
def
__init__
(
self
,
model
,
**
kwargs
):
self
.
field
=
field
self
.
model
=
model
defaults
=
dict
(
include
=
None
,
defaults
=
dict
(
include
=
None
,
exclude
=
None
)
exclude
=
None
)
...
...
flask_admin/templates/admin/lib.html
View file @
2845e4b2
...
@@ -74,7 +74,7 @@
...
@@ -74,7 +74,7 @@
{%- endmacro %}
{%- endmacro %}
{% macro render_form_fields(form, focus_set=False) %}
{% macro render_form_fields(form, focus_set=False) %}
{{ form.hidden_tag() }}
{{ form.hidden_tag()
if form.hidden_tag is defined
}}
{% for f in form if f.type != 'HiddenField' and f.type != 'CSRFTokenField' %}
{% for f in form if f.type != 'HiddenField' and f.type != 'CSRFTokenField' %}
<div
class=
"control-group{% if f.errors %} error{% endif %}"
>
<div
class=
"control-group{% if f.errors %} error{% endif %}"
>
...
...
flask_admin/templates/admin/model/inline_form_list.html
View file @
2845e4b2
...
@@ -29,5 +29,5 @@
...
@@ -29,5 +29,5 @@
</div>
</div>
{% endfor %}
{% endfor %}
</div>
</div>
<a
href=
"#"
class=
"btn"
onclick=
"faForm.addInlineModel('{{ field.id }}', '#{{ field.id }}-forms', {{ render_template(template)|tojson }});"
>
Add {{ field.
name
}}
</a>
<a
href=
"#"
class=
"btn"
onclick=
"faForm.addInlineModel('{{ field.id }}', '#{{ field.id }}-forms', {{ render_template(template)|tojson }});"
>
Add {{ field.
label.text
}}
</a>
</div>
</div>
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment