Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for Django 1.8~1.10 and Python 3 #27

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,20 @@
*.pyc
settings_local.py

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
18 changes: 11 additions & 7 deletions report_utils/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import datetime

from report_utils.model_introspection import (
_get_field_by_name,
_get_remote_field,
get_relation_fields_from_model,
get_properties_from_model,
get_direct_fields_from_model,
Expand All @@ -35,6 +37,7 @@
"path path_verbose field field_verbose aggregate total group choices field_type",
)


def generate_filename(title, ends_with):
title = title.split('.')[0]
title.replace(' ', '_')
Expand All @@ -43,6 +46,7 @@ def generate_filename(title, ends_with):
title += ends_with
return title


class DataExportMixin(object):
def build_sheet(self, data, ws, sheet_name='report', header=None, widths=None):
first_row = 1
Expand Down Expand Up @@ -223,7 +227,7 @@ def can_change_or_view(model):
path += '__' # Legacy format to append a __ here.

new_model = get_model_from_path_string(model_class, path)
model_field = new_model._meta.get_field_by_name(field)[0]
model_field = new_model._meta.get_field(field)
choices = model_field.choices
new_display_fields.append(DisplayField(
path, '', field, '', '', None, None, choices, ''
Expand Down Expand Up @@ -541,7 +545,7 @@ def get_fields(self, model_class, field_name='', path='', path_verbose=''):
app_label = model_class._meta.app_label

if field_name != '':
field = model_class._meta.get_field_by_name(field_name)
field = _get_field_by_name(model_class, field_name)
if path_verbose:
path_verbose += "::"
# TODO: need actual model name to generate choice list (not pluralized field name)
Expand All @@ -555,9 +559,9 @@ def get_fields(self, model_class, field_name='', path='', path_verbose=''):
path += '__'
if field[2]: # Direct field
try:
new_model = field[0].related.parent_model
new_model = _get_remote_field(field[0]).parent_model
except AttributeError:
new_model = field[0].related.model
new_model = _get_remote_field(field[0]).model
path_verbose = new_model.__name__.lower()
else: # Indirect related field
try:
Expand All @@ -584,13 +588,13 @@ def get_fields(self, model_class, field_name='', path='', path_verbose=''):
def get_related_fields(self, model_class, field_name, path="", path_verbose=""):
""" Get fields for a given model """
if field_name:
field = model_class._meta.get_field_by_name(field_name)
field = _get_field_by_name(model_class, field_name)
if field[2]:
# Direct field
try:
new_model = field[0].related.parent_model()
new_model = _get_remote_field(field[0]).parent_model()
except AttributeError:
new_model = field[0].related.model
new_model = _get_remote_field(field[0]).model
else:
# Indirect related field
if hasattr(field[0], 'related_model'): # Django>=1.8
Expand Down
62 changes: 52 additions & 10 deletions report_utils/model_introspection.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
""" Functioned to introspect a model """
from itertools import chain
from django.contrib.contenttypes.models import ContentType
from django.db.models.fields import FieldDoesNotExist
from django.conf import settings
import inspect


def isprop(v):
return isinstance(v, property)


def get_properties_from_model(model_class):
""" Show properties from a model """
properties = []
Expand All @@ -19,17 +22,56 @@ def get_properties_from_model(model_class):
return sorted(properties, key=lambda k: k['label'])


def _get_all_field_names(model):
"""
100% compatible version of the old API of model._meta.get_all_field_names()
From: https://docs.djangoproject.com/en/1.9/ref/models/meta/#migrating-from-the-old-api
"""
return list(set(chain.from_iterable(
(field.name, field.attname) if hasattr(field, 'attname') else (field.name,)
for field in model._meta.get_fields()
# For complete backwards compatibility, you may want to exclude
# GenericForeignKey from the results.
if not (field.many_to_one and field.related_model is None)
)))


def _get_field_by_name(model_class, field_name):
"""
Compatible with old API of model_class._meta.get_field_by_name(field_name)
"""
field = model_class._meta.get_field(field_name)
return (
field,
field.model,
not field.auto_created or field.concrete,
field.many_to_many
)


def _get_remote_field(field):
"""
Compatible with Django 1.8~1.10 ('related' was renamed to 'remote_field')
"""
if hasattr(field, 'remote_field'):
return field.remote_field
elif hasattr(field, 'related'):
return field.related
else:
return None


def get_relation_fields_from_model(model_class):
""" Get related fields (m2m, FK, and reverse FK) """
relation_fields = []
all_fields_names = model_class._meta.get_all_field_names()
all_fields_names = _get_all_field_names(model_class)
for field_name in all_fields_names:
field = model_class._meta.get_field_by_name(field_name)
field = _get_field_by_name(model_class, field_name)
# get_all_field_names will return the same field
# both with and without _id. Ignore the duplicate.
if field_name[-3:] == '_id' and field_name[:-3] in all_fields_names:
continue
if field[3] or not field[2] or hasattr(field[0], 'related'):
if field[3] or not field[2] or _get_remote_field(field[0]):
field[0].field_name = field_name
relation_fields += [field[0]]
return relation_fields
Expand All @@ -38,10 +80,10 @@ def get_relation_fields_from_model(model_class):
def get_direct_fields_from_model(model_class):
""" Direct, not m2m, not FK """
direct_fields = []
all_fields_names = model_class._meta.get_all_field_names()
all_fields_names = _get_all_field_names(model_class)
for field_name in all_fields_names:
field = model_class._meta.get_field_by_name(field_name)
if field[2] and not field[3] and not hasattr(field[0], 'related'):
field = _get_field_by_name(model_class, field_name)
if field[2] and not field[3] and not _get_remote_field(field[0]):
direct_fields += [field[0]]
return direct_fields

Expand All @@ -68,15 +110,15 @@ def get_model_from_path_string(root_model, path):
for path_section in path.split('__'):
if path_section:
try:
field = root_model._meta.get_field_by_name(path_section)
field = _get_field_by_name(root_model, path_section)
except FieldDoesNotExist:
return root_model
if field[2]:
if hasattr(field[0], 'related'):
if _get_remote_field(field[0]):
try:
root_model = field[0].related.parent_model()
root_model = _get_remote_field(field[0]).parent_model()
except AttributeError:
root_model = field[0].related.model
root_model = _get_remote_field(field[0]).model
else:
if hasattr(field[0], 'related_model'):
root_model = field[0].related_model
Expand Down
2 changes: 0 additions & 2 deletions report_utils/utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
from .mixins import DataExportMixin