mirror of
https://github.com/Hopiu/django-model-utils.git
synced 2026-04-16 09:20:59 +00:00
commit
501896d75f
3 changed files with 53 additions and 36 deletions
|
|
@ -35,10 +35,6 @@ script:
|
|||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- env: TOXENV=py27-django18
|
||||
- env: TOXENV=py32-django18
|
||||
- env: TOXENV=py33-django18
|
||||
- env: TOXENV=py34-django18
|
||||
- env: TOXENV=py27-django_trunk
|
||||
- env: TOXENV=py32-django_trunk
|
||||
- env: TOXENV=py33-django_trunk
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ from django.core.exceptions import ObjectDoesNotExist
|
|||
try:
|
||||
from django.db.models.constants import LOOKUP_SEP
|
||||
from django.utils.six import string_types
|
||||
except ImportError: # Django < 1.5
|
||||
except ImportError: # Django < 1.5
|
||||
from django.db.models.sql.constants import LOOKUP_SEP
|
||||
string_types = (basestring,)
|
||||
|
||||
|
|
@ -53,20 +53,18 @@ class InheritanceQuerySetMixin(object):
|
|||
new_qs.subclasses = subclasses
|
||||
return new_qs
|
||||
|
||||
|
||||
def _clone(self, klass=None, setup=False, **kwargs):
|
||||
for name in ['subclasses', '_annotated']:
|
||||
if hasattr(self, name):
|
||||
kwargs[name] = getattr(self, name)
|
||||
return super(InheritanceQuerySetMixin, self)._clone(klass, setup, **kwargs)
|
||||
|
||||
return super(InheritanceQuerySetMixin, self)._clone(
|
||||
klass, setup, **kwargs)
|
||||
|
||||
def annotate(self, *args, **kwargs):
|
||||
qset = super(InheritanceQuerySetMixin, self).annotate(*args, **kwargs)
|
||||
qset._annotated = [a.default_alias for a in args] + list(kwargs.keys())
|
||||
return qset
|
||||
|
||||
|
||||
def iterator(self):
|
||||
iter = super(InheritanceQuerySetMixin, self).iterator()
|
||||
if getattr(self, 'subclasses', False):
|
||||
|
|
@ -95,7 +93,6 @@ class InheritanceQuerySetMixin(object):
|
|||
for obj in iter:
|
||||
yield obj
|
||||
|
||||
|
||||
def _get_subclasses_recurse(self, model, levels=None):
|
||||
"""
|
||||
Given a Model class, find all related objects, exploring children
|
||||
|
|
@ -115,11 +112,11 @@ class InheritanceQuerySetMixin(object):
|
|||
if levels or levels is None:
|
||||
for subclass in self._get_subclasses_recurse(
|
||||
rel.field.model, levels=levels):
|
||||
subclasses.append(rel.get_accessor_name() + LOOKUP_SEP + subclass)
|
||||
subclasses.append(
|
||||
rel.get_accessor_name() + LOOKUP_SEP + subclass)
|
||||
subclasses.append(rel.get_accessor_name())
|
||||
return subclasses
|
||||
|
||||
|
||||
def _get_ancestors_path(self, model, levels=None):
|
||||
"""
|
||||
Serves as an opposite to _get_subclasses_recurse, instead walking from
|
||||
|
|
@ -127,23 +124,27 @@ class InheritanceQuerySetMixin(object):
|
|||
select_related string backwards.
|
||||
"""
|
||||
if not issubclass(model, self.model):
|
||||
raise ValueError("%r is not a subclass of %r" % (model, self.model))
|
||||
raise ValueError(
|
||||
"%r is not a subclass of %r" % (model, self.model))
|
||||
|
||||
ancestry = []
|
||||
# should be a OneToOneField or None
|
||||
parent = model._meta.get_ancestor_link(self.model)
|
||||
parent_link = model._meta.get_ancestor_link(self.model)
|
||||
if levels:
|
||||
levels -= 1
|
||||
while parent is not None:
|
||||
ancestry.insert(0, parent.related.get_accessor_name())
|
||||
while parent_link is not None:
|
||||
ancestry.insert(0, parent_link.related.get_accessor_name())
|
||||
if levels or levels is None:
|
||||
parent = parent.related.parent_model._meta.get_ancestor_link(
|
||||
if django.VERSION < (1, 8):
|
||||
parent_model = parent_link.related.parent_model
|
||||
else:
|
||||
parent_model = parent_link.related.model
|
||||
parent_link = parent_model._meta.get_ancestor_link(
|
||||
self.model)
|
||||
else:
|
||||
parent = None
|
||||
parent_link = None
|
||||
return LOOKUP_SEP.join(ancestry)
|
||||
|
||||
|
||||
def _get_sub_obj_recurse(self, obj, s):
|
||||
rel, _, s = s.partition(LOOKUP_SEP)
|
||||
try:
|
||||
|
|
@ -170,6 +171,7 @@ class InheritanceQuerySetMixin(object):
|
|||
levels = 1
|
||||
return levels
|
||||
|
||||
|
||||
class InheritanceManagerMixin(object):
|
||||
use_for_related_fields = True
|
||||
|
||||
|
|
@ -188,6 +190,7 @@ class InheritanceManagerMixin(object):
|
|||
class InheritanceQuerySet(InheritanceQuerySetMixin, QuerySet):
|
||||
pass
|
||||
|
||||
|
||||
class InheritanceManager(InheritanceManagerMixin, models.Manager):
|
||||
pass
|
||||
|
||||
|
|
@ -271,7 +274,8 @@ class PassThroughManagerMixin(object):
|
|||
|
||||
@classmethod
|
||||
def for_queryset_class(cls, queryset_cls):
|
||||
return create_pass_through_manager_for_queryset_class(cls, queryset_cls)
|
||||
return create_pass_through_manager_for_queryset_class(
|
||||
cls, queryset_cls)
|
||||
|
||||
|
||||
class PassThroughManager(PassThroughManagerMixin, models.Manager):
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ class TimeFramedModel(models.Model):
|
|||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class StatusModel(models.Model):
|
||||
"""
|
||||
An abstract base class model with a ``status`` field that
|
||||
|
|
@ -51,6 +52,7 @@ class StatusModel(models.Model):
|
|||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
def add_status_query_managers(sender, **kwargs):
|
||||
"""
|
||||
Add a Querymanager for each status item dynamically.
|
||||
|
|
@ -59,16 +61,15 @@ def add_status_query_managers(sender, **kwargs):
|
|||
if not issubclass(sender, StatusModel):
|
||||
return
|
||||
for value, display in getattr(sender, 'STATUS', ()):
|
||||
try:
|
||||
sender._meta.get_field(value)
|
||||
raise ImproperlyConfigured("StatusModel: Model '%s' has a field "
|
||||
"named '%s' which conflicts with a "
|
||||
"status of the same name."
|
||||
% (sender.__name__, value))
|
||||
except FieldDoesNotExist:
|
||||
pass
|
||||
if _field_exists(sender, value):
|
||||
raise ImproperlyConfigured(
|
||||
"StatusModel: Model '%s' has a field named '%s' which "
|
||||
"conflicts with a status of the same name."
|
||||
% (sender.__name__, value)
|
||||
)
|
||||
sender.add_to_class(value, QueryManager(status=value))
|
||||
|
||||
|
||||
def add_timeframed_query_manager(sender, **kwargs):
|
||||
"""
|
||||
Add a QueryManager for a specific timeframe.
|
||||
|
|
@ -76,14 +77,12 @@ def add_timeframed_query_manager(sender, **kwargs):
|
|||
"""
|
||||
if not issubclass(sender, TimeFramedModel):
|
||||
return
|
||||
try:
|
||||
sender._meta.get_field('timeframed')
|
||||
raise ImproperlyConfigured("Model '%s' has a field named "
|
||||
"'timeframed' which conflicts with "
|
||||
"the TimeFramedModel manager."
|
||||
% sender.__name__)
|
||||
except FieldDoesNotExist:
|
||||
pass
|
||||
if _field_exists(sender, 'timeframed'):
|
||||
raise ImproperlyConfigured(
|
||||
"Model '%s' has a field named 'timeframed' "
|
||||
"which conflicts with the TimeFramedModel manager."
|
||||
% sender.__name__
|
||||
)
|
||||
sender.add_to_class('timeframed', QueryManager(
|
||||
(models.Q(start__lte=now) | models.Q(start__isnull=True)) &
|
||||
(models.Q(end__gte=now) | models.Q(end__isnull=True))
|
||||
|
|
@ -92,3 +91,21 @@ def add_timeframed_query_manager(sender, **kwargs):
|
|||
|
||||
models.signals.class_prepared.connect(add_status_query_managers)
|
||||
models.signals.class_prepared.connect(add_timeframed_query_manager)
|
||||
|
||||
|
||||
def _field_exists(model_class, field_name):
|
||||
if hasattr(model_class._meta, '_get_fields'):
|
||||
# Django 1.8+
|
||||
field_exists = bool([
|
||||
f for f in model_class._meta._get_fields(reverse=False)
|
||||
if f.name == field_name
|
||||
])
|
||||
else:
|
||||
# Django 1.7 and previous
|
||||
try:
|
||||
model_class._meta.get_field(field_name)
|
||||
except FieldDoesNotExist:
|
||||
field_exists = False
|
||||
else:
|
||||
field_exists = True
|
||||
return field_exists
|
||||
|
|
|
|||
Loading…
Reference in a new issue