Skip to content

Commit

Permalink
Refactor select_related to use the query_terms generator, singling ou…
Browse files Browse the repository at this point in the history
…t query walking logic.
  • Loading branch information
spectras committed Feb 13, 2015
1 parent 374ebd4 commit 733e692
Show file tree
Hide file tree
Showing 3 changed files with 32 additions and 64 deletions.
5 changes: 4 additions & 1 deletion hvad/descriptors.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ def __get__(self, instance, instance_type=None):
raise AttributeError('Attribute not available until registry is ready.')
# Don't raise an attribute error so we can use it in admin.
try:
return self.opts.translations_model._meta.get_field(self.name).default
if django.VERSION >= (1, 8):
return self.opts.translations_model._meta.get_field(self.name).default
else:
return self.opts.translations_model._meta.get_field_by_name(self.name)[0].default
except FieldDoesNotExist as e:
raise AttributeError(*e.args)
return getattr(self.translation(instance), self.name)
Expand Down
85 changes: 24 additions & 61 deletions hvad/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,87 +310,50 @@ def _add_select_related(self, language_code):
force_unique_fields = []

for query_key in fields:
bits = query_key.split('__')
model = self.shared_model

for depth, bit in enumerate(bits):
# Resolve the field
if django.VERSION >= (1, 8):
try: # lookup the field on the shared model
field = model._meta.get_field.real(bit)
translated = False
except models.FieldDoesNotExist: # nope, that's from translations
field = model._meta.translations_model._meta.get_field(bit)
translated = True
except AttributeError: # model is not translatable anyway
field = model._meta.get_field(bit)
translated = False
direct = (
not field.auto_created or
getattr(field, 'db_column', None) or
getattr(field, 'attname', None)
)
else:
# older versions do not retrieve reverse/m2m with get_field, we must use the obsolete api
try:
field, _, direct, _ = model._meta.get_field_by_name.real(bit)
translated = False
except models.FieldDoesNotExist:
field, _, direct, _ = model._meta.translations_model._meta.get_field_by_name(bit)
translated = True
except AttributeError:
field, _, direct, _ = model._meta.get_field_by_name(bit)
translated = False


# Adjust current path bit
if depth == 0 and not translated:
newbits = []
for term in query_terms(self.shared_model, query_key):

# Translate term
if term.depth == 0 and not term.translated:
# on initial depth we must key to shared model
bits[depth] = 'master__%s' % bit
elif depth > 0 and translated:
newbits.append('master__%s' % term.term)
elif term.depth > 0 and term.translated:
# on deeper levels we must key to translations model
# this will work because translations will be seen as _unique
# at query time
bits[depth] = '%s__%s' % (model._meta.translations_accessor, bit)


# Find out target model
if direct: # field is on model
if field.rel: # field is a foreign key, follow it
if hasattr(field.rel, 'through'):
raise FieldError('Cannot select_related: %s can be multiple objects. '
'Use prefetch_related instead.' % query_key)
target = field.rel.to
else: # field is a regular field
raise FieldError('Cannot select_related: %s is a regular field' % query_key)
else: # field is a m2m or reverse fk
newbits.append('%s__%s' % (term.model._meta.translations_accessor, term.term))
else:
newbits.append(term.term)

# Some helpful messages for common mistakes
if term.many:
raise FieldError('Cannot select_related: %s can be multiple objects. '
'Use prefetch_related instead.' % query_key)
if term.target is None:
raise FieldError('Cannot select_related: %s is a regular field' % query_key)
if hasattr(term.field.rel, 'through'):
raise FieldError('Cannot select_related: %s can be multiple objects. '
'Use prefetch_related instead.' % query_key)
# would be target = field.model / field.related_model to follow

# If target is a translated model, select its translations
if hasattr(target._meta, 'translations_accessor'):
target_translations = getattr(term.target._meta, 'translations_accessor', None)
if target_translations is not None:
# Add the model
target_query = '__'.join(bits[:depth+1])
related_queries.append('%s__%s' % (
target_query,
target._meta.translations_accessor
))
target_query = '__'.join(newbits)
related_queries.append('%s__%s' % (target_query, target_translations))

# Add a language filter for the translation
target_translations = target._meta.translations_accessor
language_filters.append('%s__%s__language_code' % (
target_query,
target_translations,
))

# Remember to mark the field unique so JOIN is generated
# and row decoder gets cached items
target_transfield = getattr(target, target_translations).related.field
target_transfield = getattr(term.target, target_translations).related.field
force_unique_fields.append(target_transfield)

model = target
related_queries.append('__'.join(bits))
related_queries.append('__'.join(newbits))

# Apply results to query
self.query.add_select_related(related_queries)
Expand Down
6 changes: 4 additions & 2 deletions hvad/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
#===============================================================================
# Generators abstracting walking through internal django structures

QueryTerm = namedtuple('QueryTerm', 'depth term model field translated target')
QueryTerm = namedtuple('QueryTerm', 'depth term model field translated target many')

def query_terms(model, path):
""" Yields QueryTerms of given path starting from given model.
Expand Down Expand Up @@ -68,6 +68,7 @@ def query_terms(model, path):
field=field,
translated=translated,
target=target,
many=not direct
)

# Onto next iteration
Expand All @@ -87,7 +88,8 @@ def query_terms(model, path):
model=model,
field=None,
translated=None,
target=None
target=None,
many=False
)


Expand Down

0 comments on commit 733e692

Please sign in to comment.