This is a utility script that scans a models.py file and automatically outputs the corresponding newforms-admin source code - the code that goes into the admin.py file. The purpose is to simplify the migration to newforms-admin.
Here is what it outputs:
-
an import line that lists only the needed classes from the model.
-
inline editing classes - inheriting from either
admin.TabularInline
oradmin.StackedInline
-
admin options classes containing any original
Admin
class contents ('fields'
is replaced by'fieldsets'
and the value of'classes'
is made into a tuple), plus the following fields whose values are determined automatically:inlines
,prepopulated_fields
,filter_horizontal
,filter_vertical
andraw_id_fields
-
invokations of
admin.site.register
for the generated admin options classes.
Example usage of the script (this will generate the admin.py file for the models.py file in the satchmo.product module):
./new-forms-gen.py satchmo.product > admin.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 | '''
Utility script for automatically generating a
newforms-admin admin.py file based on an old-style models.py file.
The generated source code is printed to standard output.
Usage: ./newforms_gen.py my.module > admin.py
where my.module contains a models.py file.
'''
import sys
import os.path
import re
import StringIO
import imp
import copy
def _update_admin_line(s):
s = re.sub(r"'classes':\s*'(.*?)'", r"'classes': ('\1',)", s) # make 'classes' a tuple
s = re.sub(r'fields\s*=', 'fieldsets =', s) # change fields into fieldsets
return s
def generate_newforms_admin(module_name):
# read the source code
path = imp.find_module(module_name.split('.')[0])[1]
filepath = os.path.join(*[path] + module_name.split('.')[1:] + ['models.py'])
source = open(filepath, 'r').read()
# initialize some variables
output = StringIO.StringIO()
inline_classes = []
model_names = []
model_admin = {}
# initialize mapping from admin fields to formatting functions
def format_tuple(data, name, indentation):
return '%s%s = %s' % (indentation, key, repr(tuple(data)))
def format_lines(data, name, indentation):
return '\n'.join([_update_admin_line(line) for line in data if line.strip() != 'pass'])
def format_dict(data, name, indentation):
items = ', '.join(("'%s': %s" % (field, value) for field, value in data.items()))
return '%s%s = {%s}' % (indentation, key, items)
def format_list_of_names(data, name, indentation):
return '%s%s = [%s]' % (indentation, name, ', '.join(data))
default_admin_info ={'admin_lines': [],
'inlines': [],
'prepopulated_fields': {},
'filter_horizontal': [],
'filter_vertical': [],
'raw_id_fields': [],
}
field_format_func = {'admin_lines': format_lines,
'inlines': format_list_of_names,
'prepopulated_fields': format_dict,
'filter_horizontal': format_tuple,
'filter_vertical': format_tuple,
'raw_id_fields': format_tuple,}
parts = re.compile(r'^class (\w+).*$', re.M).split(source)
for class_name, code in zip(parts[1::2], parts[2::2]):
# add model to list and initialize admin info for this model
model_names.append(class_name)
model_admin[class_name] = copy.deepcopy(default_admin_info)
# determine indentation
first_line = [line for line in code.split('\n') if line.strip()][0]
indentation = first_line[ : len(first_line) - len(first_line.lstrip())]
# find any Admin class declaration
m = re.search(r'(?m)^%sclass Admin:.*\n((%s[ \t]+.+\n)*)' % (indentation, indentation), code)
if m:
model_admin[class_name]['admin_lines'] = [line.replace(indentation, '', 1)
for line in m.group(1).split('\n') if line.strip()]
# find the fields in this class
fields = re.findall(r'(?m)^%s(\w+) *= *models\.(\w+).*?\((.*\n((%s[ \t]+.+\n)*))' % (indentation, indentation), code)
for field in fields:
# extract field name, type and rest of line
field_name, field_type, field_data = field[0:3]
# extract the field initialization kwargs (only works for simple values, not tuple values)
params = dict(re.findall(r'(\w+) *= *([a-zA-Z0-9._]+)', field_data))
first_param = field_data.split(',')[0].strip()
# find foreign keys with inline editing
if field_type.endswith('ForeignKey') and 'edit_inline' in params:
if 'TABULAR' in params['edit_inline']:
inline_class = 'admin.TabularInline'
else:
inline_class = 'admin.StackedInline'
fk_class = first_param
if fk_class.startswith("'") or fk_class.startswith('"'):
fk_class = fk_class[1:-1]
# build inline class
lines = ['class %s_Inline(%s):' % (class_name, inline_class)]
lines.append('%smodel = %s' % (indentation, class_name))
for input_field, output_field in [('num_in_admin', 'extra'), ]:
if input_field in params:
lines.append('%s%s = %s' % (indentation, output_field, params[input_field]))
# add to list of inline classes and add an entry in the ForeignKey model linking to this class
inline_classes.append('\n'.join(lines) + '\n')
if not fk_class in model_admin:
model_admin[fk_class] = copy.deepcopy(default_admin_info)
model_admin[fk_class]['inlines'].append('%s_Inline' % class_name)
# store info used for the admin options class fields
m = re.search(r'prepopulate_from=(\(.+?\))', field_data)
if m:
model_admin[class_name]['prepopulated_fields'][field_name] = m.group(1)
if 'filter_interface' in params and ('HORIZONTAL' in params['filter_interface'] or 'True' in params['filter_interface']):
model_admin[class_name]['filter_horizontal'].append(field_name)
if 'filter_interface' in params and 'VERTICAL' in params['filter_interface']:
model_admin[class_name]['filter_vertical'].append(field_name)
if 'raw_id_admin' in params and params['raw_id_admin'] == 'True':
model_admin[class_name]['raw_id_fields'].append(field_name)
register_classes = [] # admin classes to register
for model in set(model_names) | set(model_admin.keys()):
if model in model_admin:
# build admin class
lines = ['class %sOptions(admin.ModelAdmin):' % model]
for key, value in model_admin[model].items():
if value:
line = field_format_func[key](value, key, indentation)
if line:
lines.append(line)
# if the admin class has any contents
if len(lines) >= 2:
admin_class = '\n'.join(lines)
if model in model_names:
register_classes.append((model, '%sOptions' % model))
else:
# if the model is not defined in this module comment out the Admin class declaration
# (it's the user's responsibility to move relevant parts of the code to the right place)
admin_class = '\n'.join(['# ' + line for line in admin_class.split('\n')])
output.write(admin_class + '\n\n') # output admin class definition
# else if the admin class lacks contents but there was an
# empty Admin class with just a 'pass' line in the original source
# and the model class was defined in the current module
elif model_admin[model]['admin_lines'] and model in model_names:
register_classes.append((model,))
for class_names in register_classes:
output.write('admin.site.register(%s)\n' % ', '.join(class_names))
# construct final output
output = output.getvalue()
lines = []
lines.append('from django.contrib import admin')
lines.append('from django.utils.translation import ugettext_lazy as _\n')
lines.extend(inline_classes)
lines.append(output)
output = '\n'.join(lines)
# add an import line that imports all models referenced in the code
referenced_models = [m for m in model_names if re.search(r'\b%s\b' % m, output)]
if referenced_models:
output = '\n'.join(['from %s.models import ' % module_name + ', '.join(referenced_models), output])
return output
if __name__ == '__main__':
print generate_newforms_admin(sys.argv[1])
|
More like this
- Template tag - list punctuation for a list of items by shapiromatron 10 months, 1 week ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 2 weeks ago
- Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 5 months ago
- Help text hyperlinks by sa2812 1 year, 6 months ago
Comments
Excellent!
#
Doesn't work. Do we need some Python Path juju going on?
I get the error:
ImportError: No module named django_site
#
I fixed my problem manually. My guess is that
#
do you have to add something to urls.py or views.py to get this to run?
#
the correct usage instructions are
Usage: open terminal to the folder the script is in and run this command
python ./603.py my.module > admin.py
(where my.module is the project.app that contains the models.py file.)
#
Cool, thanks.
I added a small modification to make it work for me, first line under
if __name__ == '__main__':
This is because I don't have the project directory under pythonpath by default.
#
Please login first before commenting.