mirror of
https://github.com/ansible/awx.git
synced 2026-01-13 19:10:07 -03:30
AC-1040 Add django-polymorphic as prerequisite for moving toward unified jobs.
This commit is contained in:
parent
61ab197b3a
commit
e957de2016
@ -18,6 +18,7 @@ django-auth-ldap==1.1.7 (django_auth_ldap/*)
|
||||
django-celery==3.1.1 (djcelery/*)
|
||||
django-extensions==1.2.5 (django_extensions/*)
|
||||
django-jsonfield==0.9.12 (jsonfield/*, minor fix in jsonfield/fields.py)
|
||||
django-polymorphic==0.5.3 (polymorphic/*)
|
||||
django-split-settings==0.1.1 (split_settings/*)
|
||||
django-taggit==0.11.2 (taggit/*)
|
||||
djangorestframework==2.3.10 (rest_framework/*)
|
||||
|
||||
46
awx/lib/site-packages/polymorphic/__init__.py
Normal file
46
awx/lib/site-packages/polymorphic/__init__.py
Normal file
@ -0,0 +1,46 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Seamless Polymorphic Inheritance for Django Models
|
||||
|
||||
Copyright:
|
||||
This code and affiliated files are (C) by Bert Constantin and individual contributors.
|
||||
Please see LICENSE and AUTHORS for more information.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
import django
|
||||
from .polymorphic_model import PolymorphicModel
|
||||
from .manager import PolymorphicManager
|
||||
from .query import PolymorphicQuerySet
|
||||
from .query_translate import translate_polymorphic_Q_object
|
||||
from .showfields import ShowFieldContent, ShowFieldType, ShowFieldTypeAndContent
|
||||
from .showfields import ShowFields, ShowFieldTypes, ShowFieldsAndTypes # import old names for compatibility
|
||||
|
||||
|
||||
# Monkey-patch Django < 1.5 to allow ContentTypes for proxy models.
|
||||
if django.VERSION[:2] < (1, 5):
|
||||
from django.contrib.contenttypes.models import ContentTypeManager
|
||||
from django.utils.encoding import smart_text
|
||||
|
||||
def get_for_model(self, model, for_concrete_model=True):
|
||||
if for_concrete_model:
|
||||
model = model._meta.concrete_model
|
||||
elif model._deferred:
|
||||
model = model._meta.proxy_for_model
|
||||
|
||||
opts = model._meta
|
||||
|
||||
try:
|
||||
ct = self._get_from_cache(opts)
|
||||
except KeyError:
|
||||
ct, created = self.get_or_create(
|
||||
app_label = opts.app_label,
|
||||
model = opts.object_name.lower(),
|
||||
defaults = {'name': smart_text(opts.verbose_name_raw)},
|
||||
)
|
||||
self._add_to_cache(self.db, ct)
|
||||
|
||||
return ct
|
||||
|
||||
ContentTypeManager.get_for_model__original = ContentTypeManager.get_for_model
|
||||
ContentTypeManager.get_for_model = get_for_model
|
||||
|
||||
14
awx/lib/site-packages/polymorphic/__version__.py
Normal file
14
awx/lib/site-packages/polymorphic/__version__.py
Normal file
@ -0,0 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
See PEP 386 (https://www.python.org/dev/peps/pep-0386/)
|
||||
|
||||
Release logic:
|
||||
1. Remove "dev#" from current (this file, now AND setup.py!).
|
||||
2. git commit
|
||||
3. git tag <version>
|
||||
4. push to pypi + push --tags to github
|
||||
5. bump the version, append ".dev0"
|
||||
6. git commit
|
||||
7. push to github
|
||||
"""
|
||||
__version__ = "0.5.3"
|
||||
507
awx/lib/site-packages/polymorphic/admin.py
Normal file
507
awx/lib/site-packages/polymorphic/admin.py
Normal file
@ -0,0 +1,507 @@
|
||||
"""
|
||||
ModelAdmin code to display polymorphic models.
|
||||
"""
|
||||
from django import forms
|
||||
from django.conf.urls import patterns, url
|
||||
from django.contrib import admin
|
||||
from django.contrib.admin.helpers import AdminForm, AdminErrorList
|
||||
from django.contrib.admin.sites import AdminSite
|
||||
from django.contrib.admin.widgets import AdminRadioSelect
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.core.urlresolvers import RegexURLResolver
|
||||
from django.http import Http404, HttpResponseRedirect
|
||||
from django.shortcuts import render_to_response
|
||||
from django.template.context import RequestContext
|
||||
from django.utils import six
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
__all__ = (
|
||||
'PolymorphicModelChoiceForm', 'PolymorphicParentModelAdmin',
|
||||
'PolymorphicChildModelAdmin', 'PolymorphicChildModelFilter'
|
||||
)
|
||||
|
||||
|
||||
class RegistrationClosed(RuntimeError):
|
||||
"The admin model can't be registered anymore at this point."
|
||||
pass
|
||||
|
||||
class ChildAdminNotRegistered(RuntimeError):
|
||||
"The admin site for the model is not registered."
|
||||
pass
|
||||
|
||||
|
||||
class PolymorphicModelChoiceForm(forms.Form):
|
||||
"""
|
||||
The default form for the ``add_type_form``. Can be overwritten and replaced.
|
||||
"""
|
||||
#: Define the label for the radiofield
|
||||
type_label = _("Type")
|
||||
|
||||
ct_id = forms.ChoiceField(label=type_label, widget=AdminRadioSelect(attrs={'class': 'radiolist'}))
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# Allow to easily redefine the label (a commonly expected usecase)
|
||||
super(PolymorphicModelChoiceForm, self).__init__(*args, **kwargs)
|
||||
self.fields['ct_id'].label = self.type_label
|
||||
|
||||
|
||||
class PolymorphicChildModelFilter(admin.SimpleListFilter):
|
||||
"""
|
||||
An admin list filter for the PolymorphicParentModelAdmin which enables
|
||||
filtering by its child models.
|
||||
"""
|
||||
title = _('Content type')
|
||||
parameter_name = 'polymorphic_ctype'
|
||||
|
||||
def lookups(self, request, model_admin):
|
||||
return model_admin.get_child_type_choices()
|
||||
|
||||
def queryset(self, request, queryset):
|
||||
try:
|
||||
value = int(self.value())
|
||||
except TypeError:
|
||||
value = None
|
||||
if value:
|
||||
# ensure the content type is allowed
|
||||
for choice_value, _ in self.lookup_choices:
|
||||
if choice_value == value:
|
||||
return queryset.filter(polymorphic_ctype_id=choice_value)
|
||||
raise PermissionDenied(
|
||||
'Invalid ContentType "{0}". It must be registered as child model.'.format(value))
|
||||
return queryset
|
||||
|
||||
|
||||
class PolymorphicParentModelAdmin(admin.ModelAdmin):
|
||||
"""
|
||||
A admin interface that can displays different change/delete pages, depending on the polymorphic model.
|
||||
To use this class, two variables need to be defined:
|
||||
|
||||
* :attr:`base_model` should
|
||||
* :attr:`child_models` should be a list of (Model, Admin) tuples
|
||||
|
||||
Alternatively, the following methods can be implemented:
|
||||
|
||||
* :func:`get_child_models` should return a list of (Model, ModelAdmin) tuples
|
||||
* optionally, :func:`get_child_type_choices` can be overwritten to refine the choices for the add dialog.
|
||||
|
||||
This class needs to be inherited by the model admin base class that is registered in the site.
|
||||
The derived models should *not* register the ModelAdmin, but instead it should be returned by :func:`get_child_models`.
|
||||
"""
|
||||
|
||||
#: The base model that the class uses
|
||||
base_model = None
|
||||
|
||||
#: The child models that should be displayed
|
||||
child_models = None
|
||||
|
||||
#: Whether the list should be polymorphic too, leave to ``False`` to optimize
|
||||
polymorphic_list = False
|
||||
|
||||
add_type_template = None
|
||||
add_type_form = PolymorphicModelChoiceForm
|
||||
|
||||
|
||||
def __init__(self, model, admin_site, *args, **kwargs):
|
||||
super(PolymorphicParentModelAdmin, self).__init__(model, admin_site, *args, **kwargs)
|
||||
self._child_admin_site = AdminSite(name=self.admin_site.name)
|
||||
self._is_setup = False
|
||||
|
||||
|
||||
def _lazy_setup(self):
|
||||
if self._is_setup:
|
||||
return
|
||||
|
||||
# By not having this in __init__() there is less stress on import dependencies as well,
|
||||
# considering an advanced use cases where a plugin system scans for the child models.
|
||||
child_models = self.get_child_models()
|
||||
for Model, Admin in child_models:
|
||||
self.register_child(Model, Admin)
|
||||
self._child_models = dict(child_models)
|
||||
|
||||
# This is needed to deal with the improved ForeignKeyRawIdWidget in Django 1.4 and perhaps other widgets too.
|
||||
# The ForeignKeyRawIdWidget checks whether the referenced model is registered in the admin, otherwise it displays itself as a textfield.
|
||||
# As simple solution, just make sure all parent admin models are also know in the child admin site.
|
||||
# This should be done after all parent models are registered off course.
|
||||
complete_registry = self.admin_site._registry.copy()
|
||||
complete_registry.update(self._child_admin_site._registry)
|
||||
|
||||
self._child_admin_site._registry = complete_registry
|
||||
self._is_setup = True
|
||||
|
||||
|
||||
def register_child(self, model, model_admin):
|
||||
"""
|
||||
Register a model with admin to display.
|
||||
"""
|
||||
# After the get_urls() is called, the URLs of the child model can't be exposed anymore to the Django URLconf,
|
||||
# which also means that a "Save and continue editing" button won't work.
|
||||
if self._is_setup:
|
||||
raise RegistrationClosed("The admin model can't be registered anymore at this point.")
|
||||
|
||||
if not issubclass(model, self.base_model):
|
||||
raise TypeError("{0} should be a subclass of {1}".format(model.__name__, self.base_model.__name__))
|
||||
if not issubclass(model_admin, admin.ModelAdmin):
|
||||
raise TypeError("{0} should be a subclass of {1}".format(model_admin.__name__, admin.ModelAdmin.__name__))
|
||||
|
||||
self._child_admin_site.register(model, model_admin)
|
||||
|
||||
|
||||
def get_child_models(self):
|
||||
"""
|
||||
Return the derived model classes which this admin should handle.
|
||||
This should return a list of tuples, exactly like :attr:`child_models` is.
|
||||
|
||||
The model classes can be retrieved as ``base_model.__subclasses__()``,
|
||||
a setting in a config file, or a query of a plugin registration system at your option
|
||||
"""
|
||||
if self.child_models is None:
|
||||
raise NotImplementedError("Implement get_child_models() or child_models")
|
||||
|
||||
return self.child_models
|
||||
|
||||
|
||||
def get_child_type_choices(self):
|
||||
"""
|
||||
Return a list of polymorphic types which can be added.
|
||||
"""
|
||||
choices = []
|
||||
for model, _ in self.get_child_models():
|
||||
ct = ContentType.objects.get_for_model(model, for_concrete_model=False)
|
||||
choices.append((ct.id, model._meta.verbose_name))
|
||||
return choices
|
||||
|
||||
|
||||
def _get_real_admin(self, object_id):
|
||||
obj = self.model.objects.non_polymorphic().values('polymorphic_ctype').get(pk=object_id)
|
||||
return self._get_real_admin_by_ct(obj['polymorphic_ctype'])
|
||||
|
||||
|
||||
def _get_real_admin_by_ct(self, ct_id):
|
||||
try:
|
||||
ct = ContentType.objects.get_for_id(ct_id)
|
||||
except ContentType.DoesNotExist as e:
|
||||
raise Http404(e) # Handle invalid GET parameters
|
||||
|
||||
model_class = ct.model_class()
|
||||
if not model_class:
|
||||
raise Http404("No model found for '{0}.{1}'.".format(*ct.natural_key())) # Handle model deletion
|
||||
|
||||
return self._get_real_admin_by_model(model_class)
|
||||
|
||||
|
||||
def _get_real_admin_by_model(self, model_class):
|
||||
# In case of a ?ct_id=### parameter, the view is already checked for permissions.
|
||||
# Hence, make sure this is a derived object, or risk exposing other admin interfaces.
|
||||
if model_class not in self._child_models:
|
||||
raise PermissionDenied("Invalid model '{0}', it must be registered as child model.".format(model_class))
|
||||
|
||||
try:
|
||||
# HACK: the only way to get the instance of an model admin,
|
||||
# is to read the registry of the AdminSite.
|
||||
return self._child_admin_site._registry[model_class]
|
||||
except KeyError:
|
||||
raise ChildAdminNotRegistered("No child admin site was registered for a '{0}' model.".format(model_class))
|
||||
|
||||
|
||||
def queryset(self, request):
|
||||
# optimize the list display.
|
||||
qs = super(PolymorphicParentModelAdmin, self).queryset(request)
|
||||
if not self.polymorphic_list:
|
||||
qs = qs.non_polymorphic()
|
||||
return qs
|
||||
|
||||
|
||||
def add_view(self, request, form_url='', extra_context=None):
|
||||
"""Redirect the add view to the real admin."""
|
||||
ct_id = int(request.GET.get('ct_id', 0))
|
||||
if not ct_id:
|
||||
# Display choices
|
||||
return self.add_type_view(request)
|
||||
else:
|
||||
real_admin = self._get_real_admin_by_ct(ct_id)
|
||||
return real_admin.add_view(request, form_url, extra_context)
|
||||
|
||||
|
||||
def change_view(self, request, object_id, *args, **kwargs):
|
||||
"""Redirect the change view to the real admin."""
|
||||
# between Django 1.3 and 1.4 this method signature differs. Hence the *args, **kwargs
|
||||
real_admin = self._get_real_admin(object_id)
|
||||
return real_admin.change_view(request, object_id, *args, **kwargs)
|
||||
|
||||
|
||||
def delete_view(self, request, object_id, extra_context=None):
|
||||
"""Redirect the delete view to the real admin."""
|
||||
real_admin = self._get_real_admin(object_id)
|
||||
return real_admin.delete_view(request, object_id, extra_context)
|
||||
|
||||
|
||||
def get_urls(self):
|
||||
"""
|
||||
Expose the custom URLs for the subclasses and the URL resolver.
|
||||
"""
|
||||
urls = super(PolymorphicParentModelAdmin, self).get_urls()
|
||||
info = self.model._meta.app_label, self.model._meta.module_name
|
||||
|
||||
# Patch the change URL so it's not a big catch-all; allowing all custom URLs to be added to the end.
|
||||
# The url needs to be recreated, patching url.regex is not an option Django 1.4's LocaleRegexProvider changed it.
|
||||
new_change_url = url(r'^(\d+)/$', self.admin_site.admin_view(self.change_view), name='{0}_{1}_change'.format(*info))
|
||||
for i, oldurl in enumerate(urls):
|
||||
if oldurl.name == new_change_url.name:
|
||||
urls[i] = new_change_url
|
||||
|
||||
# Define the catch-all for custom views
|
||||
custom_urls = patterns('',
|
||||
url(r'^(?P<path>.+)$', self.admin_site.admin_view(self.subclass_view))
|
||||
)
|
||||
|
||||
# At this point. all admin code needs to be known.
|
||||
self._lazy_setup()
|
||||
|
||||
# Add reverse names for all polymorphic models, so the delete button and "save and add" just work.
|
||||
# These definitions are masked by the definition above, since it needs special handling (and a ct_id parameter).
|
||||
dummy_urls = []
|
||||
for model, _ in self.get_child_models():
|
||||
admin = self._get_real_admin_by_model(model)
|
||||
dummy_urls += admin.get_urls()
|
||||
|
||||
return urls + custom_urls + dummy_urls
|
||||
|
||||
|
||||
def subclass_view(self, request, path):
|
||||
"""
|
||||
Forward any request to a custom view of the real admin.
|
||||
"""
|
||||
ct_id = int(request.GET.get('ct_id', 0))
|
||||
if not ct_id:
|
||||
# See if the path started with an ID.
|
||||
try:
|
||||
pos = path.find('/')
|
||||
object_id = long(path[0:pos])
|
||||
except ValueError:
|
||||
raise Http404("No ct_id parameter, unable to find admin subclass for path '{0}'.".format(path))
|
||||
|
||||
ct_id = self.model.objects.values_list('polymorphic_ctype_id', flat=True).get(pk=object_id)
|
||||
|
||||
|
||||
real_admin = self._get_real_admin_by_ct(ct_id)
|
||||
resolver = RegexURLResolver('^', real_admin.urls)
|
||||
resolvermatch = resolver.resolve(path) # May raise Resolver404
|
||||
if not resolvermatch:
|
||||
raise Http404("No match for path '{0}' in admin subclass.".format(path))
|
||||
|
||||
return resolvermatch.func(request, *resolvermatch.args, **resolvermatch.kwargs)
|
||||
|
||||
|
||||
def add_type_view(self, request, form_url=''):
|
||||
"""
|
||||
Display a choice form to select which page type to add.
|
||||
"""
|
||||
if not self.has_add_permission(request):
|
||||
raise PermissionDenied
|
||||
|
||||
extra_qs = ''
|
||||
if request.META['QUERY_STRING']:
|
||||
extra_qs = '&' + request.META['QUERY_STRING']
|
||||
|
||||
choices = self.get_child_type_choices()
|
||||
if len(choices) == 1:
|
||||
return HttpResponseRedirect('?ct_id={0}{1}'.format(choices[0][0], extra_qs))
|
||||
|
||||
# Create form
|
||||
form = self.add_type_form(
|
||||
data=request.POST if request.method == 'POST' else None,
|
||||
initial={'ct_id': choices[0][0]}
|
||||
)
|
||||
form.fields['ct_id'].choices = choices
|
||||
|
||||
if form.is_valid():
|
||||
return HttpResponseRedirect('?ct_id={0}{1}'.format(form.cleaned_data['ct_id'], extra_qs))
|
||||
|
||||
# Wrap in all admin layout
|
||||
fieldsets = ((None, {'fields': ('ct_id',)}),)
|
||||
adminForm = AdminForm(form, fieldsets, {}, model_admin=self)
|
||||
media = self.media + adminForm.media
|
||||
opts = self.model._meta
|
||||
|
||||
context = {
|
||||
'title': _('Add %s') % force_text(opts.verbose_name),
|
||||
'adminform': adminForm,
|
||||
'is_popup': "_popup" in request.REQUEST,
|
||||
'media': mark_safe(media),
|
||||
'errors': AdminErrorList(form, ()),
|
||||
'app_label': opts.app_label,
|
||||
}
|
||||
return self.render_add_type_form(request, context, form_url)
|
||||
|
||||
|
||||
def render_add_type_form(self, request, context, form_url=''):
|
||||
"""
|
||||
Render the page type choice form.
|
||||
"""
|
||||
opts = self.model._meta
|
||||
app_label = opts.app_label
|
||||
context.update({
|
||||
'has_change_permission': self.has_change_permission(request),
|
||||
'form_url': mark_safe(form_url),
|
||||
'opts': opts,
|
||||
'add': True,
|
||||
'save_on_top': self.save_on_top,
|
||||
})
|
||||
if hasattr(self.admin_site, 'root_path'):
|
||||
context['root_path'] = self.admin_site.root_path # Django < 1.4
|
||||
context_instance = RequestContext(request, current_app=self.admin_site.name)
|
||||
return render_to_response(self.add_type_template or [
|
||||
"admin/%s/%s/add_type_form.html" % (app_label, opts.object_name.lower()),
|
||||
"admin/%s/add_type_form.html" % app_label,
|
||||
"admin/polymorphic/add_type_form.html", # added default here
|
||||
"admin/add_type_form.html"
|
||||
], context, context_instance=context_instance)
|
||||
|
||||
|
||||
@property
|
||||
def change_list_template(self):
|
||||
opts = self.model._meta
|
||||
app_label = opts.app_label
|
||||
|
||||
# Pass the base options
|
||||
base_opts = self.base_model._meta
|
||||
base_app_label = base_opts.app_label
|
||||
|
||||
return [
|
||||
"admin/%s/%s/change_list.html" % (app_label, opts.object_name.lower()),
|
||||
"admin/%s/change_list.html" % app_label,
|
||||
# Added base class:
|
||||
"admin/%s/%s/change_list.html" % (base_app_label, base_opts.object_name.lower()),
|
||||
"admin/%s/change_list.html" % base_app_label,
|
||||
"admin/change_list.html"
|
||||
]
|
||||
|
||||
|
||||
|
||||
class PolymorphicChildModelAdmin(admin.ModelAdmin):
|
||||
"""
|
||||
The *optional* base class for the admin interface of derived models.
|
||||
|
||||
This base class defines some convenience behavior for the admin interface:
|
||||
|
||||
* It corrects the breadcrumbs in the admin pages.
|
||||
* It adds the base model to the template lookup paths.
|
||||
* It allows to set ``base_form`` so the derived class will automatically include other fields in the form.
|
||||
* It allows to set ``base_fieldsets`` so the derived class will automatically display any extra fields.
|
||||
|
||||
The ``base_model`` attribute must be set.
|
||||
"""
|
||||
base_model = None
|
||||
base_form = None
|
||||
base_fieldsets = None
|
||||
extra_fieldset_title = _("Contents") # Default title for extra fieldset
|
||||
|
||||
|
||||
def get_form(self, request, obj=None, **kwargs):
|
||||
# The django admin validation requires the form to have a 'class Meta: model = ..'
|
||||
# attribute, or it will complain that the fields are missing.
|
||||
# However, this enforces all derived ModelAdmin classes to redefine the model as well,
|
||||
# because they need to explicitly set the model again - it will stick with the base model.
|
||||
#
|
||||
# Instead, pass the form unchecked here, because the standard ModelForm will just work.
|
||||
# If the derived class sets the model explicitly, respect that setting.
|
||||
kwargs.setdefault('form', self.base_form or self.form)
|
||||
return super(PolymorphicChildModelAdmin, self).get_form(request, obj, **kwargs)
|
||||
|
||||
|
||||
@property
|
||||
def change_form_template(self):
|
||||
opts = self.model._meta
|
||||
app_label = opts.app_label
|
||||
|
||||
# Pass the base options
|
||||
base_opts = self.base_model._meta
|
||||
base_app_label = base_opts.app_label
|
||||
|
||||
return [
|
||||
"admin/%s/%s/change_form.html" % (app_label, opts.object_name.lower()),
|
||||
"admin/%s/change_form.html" % app_label,
|
||||
# Added:
|
||||
"admin/%s/%s/change_form.html" % (base_app_label, base_opts.object_name.lower()),
|
||||
"admin/%s/change_form.html" % base_app_label,
|
||||
"admin/polymorphic/change_form.html",
|
||||
"admin/change_form.html"
|
||||
]
|
||||
|
||||
|
||||
@property
|
||||
def delete_confirmation_template(self):
|
||||
opts = self.model._meta
|
||||
app_label = opts.app_label
|
||||
|
||||
# Pass the base options
|
||||
base_opts = self.base_model._meta
|
||||
base_app_label = base_opts.app_label
|
||||
|
||||
return [
|
||||
"admin/%s/%s/delete_confirmation.html" % (app_label, opts.object_name.lower()),
|
||||
"admin/%s/delete_confirmation.html" % app_label,
|
||||
# Added:
|
||||
"admin/%s/%s/delete_confirmation.html" % (base_app_label, base_opts.object_name.lower()),
|
||||
"admin/%s/delete_confirmation.html" % base_app_label,
|
||||
"admin/polymorphic/delete_confirmation.html",
|
||||
"admin/delete_confirmation.html"
|
||||
]
|
||||
|
||||
|
||||
def render_change_form(self, request, context, add=False, change=False, form_url='', obj=None):
|
||||
context.update({
|
||||
'base_opts': self.base_model._meta,
|
||||
})
|
||||
return super(PolymorphicChildModelAdmin, self).render_change_form(request, context, add=add, change=change, form_url=form_url, obj=obj)
|
||||
|
||||
|
||||
def delete_view(self, request, object_id, context=None):
|
||||
extra_context = {
|
||||
'base_opts': self.base_model._meta,
|
||||
}
|
||||
return super(PolymorphicChildModelAdmin, self).delete_view(request, object_id, extra_context)
|
||||
|
||||
|
||||
# ---- Extra: improving the form/fieldset default display ----
|
||||
|
||||
def get_fieldsets(self, request, obj=None):
|
||||
# If subclass declares fieldsets, this is respected
|
||||
if self.declared_fieldsets or not self.base_fieldsets:
|
||||
return super(PolymorphicChildModelAdmin, self).get_fieldsets(request, obj)
|
||||
|
||||
# Have a reasonable default fieldsets,
|
||||
# where the subclass fields are automatically included.
|
||||
other_fields = self.get_subclass_fields(request, obj)
|
||||
|
||||
if other_fields:
|
||||
return (
|
||||
self.base_fieldsets[0],
|
||||
(self.extra_fieldset_title, {'fields': other_fields}),
|
||||
) + self.base_fieldsets[1:]
|
||||
else:
|
||||
return self.base_fieldsets
|
||||
|
||||
|
||||
def get_subclass_fields(self, request, obj=None):
|
||||
# Find out how many fields would really be on the form,
|
||||
# if it weren't restricted by declared fields.
|
||||
exclude = list(self.exclude or [])
|
||||
exclude.extend(self.get_readonly_fields(request, obj))
|
||||
|
||||
# By not declaring the fields/form in the base class,
|
||||
# get_form() will populate the form with all available fields.
|
||||
form = self.get_form(request, obj, exclude=exclude)
|
||||
subclass_fields = list(six.iterkeys(form.base_fields)) + list(self.get_readonly_fields(request, obj))
|
||||
|
||||
# Find which fields are not part of the common fields.
|
||||
for fieldset in self.base_fieldsets:
|
||||
for field in fieldset[1]['fields']:
|
||||
try:
|
||||
subclass_fields.remove(field)
|
||||
except ValueError:
|
||||
pass # field not found in form, Django will raise exception later.
|
||||
return subclass_fields
|
||||
243
awx/lib/site-packages/polymorphic/base.py
Normal file
243
awx/lib/site-packages/polymorphic/base.py
Normal file
@ -0,0 +1,243 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
""" PolymorphicModel Meta Class
|
||||
Please see README.rst or DOCS.rst or http://chrisglass.github.com/django_polymorphic/
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import sys
|
||||
import inspect
|
||||
|
||||
from django.db import models
|
||||
from django.db.models.base import ModelBase
|
||||
from django.db.models.manager import ManagerDescriptor
|
||||
|
||||
from .manager import PolymorphicManager
|
||||
from .query import PolymorphicQuerySet
|
||||
|
||||
# PolymorphicQuerySet Q objects (and filter()) support these additional key words.
|
||||
# These are forbidden as field names (a descriptive exception is raised)
|
||||
POLYMORPHIC_SPECIAL_Q_KWORDS = ['instance_of', 'not_instance_of']
|
||||
|
||||
try:
|
||||
from django.db.models.manager import AbstractManagerDescriptor # Django 1.5
|
||||
except ImportError:
|
||||
AbstractManagerDescriptor = None
|
||||
|
||||
|
||||
###################################################################################
|
||||
### PolymorphicModel meta class
|
||||
|
||||
class PolymorphicModelBase(ModelBase):
|
||||
"""
|
||||
Manager inheritance is a pretty complex topic which may need
|
||||
more thought regarding how this should be handled for polymorphic
|
||||
models.
|
||||
|
||||
In any case, we probably should propagate 'objects' and 'base_objects'
|
||||
from PolymorphicModel to every subclass. We also want to somehow
|
||||
inherit/propagate _default_manager as well, as it needs to be polymorphic.
|
||||
|
||||
The current implementation below is an experiment to solve this
|
||||
problem with a very simplistic approach: We unconditionally
|
||||
inherit/propagate any and all managers (using _copy_to_model),
|
||||
as long as they are defined on polymorphic models
|
||||
(the others are left alone).
|
||||
|
||||
Like Django ModelBase, we special-case _default_manager:
|
||||
if there are any user-defined managers, it is set to the first of these.
|
||||
|
||||
We also require that _default_manager as well as any user defined
|
||||
polymorphic managers produce querysets that are derived from
|
||||
PolymorphicQuerySet.
|
||||
"""
|
||||
|
||||
def __new__(self, model_name, bases, attrs):
|
||||
#print; print '###', model_name, '- bases:', bases
|
||||
|
||||
# Workaround compatibility issue with six.with_metaclass() and custom Django model metaclasses:
|
||||
# Let Django fully ignore the class which is inserted in between.
|
||||
if not attrs and model_name == 'NewBase':
|
||||
attrs['__module__'] = 'django.utils.six'
|
||||
attrs['Meta'] = type('Meta', (), {'abstract': True})
|
||||
return super(PolymorphicModelBase, self).__new__(self, model_name, bases, attrs)
|
||||
|
||||
# create new model
|
||||
new_class = self.call_superclass_new_method(model_name, bases, attrs)
|
||||
|
||||
# check if the model fields are all allowed
|
||||
self.validate_model_fields(new_class)
|
||||
|
||||
# create list of all managers to be inherited from the base classes
|
||||
inherited_managers = new_class.get_inherited_managers(attrs)
|
||||
|
||||
# add the managers to the new model
|
||||
for source_name, mgr_name, manager in inherited_managers:
|
||||
#print '** add inherited manager from model %s, manager %s, %s' % (source_name, mgr_name, manager.__class__.__name__)
|
||||
new_manager = manager._copy_to_model(new_class)
|
||||
new_class.add_to_class(mgr_name, new_manager)
|
||||
|
||||
# get first user defined manager; if there is one, make it the _default_manager
|
||||
# this value is used by the related objects, restoring access to custom queryset methods on related objects.
|
||||
user_manager = self.get_first_user_defined_manager(new_class)
|
||||
if user_manager:
|
||||
def_mgr = user_manager._copy_to_model(new_class)
|
||||
#print '## add default manager', type(def_mgr)
|
||||
new_class.add_to_class('_default_manager', def_mgr)
|
||||
new_class._default_manager._inherited = False # the default mgr was defined by the user, not inherited
|
||||
|
||||
# validate resulting default manager
|
||||
self.validate_model_manager(new_class._default_manager, model_name, '_default_manager')
|
||||
|
||||
# for __init__ function of this class (monkeypatching inheritance accessors)
|
||||
new_class.polymorphic_super_sub_accessors_replaced = False
|
||||
|
||||
# determine the name of the primary key field and store it into the class variable
|
||||
# polymorphic_primary_key_name (it is needed by query.py)
|
||||
for f in new_class._meta.fields:
|
||||
if f.primary_key and type(f) != models.OneToOneField:
|
||||
new_class.polymorphic_primary_key_name = f.name
|
||||
break
|
||||
|
||||
return new_class
|
||||
|
||||
def get_inherited_managers(self, attrs):
|
||||
"""
|
||||
Return list of all managers to be inherited/propagated from the base classes;
|
||||
use correct mro, only use managers with _inherited==False (they are of no use),
|
||||
skip managers that are overwritten by the user with same-named class attributes (in attrs)
|
||||
"""
|
||||
#print "** ", self.__name__
|
||||
add_managers = []
|
||||
add_managers_keys = set()
|
||||
for base in self.__mro__[1:]:
|
||||
if not issubclass(base, models.Model):
|
||||
continue
|
||||
if not getattr(base, 'polymorphic_model_marker', None):
|
||||
continue # leave managers of non-polym. models alone
|
||||
|
||||
for key, manager in base.__dict__.items():
|
||||
if type(manager) == models.manager.ManagerDescriptor:
|
||||
manager = manager.manager
|
||||
|
||||
if AbstractManagerDescriptor is not None:
|
||||
# Django 1.4 unconditionally assigned managers to a model. As of Django 1.5 however,
|
||||
# the abstract models don't get any managers, only a AbstractManagerDescriptor as substitute.
|
||||
# Pretend that the manager is still there, so all code works like it used to.
|
||||
if type(manager) == AbstractManagerDescriptor and base.__name__ == 'PolymorphicModel':
|
||||
model = manager.model
|
||||
if key == 'objects':
|
||||
manager = PolymorphicManager()
|
||||
manager.model = model
|
||||
elif key == 'base_objects':
|
||||
manager = models.Manager()
|
||||
manager.model = model
|
||||
|
||||
if not isinstance(manager, models.Manager):
|
||||
continue
|
||||
if key == '_base_manager':
|
||||
continue # let Django handle _base_manager
|
||||
if key in attrs:
|
||||
continue
|
||||
if key in add_managers_keys:
|
||||
continue # manager with that name already added, skip
|
||||
if manager._inherited:
|
||||
continue # inherited managers (on the bases) have no significance, they are just copies
|
||||
#print '## {0} {1}'.format(self.__name__, key)
|
||||
|
||||
if isinstance(manager, PolymorphicManager): # validate any inherited polymorphic managers
|
||||
self.validate_model_manager(manager, self.__name__, key)
|
||||
add_managers.append((base.__name__, key, manager))
|
||||
add_managers_keys.add(key)
|
||||
|
||||
# The ordering in the base.__dict__ may randomly change depending on which method is added.
|
||||
# Make sure base_objects is on top, and 'objects' and '_default_manager' follow afterwards.
|
||||
# This makes sure that the _base_manager is also assigned properly.
|
||||
add_managers = sorted(add_managers, key=lambda item: (item[1].startswith('_'), item[1]))
|
||||
return add_managers
|
||||
|
||||
@classmethod
|
||||
def get_first_user_defined_manager(mcs, new_class):
|
||||
# See if there is a manager attribute directly stored at this inheritance level.
|
||||
mgr_list = []
|
||||
for key, val in new_class.__dict__.items():
|
||||
if isinstance(val, ManagerDescriptor):
|
||||
val = val.manager
|
||||
if not isinstance(val, PolymorphicManager) or type(val) is PolymorphicManager:
|
||||
continue
|
||||
|
||||
mgr_list.append((val.creation_counter, key, val))
|
||||
|
||||
# if there are user defined managers, use first one as _default_manager
|
||||
if mgr_list:
|
||||
_, manager_name, manager = sorted(mgr_list)[0]
|
||||
#sys.stderr.write( '\n# first user defined manager for model "{model}":\n# "{mgrname}": {mgr}\n# manager model: {mgrmodel}\n\n'
|
||||
# .format( model=self.__name__, mgrname=manager_name, mgr=manager, mgrmodel=manager.model ) )
|
||||
return manager
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def call_superclass_new_method(self, model_name, bases, attrs):
|
||||
"""call __new__ method of super class and return the newly created class.
|
||||
Also work around a limitation in Django's ModelBase."""
|
||||
# There seems to be a general limitation in Django's app_label handling
|
||||
# regarding abstract models (in ModelBase). See issue 1 on github - TODO: propose patch for Django
|
||||
# We run into this problem if polymorphic.py is located in a top-level directory
|
||||
# which is directly in the python path. To work around this we temporarily set
|
||||
# app_label here for PolymorphicModel.
|
||||
meta = attrs.get('Meta', None)
|
||||
do_app_label_workaround = (meta
|
||||
and attrs['__module__'] == 'polymorphic'
|
||||
and model_name == 'PolymorphicModel'
|
||||
and getattr(meta, 'app_label', None) is None)
|
||||
|
||||
if do_app_label_workaround:
|
||||
meta.app_label = 'poly_dummy_app_label'
|
||||
new_class = super(PolymorphicModelBase, self).__new__(self, model_name, bases, attrs)
|
||||
if do_app_label_workaround:
|
||||
del(meta.app_label)
|
||||
return new_class
|
||||
|
||||
def validate_model_fields(self):
|
||||
"check if all fields names are allowed (i.e. not in POLYMORPHIC_SPECIAL_Q_KWORDS)"
|
||||
for f in self._meta.fields:
|
||||
if f.name in POLYMORPHIC_SPECIAL_Q_KWORDS:
|
||||
e = 'PolymorphicModel: "%s" - field name "%s" is not allowed in polymorphic models'
|
||||
raise AssertionError(e % (self.__name__, f.name))
|
||||
|
||||
@classmethod
|
||||
def validate_model_manager(self, manager, model_name, manager_name):
|
||||
"""check if the manager is derived from PolymorphicManager
|
||||
and its querysets from PolymorphicQuerySet - throw AssertionError if not"""
|
||||
|
||||
if not issubclass(type(manager), PolymorphicManager):
|
||||
e = 'PolymorphicModel: "' + model_name + '.' + manager_name + '" manager is of type "' + type(manager).__name__
|
||||
e += '", but must be a subclass of PolymorphicManager'
|
||||
raise AssertionError(e)
|
||||
if not getattr(manager, 'queryset_class', None) or not issubclass(manager.queryset_class, PolymorphicQuerySet):
|
||||
e = 'PolymorphicModel: "' + model_name + '.' + manager_name + '" (PolymorphicManager) has been instantiated with a queryset class which is'
|
||||
e += ' not a subclass of PolymorphicQuerySet (which is required)'
|
||||
raise AssertionError(e)
|
||||
return manager
|
||||
|
||||
# hack: a small patch to Django would be a better solution.
|
||||
# Django's management command 'dumpdata' relies on non-polymorphic
|
||||
# behaviour of the _default_manager. Therefore, we catch any access to _default_manager
|
||||
# here and return the non-polymorphic default manager instead if we are called from 'dumpdata.py'
|
||||
# (non-polymorphic default manager is 'base_objects' for polymorphic models).
|
||||
# This way we don't need to patch django.core.management.commands.dumpdata
|
||||
# for all supported Django versions.
|
||||
# TODO: investigate Django how this can be avoided
|
||||
_dumpdata_command_running = False
|
||||
if len(sys.argv) > 1:
|
||||
_dumpdata_command_running = (sys.argv[1] == 'dumpdata')
|
||||
|
||||
def __getattribute__(self, name):
|
||||
if name == '_default_manager':
|
||||
if self._dumpdata_command_running:
|
||||
frm = inspect.stack()[1] # frm[1] is caller file name, frm[3] is caller function name
|
||||
if 'django/core/management/commands/dumpdata.py' in frm[1]:
|
||||
return self.base_objects
|
||||
#caller_mod_name = inspect.getmodule(frm[0]).__name__ # does not work with python 2.4
|
||||
#if caller_mod_name == 'django.core.management.commands.dumpdata':
|
||||
|
||||
return super(PolymorphicModelBase, self).__getattribute__(name)
|
||||
46
awx/lib/site-packages/polymorphic/manager.py
Normal file
46
awx/lib/site-packages/polymorphic/manager.py
Normal file
@ -0,0 +1,46 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
""" PolymorphicManager
|
||||
Please see README.rst or DOCS.rst or http://chrisglass.github.com/django_polymorphic/
|
||||
"""
|
||||
from __future__ import unicode_literals
|
||||
import warnings
|
||||
from django.db import models
|
||||
from polymorphic.query import PolymorphicQuerySet
|
||||
|
||||
|
||||
class PolymorphicManager(models.Manager):
|
||||
"""
|
||||
Manager for PolymorphicModel
|
||||
|
||||
Usually not explicitly needed, except if a custom manager or
|
||||
a custom queryset class is to be used.
|
||||
"""
|
||||
# Tell Django that related fields also need to use this manager:
|
||||
use_for_related_fields = True
|
||||
queryset_class = PolymorphicQuerySet
|
||||
|
||||
def __init__(self, queryset_class=None, *args, **kwrags):
|
||||
# Up till polymorphic 0.4, the queryset class could be specified as parameter to __init__.
|
||||
# However, this doesn't work for related managers which instantiate a new version of this class.
|
||||
# Hence, for custom managers the new default is using the 'queryset_class' attribute at class level instead.
|
||||
if queryset_class:
|
||||
warnings.warn("Using PolymorphicManager(queryset_class=..) is deprecated; override the queryset_class attribute instead", DeprecationWarning)
|
||||
# For backwards compatibility, still allow the parameter:
|
||||
self.queryset_class = queryset_class
|
||||
|
||||
super(PolymorphicManager, self).__init__(*args, **kwrags)
|
||||
|
||||
def get_query_set(self):
|
||||
return self.queryset_class(self.model, using=self._db)
|
||||
|
||||
# Proxy all unknown method calls to the queryset, so that its members are
|
||||
# directly accessible as PolymorphicModel.objects.*
|
||||
# The advantage of this method is that not yet known member functions of derived querysets will be proxied as well.
|
||||
# We exclude any special functions (__) from this automatic proxying.
|
||||
def __getattr__(self, name):
|
||||
if name.startswith('__'):
|
||||
return super(PolymorphicManager, self).__getattr__(self, name)
|
||||
return getattr(self.get_query_set(), name)
|
||||
|
||||
def __unicode__(self):
|
||||
return '%s (PolymorphicManager) using %s' % (self.__class__.__name__, self.queryset_class.__name__)
|
||||
10
awx/lib/site-packages/polymorphic/models.py
Normal file
10
awx/lib/site-packages/polymorphic/models.py
Normal file
@ -0,0 +1,10 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
IMPORTANT:
|
||||
|
||||
The models.py module is not used anymore.
|
||||
Please use the following import method in your apps:
|
||||
|
||||
from polymorphic import PolymorphicModel, ...
|
||||
|
||||
"""
|
||||
201
awx/lib/site-packages/polymorphic/polymorphic_model.py
Normal file
201
awx/lib/site-packages/polymorphic/polymorphic_model.py
Normal file
@ -0,0 +1,201 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Seamless Polymorphic Inheritance for Django Models
|
||||
==================================================
|
||||
|
||||
Please see README.rst and DOCS.rst for further information.
|
||||
|
||||
Or on the Web:
|
||||
http://chrisglass.github.com/django_polymorphic/
|
||||
http://github.com/chrisglass/django_polymorphic
|
||||
|
||||
Copyright:
|
||||
This code and affiliated files are (C) by Bert Constantin and individual contributors.
|
||||
Please see LICENSE and AUTHORS for more information.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
from django.db import models
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils import six
|
||||
|
||||
from .base import PolymorphicModelBase
|
||||
from .manager import PolymorphicManager
|
||||
from .query_translate import translate_polymorphic_Q_object
|
||||
|
||||
|
||||
###################################################################################
|
||||
### PolymorphicModel
|
||||
|
||||
class PolymorphicModel(six.with_metaclass(PolymorphicModelBase, models.Model)):
|
||||
"""
|
||||
Abstract base class that provides polymorphic behaviour
|
||||
for any model directly or indirectly derived from it.
|
||||
|
||||
For usage instructions & examples please see documentation.
|
||||
|
||||
PolymorphicModel declares one field for internal use (polymorphic_ctype)
|
||||
and provides a polymorphic manager as the default manager
|
||||
(and as 'objects').
|
||||
|
||||
PolymorphicModel overrides the save() and __init__ methods.
|
||||
|
||||
If your derived class overrides any of these methods as well, then you need
|
||||
to take care that you correctly call the method of the superclass, like:
|
||||
|
||||
super(YourClass,self).save(*args,**kwargs)
|
||||
"""
|
||||
|
||||
# for PolymorphicModelBase, so it can tell which models are polymorphic and which are not (duck typing)
|
||||
polymorphic_model_marker = True
|
||||
|
||||
# for PolymorphicQuery, True => an overloaded __repr__ with nicer multi-line output is used by PolymorphicQuery
|
||||
polymorphic_query_multiline_output = False
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
# avoid ContentType related field accessor clash (an error emitted by model validation)
|
||||
polymorphic_ctype = models.ForeignKey(ContentType, null=True, editable=False,
|
||||
related_name='polymorphic_%(app_label)s.%(class)s_set')
|
||||
|
||||
# some applications want to know the name of the fields that are added to its models
|
||||
polymorphic_internal_model_fields = ['polymorphic_ctype']
|
||||
|
||||
# Note that Django 1.5 removes these managers because the model is abstract.
|
||||
# They are pretended to be there by the metaclass in PolymorphicModelBase.get_inherited_managers()
|
||||
objects = PolymorphicManager()
|
||||
base_objects = models.Manager()
|
||||
|
||||
@classmethod
|
||||
def translate_polymorphic_Q_object(self_class, q):
|
||||
return translate_polymorphic_Q_object(self_class, q)
|
||||
|
||||
def pre_save_polymorphic(self):
|
||||
"""Normally not needed.
|
||||
This function may be called manually in special use-cases. When the object
|
||||
is saved for the first time, we store its real class in polymorphic_ctype.
|
||||
When the object later is retrieved by PolymorphicQuerySet, it uses this
|
||||
field to figure out the real class of this object
|
||||
(used by PolymorphicQuerySet._get_real_instances)
|
||||
"""
|
||||
if not self.polymorphic_ctype_id:
|
||||
self.polymorphic_ctype = ContentType.objects.get_for_model(self, for_concrete_model=False)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""Overridden model save function which supports the polymorphism
|
||||
functionality (through pre_save_polymorphic)."""
|
||||
self.pre_save_polymorphic()
|
||||
return super(PolymorphicModel, self).save(*args, **kwargs)
|
||||
|
||||
def get_real_instance_class(self):
|
||||
"""
|
||||
Normally not needed.
|
||||
If a non-polymorphic manager (like base_objects) has been used to
|
||||
retrieve objects, then the real class/type of these objects may be
|
||||
determined using this method.
|
||||
"""
|
||||
# the following line would be the easiest way to do this, but it produces sql queries
|
||||
# return self.polymorphic_ctype.model_class()
|
||||
# so we use the following version, which uses the CopntentType manager cache.
|
||||
# Note that model_class() can return None for stale content types;
|
||||
# when the content type record still exists but no longer refers to an existing model.
|
||||
try:
|
||||
return ContentType.objects.get_for_id(self.polymorphic_ctype_id).model_class()
|
||||
except AttributeError:
|
||||
# Django <1.6 workaround
|
||||
return None
|
||||
|
||||
def get_real_concrete_instance_class_id(self):
|
||||
model_class = self.get_real_instance_class()
|
||||
if model_class is None:
|
||||
return None
|
||||
return ContentType.objects.get_for_model(model_class, for_concrete_model=True).pk
|
||||
|
||||
def get_real_concrete_instance_class(self):
|
||||
model_class = self.get_real_instance_class()
|
||||
if model_class is None:
|
||||
return None
|
||||
return ContentType.objects.get_for_model(model_class, for_concrete_model=True).model_class()
|
||||
|
||||
def get_real_instance(self):
|
||||
"""Normally not needed.
|
||||
If a non-polymorphic manager (like base_objects) has been used to
|
||||
retrieve objects, then the complete object with it's real class/type
|
||||
and all fields may be retrieved with this method.
|
||||
Each method call executes one db query (if necessary)."""
|
||||
real_model = self.get_real_instance_class()
|
||||
if real_model == self.__class__:
|
||||
return self
|
||||
return real_model.objects.get(pk=self.pk)
|
||||
|
||||
def __init__(self, * args, ** kwargs):
|
||||
"""Replace Django's inheritance accessor member functions for our model
|
||||
(self.__class__) with our own versions.
|
||||
We monkey patch them until a patch can be added to Django
|
||||
(which would probably be very small and make all of this obsolete).
|
||||
|
||||
If we have inheritance of the form ModelA -> ModelB ->ModelC then
|
||||
Django creates accessors like this:
|
||||
- ModelA: modelb
|
||||
- ModelB: modela_ptr, modelb, modelc
|
||||
- ModelC: modela_ptr, modelb, modelb_ptr, modelc
|
||||
|
||||
These accessors allow Django (and everyone else) to travel up and down
|
||||
the inheritance tree for the db object at hand.
|
||||
|
||||
The original Django accessors use our polymorphic manager.
|
||||
But they should not. So we replace them with our own accessors that use
|
||||
our appropriate base_objects manager.
|
||||
"""
|
||||
super(PolymorphicModel, self).__init__(*args, ** kwargs)
|
||||
|
||||
if self.__class__.polymorphic_super_sub_accessors_replaced:
|
||||
return
|
||||
self.__class__.polymorphic_super_sub_accessors_replaced = True
|
||||
|
||||
def create_accessor_function_for_model(model, accessor_name):
|
||||
def accessor_function(self):
|
||||
attr = model.base_objects.get(pk=self.pk)
|
||||
return attr
|
||||
return accessor_function
|
||||
|
||||
subclasses_and_superclasses_accessors = self._get_inheritance_relation_fields_and_models()
|
||||
|
||||
from django.db.models.fields.related import SingleRelatedObjectDescriptor, ReverseSingleRelatedObjectDescriptor
|
||||
for name, model in subclasses_and_superclasses_accessors.items():
|
||||
orig_accessor = getattr(self.__class__, name, None)
|
||||
if type(orig_accessor) in [SingleRelatedObjectDescriptor, ReverseSingleRelatedObjectDescriptor]:
|
||||
#print >>sys.stderr, '---------- replacing', name, orig_accessor, '->', model
|
||||
setattr(self.__class__, name, property(create_accessor_function_for_model(model, name)))
|
||||
|
||||
def _get_inheritance_relation_fields_and_models(self):
|
||||
"""helper function for __init__:
|
||||
determine names of all Django inheritance accessor member functions for type(self)"""
|
||||
|
||||
def add_model(model, as_ptr, result):
|
||||
name = model.__name__.lower()
|
||||
if as_ptr:
|
||||
name += '_ptr'
|
||||
result[name] = model
|
||||
|
||||
def add_model_if_regular(model, as_ptr, result):
|
||||
if (issubclass(model, models.Model)
|
||||
and model != models.Model
|
||||
and model != self.__class__
|
||||
and model != PolymorphicModel):
|
||||
add_model(model, as_ptr, result)
|
||||
|
||||
def add_all_super_models(model, result):
|
||||
add_model_if_regular(model, True, result)
|
||||
for b in model.__bases__:
|
||||
add_all_super_models(b, result)
|
||||
|
||||
def add_all_sub_models(model, result):
|
||||
for b in model.__subclasses__():
|
||||
add_model_if_regular(b, False, result)
|
||||
|
||||
result = {}
|
||||
add_all_super_models(self.__class__, result)
|
||||
add_all_sub_models(self.__class__, result)
|
||||
return result
|
||||
307
awx/lib/site-packages/polymorphic/query.py
Normal file
307
awx/lib/site-packages/polymorphic/query.py
Normal file
@ -0,0 +1,307 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
""" QuerySet for PolymorphicModel
|
||||
Please see README.rst or DOCS.rst or http://chrisglass.github.com/django_polymorphic/
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
from django.db.models.query import QuerySet
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils import six
|
||||
|
||||
from .query_translate import translate_polymorphic_filter_definitions_in_kwargs, translate_polymorphic_filter_definitions_in_args
|
||||
from .query_translate import translate_polymorphic_field_path
|
||||
|
||||
# chunk-size: maximum number of objects requested per db-request
|
||||
# by the polymorphic queryset.iterator() implementation; we use the same chunk size as Django
|
||||
try:
|
||||
from django.db.models.query import CHUNK_SIZE # this is 100 for Django 1.1/1.2
|
||||
except ImportError:
|
||||
# CHUNK_SIZE was removed in Django 1.6
|
||||
CHUNK_SIZE = 100
|
||||
Polymorphic_QuerySet_objects_per_request = CHUNK_SIZE
|
||||
|
||||
|
||||
def transmogrify(cls, obj):
|
||||
"""
|
||||
Upcast a class to a different type without asking questions.
|
||||
"""
|
||||
if not '__init__' in obj.__dict__:
|
||||
# Just assign __class__ to a different value.
|
||||
new = obj
|
||||
new.__class__ = cls
|
||||
else:
|
||||
# Run constructor, reassign values
|
||||
new = cls()
|
||||
for k,v in obj.__dict__.items():
|
||||
new.__dict__[k] = v
|
||||
return new
|
||||
|
||||
|
||||
###################################################################################
|
||||
### PolymorphicQuerySet
|
||||
|
||||
class PolymorphicQuerySet(QuerySet):
|
||||
"""
|
||||
QuerySet for PolymorphicModel
|
||||
|
||||
Contains the core functionality for PolymorphicModel
|
||||
|
||||
Usually not explicitly needed, except if a custom queryset class
|
||||
is to be used.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"init our queryset object member variables"
|
||||
self.polymorphic_disabled = False
|
||||
super(PolymorphicQuerySet, self).__init__(*args, **kwargs)
|
||||
|
||||
def _clone(self, *args, **kwargs):
|
||||
"Django's _clone only copies its own variables, so we need to copy ours here"
|
||||
new = super(PolymorphicQuerySet, self)._clone(*args, **kwargs)
|
||||
new.polymorphic_disabled = self.polymorphic_disabled
|
||||
return new
|
||||
|
||||
def non_polymorphic(self, *args, **kwargs):
|
||||
"""switch off polymorphic behaviour for this query.
|
||||
When the queryset is evaluated, only objects of the type of the
|
||||
base class used for this query are returned."""
|
||||
self.polymorphic_disabled = True
|
||||
return self
|
||||
|
||||
def instance_of(self, *args):
|
||||
"""Filter the queryset to only include the classes in args (and their subclasses).
|
||||
Implementation in _translate_polymorphic_filter_defnition."""
|
||||
return self.filter(instance_of=args)
|
||||
|
||||
def not_instance_of(self, *args):
|
||||
"""Filter the queryset to exclude the classes in args (and their subclasses).
|
||||
Implementation in _translate_polymorphic_filter_defnition."""
|
||||
return self.filter(not_instance_of=args)
|
||||
|
||||
def _filter_or_exclude(self, negate, *args, **kwargs):
|
||||
"We override this internal Django functon as it is used for all filter member functions."
|
||||
translate_polymorphic_filter_definitions_in_args(self.model, args) # the Q objects
|
||||
additional_args = translate_polymorphic_filter_definitions_in_kwargs(self.model, kwargs) # filter_field='data'
|
||||
return super(PolymorphicQuerySet, self)._filter_or_exclude(negate, *(list(args) + additional_args), **kwargs)
|
||||
|
||||
def order_by(self, *args, **kwargs):
|
||||
"""translate the field paths in the args, then call vanilla order_by."""
|
||||
new_args = [translate_polymorphic_field_path(self.model, a) for a in args]
|
||||
return super(PolymorphicQuerySet, self).order_by(*new_args, **kwargs)
|
||||
|
||||
def _process_aggregate_args(self, args, kwargs):
|
||||
"""for aggregate and annotate kwargs: allow ModelX___field syntax for kwargs, forbid it for args.
|
||||
Modifies kwargs if needed (these are Aggregate objects, we translate the lookup member variable)"""
|
||||
for a in args:
|
||||
assert not '___' in a.lookup, 'PolymorphicModel: annotate()/aggregate(): ___ model lookup supported for keyword arguments only'
|
||||
for a in six.itervalues(kwargs):
|
||||
a.lookup = translate_polymorphic_field_path(self.model, a.lookup)
|
||||
|
||||
def annotate(self, *args, **kwargs):
|
||||
"""translate the polymorphic field paths in the kwargs, then call vanilla annotate.
|
||||
_get_real_instances will do the rest of the job after executing the query."""
|
||||
self._process_aggregate_args(args, kwargs)
|
||||
return super(PolymorphicQuerySet, self).annotate(*args, **kwargs)
|
||||
|
||||
def aggregate(self, *args, **kwargs):
|
||||
"""translate the polymorphic field paths in the kwargs, then call vanilla aggregate.
|
||||
We need no polymorphic object retrieval for aggregate => switch it off."""
|
||||
self._process_aggregate_args(args, kwargs)
|
||||
self.polymorphic_disabled = True
|
||||
return super(PolymorphicQuerySet, self).aggregate(*args, **kwargs)
|
||||
|
||||
# Since django_polymorphic 'V1.0 beta2', extra() always returns polymorphic results.^
|
||||
# The resulting objects are required to have a unique primary key within the result set
|
||||
# (otherwise an error is thrown).
|
||||
# The "polymorphic" keyword argument is not supported anymore.
|
||||
#def extra(self, *args, **kwargs):
|
||||
|
||||
def _get_real_instances(self, base_result_objects):
|
||||
"""
|
||||
Polymorphic object loader
|
||||
|
||||
Does the same as:
|
||||
|
||||
return [ o.get_real_instance() for o in base_result_objects ]
|
||||
|
||||
but more efficiently.
|
||||
|
||||
The list base_result_objects contains the objects from the executed
|
||||
base class query. The class of all of them is self.model (our base model).
|
||||
|
||||
Some, many or all of these objects were not created and stored as
|
||||
class self.model, but as a class derived from self.model. We want to re-fetch
|
||||
these objects from the db as their original class so we can return them
|
||||
just as they were created/saved.
|
||||
|
||||
We identify these objects by looking at o.polymorphic_ctype, which specifies
|
||||
the real class of these objects (the class at the time they were saved).
|
||||
|
||||
First, we sort the result objects in base_result_objects for their
|
||||
subclass (from o.polymorphic_ctype), and then we execute one db query per
|
||||
subclass of objects. Here, we handle any annotations from annotate().
|
||||
|
||||
Finally we re-sort the resulting objects into the correct order and
|
||||
return them as a list.
|
||||
"""
|
||||
ordered_id_list = [] # list of ids of result-objects in correct order
|
||||
results = {} # polymorphic dict of result-objects, keyed with their id (no order)
|
||||
|
||||
# dict contains one entry per unique model type occurring in result,
|
||||
# in the format idlist_per_model[modelclass]=[list-of-object-ids]
|
||||
idlist_per_model = defaultdict(list)
|
||||
|
||||
# - sort base_result_object ids into idlist_per_model lists, depending on their real class;
|
||||
# - also record the correct result order in "ordered_id_list"
|
||||
# - store objects that already have the correct class into "results"
|
||||
base_result_objects_by_id = {}
|
||||
self_model_class_id = ContentType.objects.get_for_model(self.model, for_concrete_model=False).pk
|
||||
self_concrete_model_class_id = ContentType.objects.get_for_model(self.model, for_concrete_model=True).pk
|
||||
|
||||
for base_object in base_result_objects:
|
||||
ordered_id_list.append(base_object.pk)
|
||||
|
||||
# check if id of the result object occeres more than once - this can happen e.g. with base_objects.extra(tables=...)
|
||||
if not base_object.pk in base_result_objects_by_id:
|
||||
base_result_objects_by_id[base_object.pk] = base_object
|
||||
|
||||
if base_object.polymorphic_ctype_id == self_model_class_id:
|
||||
# Real class is exactly the same as base class, go straight to results
|
||||
results[base_object.pk] = base_object
|
||||
|
||||
else:
|
||||
real_concrete_class = base_object.get_real_instance_class()
|
||||
real_concrete_class_id = base_object.get_real_concrete_instance_class_id()
|
||||
|
||||
if real_concrete_class_id is None:
|
||||
# Dealing with a stale content type
|
||||
continue
|
||||
elif real_concrete_class_id == self_concrete_model_class_id:
|
||||
# Real and base classes share the same concrete ancestor,
|
||||
# upcast it and put it in the results
|
||||
results[base_object.pk] = transmogrify(real_concrete_class, base_object)
|
||||
else:
|
||||
real_concrete_class = ContentType.objects.get_for_id(real_concrete_class_id).model_class()
|
||||
idlist_per_model[real_concrete_class].append(base_object.pk)
|
||||
|
||||
# django's automatic ".pk" field does not always work correctly for
|
||||
# custom fields in derived objects (unclear yet who to put the blame on).
|
||||
# We get different type(o.pk) in this case.
|
||||
# We work around this by using the real name of the field directly
|
||||
# for accessing the primary key of the the derived objects.
|
||||
# We might assume that self.model._meta.pk.name gives us the name of the primary key field,
|
||||
# but it doesn't. Therefore we use polymorphic_primary_key_name, which we set up in base.py.
|
||||
pk_name = self.model.polymorphic_primary_key_name
|
||||
|
||||
# For each model in "idlist_per_model" request its objects (the real model)
|
||||
# from the db and store them in results[].
|
||||
# Then we copy the annotate fields from the base objects to the real objects.
|
||||
# Then we copy the extra() select fields from the base objects to the real objects.
|
||||
# TODO: defer(), only(): support for these would be around here
|
||||
for real_concrete_class, idlist in idlist_per_model.items():
|
||||
real_objects = real_concrete_class.base_objects.filter(pk__in=idlist) # use pk__in instead ####
|
||||
real_objects.query.select_related = self.query.select_related # copy select related configuration to new qs
|
||||
|
||||
for real_object in real_objects:
|
||||
o_pk = getattr(real_object, pk_name)
|
||||
real_class = real_object.get_real_instance_class()
|
||||
|
||||
# If the real class is a proxy, upcast it
|
||||
if real_class != real_concrete_class:
|
||||
real_object = transmogrify(real_class, real_object)
|
||||
|
||||
if self.query.aggregates:
|
||||
for anno_field_name in six.iterkeys(self.query.aggregates):
|
||||
attr = getattr(base_result_objects_by_id[o_pk], anno_field_name)
|
||||
setattr(real_object, anno_field_name, attr)
|
||||
|
||||
if self.query.extra_select:
|
||||
for select_field_name in six.iterkeys(self.query.extra_select):
|
||||
attr = getattr(base_result_objects_by_id[o_pk], select_field_name)
|
||||
setattr(real_object, select_field_name, attr)
|
||||
|
||||
results[o_pk] = real_object
|
||||
|
||||
# re-create correct order and return result list
|
||||
resultlist = [results[ordered_id] for ordered_id in ordered_id_list if ordered_id in results]
|
||||
|
||||
# set polymorphic_annotate_names in all objects (currently just used for debugging/printing)
|
||||
if self.query.aggregates:
|
||||
annotate_names = six.iterkeys(self.query.aggregates) # get annotate field list
|
||||
for real_object in resultlist:
|
||||
real_object.polymorphic_annotate_names = annotate_names
|
||||
|
||||
# set polymorphic_extra_select_names in all objects (currently just used for debugging/printing)
|
||||
if self.query.extra_select:
|
||||
extra_select_names = six.iterkeys(self.query.extra_select) # get extra select field list
|
||||
for real_object in resultlist:
|
||||
real_object.polymorphic_extra_select_names = extra_select_names
|
||||
|
||||
return resultlist
|
||||
|
||||
def iterator(self):
|
||||
"""
|
||||
This function is used by Django for all object retrieval.
|
||||
By overriding it, we modify the objects that this queryset returns
|
||||
when it is evaluated (or its get method or other object-returning methods are called).
|
||||
|
||||
Here we do the same as:
|
||||
|
||||
base_result_objects=list(super(PolymorphicQuerySet, self).iterator())
|
||||
real_results=self._get_real_instances(base_result_objects)
|
||||
for o in real_results: yield o
|
||||
|
||||
but it requests the objects in chunks from the database,
|
||||
with Polymorphic_QuerySet_objects_per_request per chunk
|
||||
"""
|
||||
base_iter = super(PolymorphicQuerySet, self).iterator()
|
||||
|
||||
# disabled => work just like a normal queryset
|
||||
if self.polymorphic_disabled:
|
||||
for o in base_iter:
|
||||
yield o
|
||||
raise StopIteration
|
||||
|
||||
while True:
|
||||
base_result_objects = []
|
||||
reached_end = False
|
||||
|
||||
for i in range(Polymorphic_QuerySet_objects_per_request):
|
||||
try:
|
||||
o = next(base_iter)
|
||||
base_result_objects.append(o)
|
||||
except StopIteration:
|
||||
reached_end = True
|
||||
break
|
||||
|
||||
real_results = self._get_real_instances(base_result_objects)
|
||||
|
||||
for o in real_results:
|
||||
yield o
|
||||
|
||||
if reached_end:
|
||||
raise StopIteration
|
||||
|
||||
def __repr__(self, *args, **kwargs):
|
||||
if self.model.polymorphic_query_multiline_output:
|
||||
result = [repr(o) for o in self.all()]
|
||||
return '[ ' + ',\n '.join(result) + ' ]'
|
||||
else:
|
||||
return super(PolymorphicQuerySet, self).__repr__(*args, **kwargs)
|
||||
|
||||
class _p_list_class(list):
|
||||
def __repr__(self, *args, **kwargs):
|
||||
result = [repr(o) for o in self]
|
||||
return '[ ' + ',\n '.join(result) + ' ]'
|
||||
|
||||
def get_real_instances(self, base_result_objects=None):
|
||||
"same as _get_real_instances, but make sure that __repr__ for ShowField... creates correct output"
|
||||
if not base_result_objects:
|
||||
base_result_objects = self
|
||||
olist = self._get_real_instances(base_result_objects)
|
||||
if not self.model.polymorphic_query_multiline_output:
|
||||
return olist
|
||||
clist = PolymorphicQuerySet._p_list_class(olist)
|
||||
return clist
|
||||
248
awx/lib/site-packages/polymorphic/query_translate.py
Normal file
248
awx/lib/site-packages/polymorphic/query_translate.py
Normal file
@ -0,0 +1,248 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
""" PolymorphicQuerySet support functions
|
||||
Please see README.rst or DOCS.rst or http://chrisglass.github.com/django_polymorphic/
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
from django.db import models
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db.models import Q, FieldDoesNotExist
|
||||
from django.db.models.related import RelatedObject
|
||||
|
||||
from functools import reduce
|
||||
|
||||
|
||||
###################################################################################
|
||||
### PolymorphicQuerySet support functions
|
||||
|
||||
# These functions implement the additional filter- and Q-object functionality.
|
||||
# They form a kind of small framework for easily adding more
|
||||
# functionality to filters and Q objects.
|
||||
# Probably a more general queryset enhancement class could be made out of them.
|
||||
|
||||
def translate_polymorphic_filter_definitions_in_kwargs(queryset_model, kwargs):
|
||||
"""
|
||||
Translate the keyword argument list for PolymorphicQuerySet.filter()
|
||||
|
||||
Any kwargs with special polymorphic functionality are replaced in the kwargs
|
||||
dict with their vanilla django equivalents.
|
||||
|
||||
For some kwargs a direct replacement is not possible, as a Q object is needed
|
||||
instead to implement the required functionality. In these cases the kwarg is
|
||||
deleted from the kwargs dict and a Q object is added to the return list.
|
||||
|
||||
Modifies: kwargs dict
|
||||
Returns: a list of non-keyword-arguments (Q objects) to be added to the filter() query.
|
||||
"""
|
||||
additional_args = []
|
||||
for field_path, val in kwargs.copy().items(): # Python 3 needs copy
|
||||
|
||||
new_expr = _translate_polymorphic_filter_definition(queryset_model, field_path, val)
|
||||
|
||||
if type(new_expr) == tuple:
|
||||
# replace kwargs element
|
||||
del(kwargs[field_path])
|
||||
kwargs[new_expr[0]] = new_expr[1]
|
||||
|
||||
elif isinstance(new_expr, models.Q):
|
||||
del(kwargs[field_path])
|
||||
additional_args.append(new_expr)
|
||||
|
||||
return additional_args
|
||||
|
||||
|
||||
def translate_polymorphic_Q_object(queryset_model, potential_q_object):
|
||||
def tree_node_correct_field_specs(my_model, node):
|
||||
" process all children of this Q node "
|
||||
for i in range(len(node.children)):
|
||||
child = node.children[i]
|
||||
|
||||
if type(child) == tuple:
|
||||
# this Q object child is a tuple => a kwarg like Q( instance_of=ModelB )
|
||||
key, val = child
|
||||
new_expr = _translate_polymorphic_filter_definition(my_model, key, val)
|
||||
if new_expr:
|
||||
node.children[i] = new_expr
|
||||
else:
|
||||
# this Q object child is another Q object, recursively process this as well
|
||||
tree_node_correct_field_specs(my_model, child)
|
||||
|
||||
if isinstance(potential_q_object, models.Q):
|
||||
tree_node_correct_field_specs(queryset_model, potential_q_object)
|
||||
|
||||
return potential_q_object
|
||||
|
||||
|
||||
def translate_polymorphic_filter_definitions_in_args(queryset_model, args):
|
||||
"""
|
||||
Translate the non-keyword argument list for PolymorphicQuerySet.filter()
|
||||
|
||||
In the args list, we replace all kwargs to Q-objects that contain special
|
||||
polymorphic functionality with their vanilla django equivalents.
|
||||
We traverse the Q object tree for this (which is simple).
|
||||
|
||||
TODO: investigate: we modify the Q-objects ina args in-place. Is this OK?
|
||||
|
||||
Modifies: args list
|
||||
"""
|
||||
|
||||
for q in args:
|
||||
translate_polymorphic_Q_object(queryset_model, q)
|
||||
|
||||
|
||||
def _translate_polymorphic_filter_definition(queryset_model, field_path, field_val):
|
||||
"""
|
||||
Translate a keyword argument (field_path=field_val), as used for
|
||||
PolymorphicQuerySet.filter()-like functions (and Q objects).
|
||||
|
||||
A kwarg with special polymorphic functionality is translated into
|
||||
its vanilla django equivalent, which is returned, either as tuple
|
||||
(field_path, field_val) or as Q object.
|
||||
|
||||
Returns: kwarg tuple or Q object or None (if no change is required)
|
||||
"""
|
||||
|
||||
# handle instance_of expressions or alternatively,
|
||||
# if this is a normal Django filter expression, return None
|
||||
if field_path == 'instance_of':
|
||||
return _create_model_filter_Q(field_val)
|
||||
elif field_path == 'not_instance_of':
|
||||
return _create_model_filter_Q(field_val, not_instance_of=True)
|
||||
elif not '___' in field_path:
|
||||
return None # no change
|
||||
|
||||
# filter expression contains '___' (i.e. filter for polymorphic field)
|
||||
# => get the model class specified in the filter expression
|
||||
newpath = translate_polymorphic_field_path(queryset_model, field_path)
|
||||
return (newpath, field_val)
|
||||
|
||||
|
||||
def translate_polymorphic_field_path(queryset_model, field_path):
|
||||
"""
|
||||
Translate a field path from a keyword argument, as used for
|
||||
PolymorphicQuerySet.filter()-like functions (and Q objects).
|
||||
Supports leading '-' (for order_by args).
|
||||
|
||||
E.g.: if queryset_model is ModelA, then "ModelC___field3" is translated
|
||||
into modela__modelb__modelc__field3.
|
||||
Returns: translated path (unchanged, if no translation needed)
|
||||
"""
|
||||
classname, sep, pure_field_path = field_path.partition('___')
|
||||
if not sep:
|
||||
return field_path
|
||||
assert classname, 'PolymorphicModel: %s: bad field specification' % field_path
|
||||
|
||||
negated = False
|
||||
if classname[0] == '-':
|
||||
negated = True
|
||||
classname = classname.lstrip('-')
|
||||
|
||||
if '__' in classname:
|
||||
# the user has app label prepended to class name via __ => use Django's get_model function
|
||||
appname, sep, classname = classname.partition('__')
|
||||
model = models.get_model(appname, classname)
|
||||
assert model, 'PolymorphicModel: model %s (in app %s) not found!' % (model.__name__, appname)
|
||||
if not issubclass(model, queryset_model):
|
||||
e = 'PolymorphicModel: queryset filter error: "' + model.__name__ + '" is not derived from "' + queryset_model.__name__ + '"'
|
||||
raise AssertionError(e)
|
||||
|
||||
else:
|
||||
# the user has only given us the class name via ___
|
||||
# => select the model from the sub models of the queryset base model
|
||||
|
||||
# Test whether it's actually a regular relation__ _fieldname (the field starting with an _)
|
||||
# so no tripple ClassName___field was intended.
|
||||
try:
|
||||
# rel = (field_object, model, direct, m2m)
|
||||
field = queryset_model._meta.get_field_by_name(classname)[0]
|
||||
if isinstance(field, RelatedObject):
|
||||
# Can also test whether the field exists in the related object to avoid ambiguity between
|
||||
# class names and field names, but that never happens when your class names are in CamelCase.
|
||||
return field_path # No exception raised, field does exist.
|
||||
except FieldDoesNotExist:
|
||||
pass
|
||||
|
||||
# function to collect all sub-models, this should be optimized (cached)
|
||||
def add_all_sub_models(model, result):
|
||||
if issubclass(model, models.Model) and model != models.Model:
|
||||
# model name is occurring twice in submodel inheritance tree => Error
|
||||
if model.__name__ in result and model != result[model.__name__]:
|
||||
e = 'PolymorphicModel: model name alone is ambiguous: %s.%s and %s.%s!\n'
|
||||
e += 'In this case, please use the syntax: applabel__ModelName___field'
|
||||
assert model, e % (
|
||||
model._meta.app_label, model.__name__,
|
||||
result[model.__name__]._meta.app_label, result[model.__name__].__name__)
|
||||
|
||||
result[model.__name__] = model
|
||||
|
||||
for b in model.__subclasses__():
|
||||
add_all_sub_models(b, result)
|
||||
|
||||
submodels = {}
|
||||
add_all_sub_models(queryset_model, submodels)
|
||||
model = submodels.get(classname, None)
|
||||
assert model, 'PolymorphicModel: model %s not found (not a subclass of %s)!' % (classname, queryset_model.__name__)
|
||||
|
||||
# create new field path for expressions, e.g. for baseclass=ModelA, myclass=ModelC
|
||||
# 'modelb__modelc" is returned
|
||||
def _create_base_path(baseclass, myclass):
|
||||
bases = myclass.__bases__
|
||||
for b in bases:
|
||||
if b == baseclass:
|
||||
return myclass.__name__.lower()
|
||||
path = _create_base_path(baseclass, b)
|
||||
if path:
|
||||
return path + '__' + myclass.__name__.lower()
|
||||
return ''
|
||||
|
||||
basepath = _create_base_path(queryset_model, model)
|
||||
|
||||
if negated:
|
||||
newpath = '-'
|
||||
else:
|
||||
newpath = ''
|
||||
|
||||
newpath += basepath
|
||||
if basepath:
|
||||
newpath += '__'
|
||||
|
||||
newpath += pure_field_path
|
||||
return newpath
|
||||
|
||||
|
||||
def _create_model_filter_Q(modellist, not_instance_of=False):
|
||||
"""
|
||||
Helper function for instance_of / not_instance_of
|
||||
Creates and returns a Q object that filters for the models in modellist,
|
||||
including all subclasses of these models (as we want to do the same
|
||||
as pythons isinstance() ).
|
||||
.
|
||||
We recursively collect all __subclasses__(), create a Q filter for each,
|
||||
and or-combine these Q objects. This could be done much more
|
||||
efficiently however (regarding the resulting sql), should an optimization
|
||||
be needed.
|
||||
"""
|
||||
|
||||
if not modellist:
|
||||
return None
|
||||
|
||||
from .polymorphic_model import PolymorphicModel
|
||||
|
||||
if type(modellist) != list and type(modellist) != tuple:
|
||||
if issubclass(modellist, PolymorphicModel):
|
||||
modellist = [modellist]
|
||||
else:
|
||||
assert False, 'PolymorphicModel: instance_of expects a list of (polymorphic) models or a single (polymorphic) model'
|
||||
|
||||
def q_class_with_subclasses(model):
|
||||
q = Q(polymorphic_ctype=ContentType.objects.get_for_model(model, for_concrete_model=False))
|
||||
for subclass in model.__subclasses__():
|
||||
q = q | q_class_with_subclasses(subclass)
|
||||
return q
|
||||
|
||||
qlist = [q_class_with_subclasses(m) for m in modellist]
|
||||
|
||||
q_ored = reduce(lambda a, b: a | b, qlist)
|
||||
if not_instance_of:
|
||||
q_ored = ~q_ored
|
||||
return q_ored
|
||||
164
awx/lib/site-packages/polymorphic/showfields.py
Normal file
164
awx/lib/site-packages/polymorphic/showfields.py
Normal file
@ -0,0 +1,164 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from django.db import models
|
||||
from django.utils import six
|
||||
|
||||
class ShowFieldBase(object):
|
||||
""" base class for the ShowField... model mixins, does the work """
|
||||
|
||||
polymorphic_query_multiline_output = True # cause nicer multiline PolymorphicQuery output
|
||||
|
||||
polymorphic_showfield_type = False
|
||||
polymorphic_showfield_content = False
|
||||
|
||||
# these may be overridden by the user
|
||||
polymorphic_showfield_max_line_width = None
|
||||
polymorphic_showfield_max_field_width = 20
|
||||
polymorphic_showfield_old_format = False
|
||||
|
||||
def __repr__(self):
|
||||
return self.__unicode__()
|
||||
|
||||
def _showfields_get_content(self, field_name, field_type=type(None)):
|
||||
"helper for __unicode__"
|
||||
content = getattr(self, field_name)
|
||||
if self.polymorphic_showfield_old_format:
|
||||
out = ': '
|
||||
else:
|
||||
out = ' '
|
||||
if issubclass(field_type, models.ForeignKey):
|
||||
if content is None:
|
||||
out += 'None'
|
||||
else:
|
||||
out += content.__class__.__name__
|
||||
elif issubclass(field_type, models.ManyToManyField):
|
||||
out += '%d' % content.count()
|
||||
elif isinstance(content, six.integer_types):
|
||||
out += str(content)
|
||||
elif content is None:
|
||||
out += 'None'
|
||||
else:
|
||||
txt = str(content)
|
||||
if len(txt) > self.polymorphic_showfield_max_field_width:
|
||||
txt = txt[:self.polymorphic_showfield_max_field_width - 2] + '..'
|
||||
out += '"' + txt + '"'
|
||||
return out
|
||||
|
||||
def _showfields_add_regular_fields(self, parts):
|
||||
"helper for __unicode__"
|
||||
done_fields = set()
|
||||
for field in self._meta.fields + self._meta.many_to_many:
|
||||
if field.name in self.polymorphic_internal_model_fields or '_ptr' in field.name:
|
||||
continue
|
||||
if field.name in done_fields:
|
||||
continue # work around django diamond inheritance problem
|
||||
done_fields.add(field.name)
|
||||
|
||||
out = field.name
|
||||
|
||||
# if this is the standard primary key named "id", print it as we did with older versions of django_polymorphic
|
||||
if field.primary_key and field.name == 'id' and type(field) == models.AutoField:
|
||||
out += ' ' + str(getattr(self, field.name))
|
||||
|
||||
# otherwise, display it just like all other fields (with correct type, shortened content etc.)
|
||||
else:
|
||||
if self.polymorphic_showfield_type:
|
||||
out += ' (' + type(field).__name__
|
||||
if field.primary_key:
|
||||
out += '/pk'
|
||||
out += ')'
|
||||
|
||||
if self.polymorphic_showfield_content:
|
||||
out += self._showfields_get_content(field.name, type(field))
|
||||
|
||||
parts.append((False, out, ','))
|
||||
|
||||
def _showfields_add_dynamic_fields(self, field_list, title, parts):
|
||||
"helper for __unicode__"
|
||||
parts.append((True, '- ' + title, ':'))
|
||||
for field_name in field_list:
|
||||
out = field_name
|
||||
content = getattr(self, field_name)
|
||||
if self.polymorphic_showfield_type:
|
||||
out += ' (' + type(content).__name__ + ')'
|
||||
if self.polymorphic_showfield_content:
|
||||
out += self._showfields_get_content(field_name)
|
||||
|
||||
parts.append((False, out, ','))
|
||||
|
||||
def __unicode__(self):
|
||||
# create list ("parts") containing one tuple for each title/field:
|
||||
# ( bool: new section , item-text , separator to use after item )
|
||||
|
||||
# start with model name
|
||||
parts = [(True, self.__class__.__name__, ':')]
|
||||
|
||||
# add all regular fields
|
||||
self._showfields_add_regular_fields(parts)
|
||||
|
||||
# add annotate fields
|
||||
if hasattr(self, 'polymorphic_annotate_names'):
|
||||
self._showfields_add_dynamic_fields(self.polymorphic_annotate_names, 'Ann', parts)
|
||||
|
||||
# add extra() select fields
|
||||
if hasattr(self, 'polymorphic_extra_select_names'):
|
||||
self._showfields_add_dynamic_fields(self.polymorphic_extra_select_names, 'Extra', parts)
|
||||
|
||||
# format result
|
||||
|
||||
indent = len(self.__class__.__name__) + 5
|
||||
indentstr = ''.rjust(indent)
|
||||
out = ''
|
||||
xpos = 0
|
||||
possible_line_break_pos = None
|
||||
|
||||
for i in range(len(parts)):
|
||||
new_section, p, separator = parts[i]
|
||||
final = (i == len(parts) - 1)
|
||||
if not final:
|
||||
next_new_section, _, _ = parts[i + 1]
|
||||
|
||||
if (self.polymorphic_showfield_max_line_width
|
||||
and xpos + len(p) > self.polymorphic_showfield_max_line_width
|
||||
and possible_line_break_pos != None):
|
||||
rest = out[possible_line_break_pos:]
|
||||
out = out[:possible_line_break_pos]
|
||||
out += '\n' + indentstr + rest
|
||||
xpos = indent + len(rest)
|
||||
|
||||
out += p
|
||||
xpos += len(p)
|
||||
|
||||
if not final:
|
||||
if not next_new_section:
|
||||
out += separator
|
||||
xpos += len(separator)
|
||||
out += ' '
|
||||
xpos += 1
|
||||
|
||||
if not new_section:
|
||||
possible_line_break_pos = len(out)
|
||||
|
||||
return '<' + out + '>'
|
||||
|
||||
|
||||
class ShowFieldType(ShowFieldBase):
|
||||
""" model mixin that shows the object's class and it's field types """
|
||||
polymorphic_showfield_type = True
|
||||
|
||||
|
||||
class ShowFieldContent(ShowFieldBase):
|
||||
""" model mixin that shows the object's class, it's fields and field contents """
|
||||
polymorphic_showfield_content = True
|
||||
|
||||
|
||||
class ShowFieldTypeAndContent(ShowFieldBase):
|
||||
""" model mixin, like ShowFieldContent, but also show field types """
|
||||
polymorphic_showfield_type = True
|
||||
polymorphic_showfield_content = True
|
||||
|
||||
|
||||
# compatibility with old class names
|
||||
ShowFieldTypes = ShowFieldType
|
||||
ShowFields = ShowFieldContent
|
||||
ShowFieldsAndTypes = ShowFieldTypeAndContent
|
||||
@ -0,0 +1,11 @@
|
||||
{% extends "admin/change_form.html" %}
|
||||
|
||||
{% if save_on_top %}
|
||||
{% block submit_buttons_top %}
|
||||
{% include 'admin/submit_line.html' with show_save=1 %}
|
||||
{% endblock %}
|
||||
{% endif %}
|
||||
|
||||
{% block submit_buttons_bottom %}
|
||||
{% include 'admin/submit_line.html' with show_save=1 %}
|
||||
{% endblock %}
|
||||
@ -0,0 +1,6 @@
|
||||
{% extends "admin/change_form.html" %}
|
||||
{% load polymorphic_admin_tags %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
{% breadcrumb_scope base_opts %}{{ block.super }}{% endbreadcrumb_scope %}
|
||||
{% endblock %}
|
||||
@ -0,0 +1,6 @@
|
||||
{% extends "admin/delete_confirmation.html" %}
|
||||
{% load polymorphic_admin_tags %}
|
||||
|
||||
{% block breadcrumbs %}
|
||||
{% breadcrumb_scope base_opts %}{{ block.super }}{% endbreadcrumb_scope %}
|
||||
{% endblock %}
|
||||
@ -0,0 +1,53 @@
|
||||
from django.template import Library, Node, TemplateSyntaxError
|
||||
from django.utils import six
|
||||
|
||||
register = Library()
|
||||
|
||||
|
||||
class BreadcrumbScope(Node):
|
||||
def __init__(self, base_opts, nodelist):
|
||||
self.base_opts = base_opts
|
||||
self.nodelist = nodelist # Note, takes advantage of Node.child_nodelists
|
||||
|
||||
@classmethod
|
||||
def parse(cls, parser, token):
|
||||
bits = token.split_contents()
|
||||
if len(bits) == 2:
|
||||
(tagname, base_opts) = bits
|
||||
base_opts = parser.compile_filter(base_opts)
|
||||
nodelist = parser.parse(('endbreadcrumb_scope',))
|
||||
parser.delete_first_token()
|
||||
|
||||
return cls(
|
||||
base_opts=base_opts,
|
||||
nodelist=nodelist
|
||||
)
|
||||
else:
|
||||
raise TemplateSyntaxError("{0} tag expects 1 argument".format(token.contents[0]))
|
||||
|
||||
|
||||
def render(self, context):
|
||||
# app_label is really hard to overwrite in the standard Django ModelAdmin.
|
||||
# To insert it in the template, the entire render_change_form() and delete_view() have to copied and adjusted.
|
||||
# Instead, have an assignment tag that inserts that in the template.
|
||||
base_opts = self.base_opts.resolve(context)
|
||||
new_vars = {}
|
||||
if base_opts and not isinstance(base_opts, six.string_types):
|
||||
new_vars = {
|
||||
'app_label': base_opts.app_label, # What this is all about
|
||||
'opts': base_opts,
|
||||
}
|
||||
|
||||
new_scope = context.push()
|
||||
new_scope.update(new_vars)
|
||||
html = self.nodelist.render(context)
|
||||
context.pop()
|
||||
return html
|
||||
|
||||
|
||||
@register.tag
|
||||
def breadcrumb_scope(parser, token):
|
||||
"""
|
||||
Easily allow the breadcrumb to be generated in the admin change templates.
|
||||
"""
|
||||
return BreadcrumbScope.parse(parser, token)
|
||||
791
awx/lib/site-packages/polymorphic/tests.py
Normal file
791
awx/lib/site-packages/polymorphic/tests.py
Normal file
@ -0,0 +1,791 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
""" Test Cases
|
||||
Please see README.rst or DOCS.rst or http://chrisglass.github.com/django_polymorphic/
|
||||
"""
|
||||
from __future__ import print_function
|
||||
import uuid
|
||||
import re
|
||||
from django.db.models.query import QuerySet
|
||||
|
||||
from django.test import TestCase
|
||||
from django.db.models import Q,Count
|
||||
from django.db import models
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.utils import six
|
||||
|
||||
from polymorphic import PolymorphicModel, PolymorphicManager, PolymorphicQuerySet
|
||||
from polymorphic import ShowFieldContent, ShowFieldType, ShowFieldTypeAndContent
|
||||
from polymorphic.tools_for_tests import UUIDField
|
||||
|
||||
|
||||
class PlainA(models.Model):
|
||||
field1 = models.CharField(max_length=10)
|
||||
class PlainB(PlainA):
|
||||
field2 = models.CharField(max_length=10)
|
||||
class PlainC(PlainB):
|
||||
field3 = models.CharField(max_length=10)
|
||||
|
||||
class Model2A(ShowFieldType, PolymorphicModel):
|
||||
field1 = models.CharField(max_length=10)
|
||||
class Model2B(Model2A):
|
||||
field2 = models.CharField(max_length=10)
|
||||
class Model2C(Model2B):
|
||||
field3 = models.CharField(max_length=10)
|
||||
class Model2D(Model2C):
|
||||
field4 = models.CharField(max_length=10)
|
||||
|
||||
class ModelExtraA(ShowFieldTypeAndContent, PolymorphicModel):
|
||||
field1 = models.CharField(max_length=10)
|
||||
class ModelExtraB(ModelExtraA):
|
||||
field2 = models.CharField(max_length=10)
|
||||
class ModelExtraC(ModelExtraB):
|
||||
field3 = models.CharField(max_length=10)
|
||||
class ModelExtraExternal(models.Model):
|
||||
topic = models.CharField(max_length=10)
|
||||
|
||||
class ModelShow1(ShowFieldType,PolymorphicModel):
|
||||
field1 = models.CharField(max_length=10)
|
||||
m2m = models.ManyToManyField('self')
|
||||
class ModelShow2(ShowFieldContent, PolymorphicModel):
|
||||
field1 = models.CharField(max_length=10)
|
||||
m2m = models.ManyToManyField('self')
|
||||
class ModelShow3(ShowFieldTypeAndContent, PolymorphicModel):
|
||||
field1 = models.CharField(max_length=10)
|
||||
m2m = models.ManyToManyField('self')
|
||||
|
||||
class ModelShow1_plain(PolymorphicModel):
|
||||
field1 = models.CharField(max_length=10)
|
||||
class ModelShow2_plain(ModelShow1_plain):
|
||||
field2 = models.CharField(max_length=10)
|
||||
|
||||
|
||||
class Base(ShowFieldType, PolymorphicModel):
|
||||
field_b = models.CharField(max_length=10)
|
||||
class ModelX(Base):
|
||||
field_x = models.CharField(max_length=10)
|
||||
class ModelY(Base):
|
||||
field_y = models.CharField(max_length=10)
|
||||
|
||||
class Enhance_Plain(models.Model):
|
||||
field_p = models.CharField(max_length=10)
|
||||
class Enhance_Base(ShowFieldTypeAndContent, PolymorphicModel):
|
||||
field_b = models.CharField(max_length=10)
|
||||
class Enhance_Inherit(Enhance_Base, Enhance_Plain):
|
||||
field_i = models.CharField(max_length=10)
|
||||
|
||||
class DiamondBase(models.Model):
|
||||
field_b = models.CharField(max_length=10)
|
||||
class DiamondX(DiamondBase):
|
||||
field_x = models.CharField(max_length=10)
|
||||
class DiamondY(DiamondBase):
|
||||
field_y = models.CharField(max_length=10)
|
||||
class DiamondXY(DiamondX, DiamondY):
|
||||
pass
|
||||
|
||||
class RelationBase(ShowFieldTypeAndContent, PolymorphicModel):
|
||||
field_base = models.CharField(max_length=10)
|
||||
fk = models.ForeignKey('self', null=True, related_name='relationbase_set')
|
||||
m2m = models.ManyToManyField('self')
|
||||
class RelationA(RelationBase):
|
||||
field_a = models.CharField(max_length=10)
|
||||
class RelationB(RelationBase):
|
||||
field_b = models.CharField(max_length=10)
|
||||
class RelationBC(RelationB):
|
||||
field_c = models.CharField(max_length=10)
|
||||
|
||||
class RelatingModel(models.Model):
|
||||
many2many = models.ManyToManyField(Model2A)
|
||||
|
||||
class One2OneRelatingModel(PolymorphicModel):
|
||||
one2one = models.OneToOneField(Model2A)
|
||||
field1 = models.CharField(max_length=10)
|
||||
|
||||
class One2OneRelatingModelDerived(One2OneRelatingModel):
|
||||
field2 = models.CharField(max_length=10)
|
||||
|
||||
class MyManagerQuerySet(PolymorphicQuerySet):
|
||||
def my_queryset_foo(self):
|
||||
return self.all() # Just a method to prove the existance of the custom queryset.
|
||||
|
||||
class MyManager(PolymorphicManager):
|
||||
queryset_class = MyManagerQuerySet
|
||||
|
||||
def get_query_set(self):
|
||||
return super(MyManager, self).get_query_set().order_by('-field1')
|
||||
|
||||
class ModelWithMyManager(ShowFieldTypeAndContent, Model2A):
|
||||
objects = MyManager()
|
||||
field4 = models.CharField(max_length=10)
|
||||
|
||||
class MROBase1(ShowFieldType, PolymorphicModel):
|
||||
objects = MyManager()
|
||||
field1 = models.CharField(max_length=10) # needed as MyManager uses it
|
||||
class MROBase2(MROBase1):
|
||||
pass # Django vanilla inheritance does not inherit MyManager as _default_manager here
|
||||
class MROBase3(models.Model):
|
||||
objects = PolymorphicManager()
|
||||
class MRODerived(MROBase2, MROBase3):
|
||||
pass
|
||||
|
||||
class ParentModelWithManager(PolymorphicModel):
|
||||
pass
|
||||
class ChildModelWithManager(PolymorphicModel):
|
||||
# Also test whether foreign keys receive the manager:
|
||||
fk = models.ForeignKey(ParentModelWithManager, related_name='childmodel_set')
|
||||
objects = MyManager()
|
||||
|
||||
|
||||
class PlainMyManagerQuerySet(QuerySet):
|
||||
def my_queryset_foo(self):
|
||||
return self.all() # Just a method to prove the existance of the custom queryset.
|
||||
|
||||
class PlainMyManager(models.Manager):
|
||||
def my_queryset_foo(self):
|
||||
return self.get_query_set().my_queryset_foo()
|
||||
|
||||
def get_query_set(self):
|
||||
return PlainMyManagerQuerySet(self.model, using=self._db)
|
||||
|
||||
class PlainParentModelWithManager(models.Model):
|
||||
pass
|
||||
|
||||
class PlainChildModelWithManager(models.Model):
|
||||
fk = models.ForeignKey(PlainParentModelWithManager, related_name='childmodel_set')
|
||||
objects = PlainMyManager()
|
||||
|
||||
|
||||
class MgrInheritA(models.Model):
|
||||
mgrA = models.Manager()
|
||||
mgrA2 = models.Manager()
|
||||
field1 = models.CharField(max_length=10)
|
||||
class MgrInheritB(MgrInheritA):
|
||||
mgrB = models.Manager()
|
||||
field2 = models.CharField(max_length=10)
|
||||
class MgrInheritC(ShowFieldTypeAndContent, MgrInheritB):
|
||||
pass
|
||||
|
||||
class BlogBase(ShowFieldTypeAndContent, PolymorphicModel):
|
||||
name = models.CharField(max_length=10)
|
||||
class BlogA(BlogBase):
|
||||
info = models.CharField(max_length=10)
|
||||
class BlogB(BlogBase):
|
||||
pass
|
||||
class BlogEntry(ShowFieldTypeAndContent, PolymorphicModel):
|
||||
blog = models.ForeignKey(BlogA)
|
||||
text = models.CharField(max_length=10)
|
||||
|
||||
class BlogEntry_limit_choices_to(ShowFieldTypeAndContent, PolymorphicModel):
|
||||
blog = models.ForeignKey(BlogBase)
|
||||
text = models.CharField(max_length=10)
|
||||
|
||||
class ModelFieldNameTest(ShowFieldType, PolymorphicModel):
|
||||
modelfieldnametest = models.CharField(max_length=10)
|
||||
|
||||
class InitTestModel(ShowFieldType, PolymorphicModel):
|
||||
bar = models.CharField(max_length=100)
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['bar'] = self.x()
|
||||
super(InitTestModel, self).__init__(*args, **kwargs)
|
||||
class InitTestModelSubclass(InitTestModel):
|
||||
def x(self):
|
||||
return 'XYZ'
|
||||
|
||||
# models from github issue
|
||||
class Top(PolymorphicModel):
|
||||
name = models.CharField(max_length=50)
|
||||
class Meta:
|
||||
ordering = ('pk',)
|
||||
class Middle(Top):
|
||||
description = models.TextField()
|
||||
class Bottom(Middle):
|
||||
author = models.CharField(max_length=50)
|
||||
|
||||
class UUIDProject(ShowFieldTypeAndContent, PolymorphicModel):
|
||||
uuid_primary_key = UUIDField(primary_key = True)
|
||||
topic = models.CharField(max_length = 30)
|
||||
class UUIDArtProject(UUIDProject):
|
||||
artist = models.CharField(max_length = 30)
|
||||
class UUIDResearchProject(UUIDProject):
|
||||
supervisor = models.CharField(max_length = 30)
|
||||
|
||||
class UUIDPlainA(models.Model):
|
||||
uuid_primary_key = UUIDField(primary_key = True)
|
||||
field1 = models.CharField(max_length=10)
|
||||
class UUIDPlainB(UUIDPlainA):
|
||||
field2 = models.CharField(max_length=10)
|
||||
class UUIDPlainC(UUIDPlainB):
|
||||
field3 = models.CharField(max_length=10)
|
||||
|
||||
# base -> proxy
|
||||
class ProxyBase(PolymorphicModel):
|
||||
some_data = models.CharField(max_length=128)
|
||||
class ProxyChild(ProxyBase):
|
||||
class Meta:
|
||||
proxy = True
|
||||
|
||||
# base -> proxy -> real models
|
||||
class ProxiedBase(ShowFieldTypeAndContent, PolymorphicModel):
|
||||
name = models.CharField(max_length=10)
|
||||
class ProxyModelBase(ProxiedBase):
|
||||
class Meta:
|
||||
proxy = True
|
||||
class ProxyModelA(ProxyModelBase):
|
||||
field1 = models.CharField(max_length=10)
|
||||
class ProxyModelB(ProxyModelBase):
|
||||
field2 = models.CharField(max_length=10)
|
||||
|
||||
|
||||
# test bad field name
|
||||
#class TestBadFieldModel(ShowFieldType, PolymorphicModel):
|
||||
# instance_of = models.CharField(max_length=10)
|
||||
|
||||
# validation error: "polymorphic.relatednameclash: Accessor for field 'polymorphic_ctype' clashes
|
||||
# with related field 'ContentType.relatednameclash_set'." (reported by Andrew Ingram)
|
||||
# fixed with related_name
|
||||
class RelatedNameClash(ShowFieldType, PolymorphicModel):
|
||||
ctype = models.ForeignKey(ContentType, null=True, editable=False)
|
||||
|
||||
|
||||
class PolymorphicTests(TestCase):
|
||||
"""
|
||||
The test suite
|
||||
"""
|
||||
def test_diamond_inheritance(self):
|
||||
# Django diamond problem
|
||||
# https://code.djangoproject.com/ticket/10808
|
||||
o1 = DiamondXY.objects.create(field_b='b', field_x='x', field_y='y')
|
||||
o2 = DiamondXY.objects.get()
|
||||
|
||||
if o2.field_b != 'b':
|
||||
print('')
|
||||
print('# known django model inheritance diamond problem detected')
|
||||
print('DiamondXY fields 1: field_b "{0}", field_x "{1}", field_y "{2}"'.format(o1.field_b, o1.field_x, o1.field_y))
|
||||
print('DiamondXY fields 2: field_b "{0}", field_x "{1}", field_y "{2}"'.format(o2.field_b, o2.field_x, o2.field_y))
|
||||
|
||||
|
||||
def test_annotate_aggregate_order(self):
|
||||
# create a blog of type BlogA
|
||||
# create two blog entries in BlogA
|
||||
# create some blogs of type BlogB to make the BlogBase table data really polymorphic
|
||||
blog = BlogA.objects.create(name='B1', info='i1')
|
||||
blog.blogentry_set.create(text='bla')
|
||||
BlogEntry.objects.create(blog=blog, text='bla2')
|
||||
BlogB.objects.create(name='Bb1')
|
||||
BlogB.objects.create(name='Bb2')
|
||||
BlogB.objects.create(name='Bb3')
|
||||
|
||||
qs = BlogBase.objects.annotate(entrycount=Count('BlogA___blogentry'))
|
||||
self.assertEqual(len(qs), 4)
|
||||
|
||||
for o in qs:
|
||||
if o.name == 'B1':
|
||||
self.assertEqual(o.entrycount, 2)
|
||||
else:
|
||||
self.assertEqual(o.entrycount, 0)
|
||||
|
||||
x = BlogBase.objects.aggregate(entrycount=Count('BlogA___blogentry'))
|
||||
self.assertEqual(x['entrycount'], 2)
|
||||
|
||||
# create some more blogs for next test
|
||||
BlogA.objects.create(name='B2', info='i2')
|
||||
BlogA.objects.create(name='B3', info='i3')
|
||||
BlogA.objects.create(name='B4', info='i4')
|
||||
BlogA.objects.create(name='B5', info='i5')
|
||||
|
||||
# test ordering for field in all entries
|
||||
expected = '''
|
||||
[ <BlogB: id 4, name (CharField) "Bb3">,
|
||||
<BlogB: id 3, name (CharField) "Bb2">,
|
||||
<BlogB: id 2, name (CharField) "Bb1">,
|
||||
<BlogA: id 8, name (CharField) "B5", info (CharField) "i5">,
|
||||
<BlogA: id 7, name (CharField) "B4", info (CharField) "i4">,
|
||||
<BlogA: id 6, name (CharField) "B3", info (CharField) "i3">,
|
||||
<BlogA: id 5, name (CharField) "B2", info (CharField) "i2">,
|
||||
<BlogA: id 1, name (CharField) "B1", info (CharField) "i1"> ]'''
|
||||
x = '\n' + repr(BlogBase.objects.order_by('-name'))
|
||||
self.assertEqual(x, expected)
|
||||
|
||||
# test ordering for field in one subclass only
|
||||
# MySQL and SQLite return this order
|
||||
expected1='''
|
||||
[ <BlogA: id 8, name (CharField) "B5", info (CharField) "i5">,
|
||||
<BlogA: id 7, name (CharField) "B4", info (CharField) "i4">,
|
||||
<BlogA: id 6, name (CharField) "B3", info (CharField) "i3">,
|
||||
<BlogA: id 5, name (CharField) "B2", info (CharField) "i2">,
|
||||
<BlogA: id 1, name (CharField) "B1", info (CharField) "i1">,
|
||||
<BlogB: id 2, name (CharField) "Bb1">,
|
||||
<BlogB: id 3, name (CharField) "Bb2">,
|
||||
<BlogB: id 4, name (CharField) "Bb3"> ]'''
|
||||
|
||||
# PostgreSQL returns this order
|
||||
expected2='''
|
||||
[ <BlogB: id 2, name (CharField) "Bb1">,
|
||||
<BlogB: id 3, name (CharField) "Bb2">,
|
||||
<BlogB: id 4, name (CharField) "Bb3">,
|
||||
<BlogA: id 8, name (CharField) "B5", info (CharField) "i5">,
|
||||
<BlogA: id 7, name (CharField) "B4", info (CharField) "i4">,
|
||||
<BlogA: id 6, name (CharField) "B3", info (CharField) "i3">,
|
||||
<BlogA: id 5, name (CharField) "B2", info (CharField) "i2">,
|
||||
<BlogA: id 1, name (CharField) "B1", info (CharField) "i1"> ]'''
|
||||
|
||||
x = '\n' + repr(BlogBase.objects.order_by('-BlogA___info'))
|
||||
self.assertTrue(x == expected1 or x == expected2)
|
||||
|
||||
|
||||
def test_limit_choices_to(self):
|
||||
"""
|
||||
this is not really a testcase, as limit_choices_to only affects the Django admin
|
||||
"""
|
||||
# create a blog of type BlogA
|
||||
blog_a = BlogA.objects.create(name='aa', info='aa')
|
||||
blog_b = BlogB.objects.create(name='bb')
|
||||
# create two blog entries
|
||||
entry1 = BlogEntry_limit_choices_to.objects.create(blog=blog_b, text='bla2')
|
||||
entry2 = BlogEntry_limit_choices_to.objects.create(blog=blog_b, text='bla2')
|
||||
|
||||
|
||||
def test_primary_key_custom_field_problem(self):
|
||||
"""
|
||||
object retrieval problem occuring with some custom primary key fields (UUIDField as test case)
|
||||
"""
|
||||
UUIDProject.objects.create(topic="John's gathering")
|
||||
UUIDArtProject.objects.create(topic="Sculpting with Tim", artist="T. Turner")
|
||||
UUIDResearchProject.objects.create(topic="Swallow Aerodynamics", supervisor="Dr. Winter")
|
||||
|
||||
qs = UUIDProject.objects.all()
|
||||
ol = list(qs)
|
||||
a = qs[0]
|
||||
b = qs[1]
|
||||
c = qs[2]
|
||||
self.assertEqual(len(qs), 3)
|
||||
self.assertIsInstance(a.uuid_primary_key, uuid.UUID)
|
||||
self.assertIsInstance(a.pk, uuid.UUID)
|
||||
|
||||
res = re.sub(' "(.*?)..", topic',', topic', repr(qs))
|
||||
res_exp = """[ <UUIDProject: uuid_primary_key (UUIDField/pk), topic (CharField) "John's gathering">,
|
||||
<UUIDArtProject: uuid_primary_key (UUIDField/pk), topic (CharField) "Sculpting with Tim", artist (CharField) "T. Turner">,
|
||||
<UUIDResearchProject: uuid_primary_key (UUIDField/pk), topic (CharField) "Swallow Aerodynamics", supervisor (CharField) "Dr. Winter"> ]"""
|
||||
self.assertEqual(res, res_exp)
|
||||
#if (a.pk!= uuid.UUID or c.pk!= uuid.UUID):
|
||||
# print()
|
||||
# print('# known inconstency with custom primary key field detected (django problem?)')
|
||||
|
||||
a = UUIDPlainA.objects.create(field1='A1')
|
||||
b = UUIDPlainB.objects.create(field1='B1', field2='B2')
|
||||
c = UUIDPlainC.objects.create(field1='C1', field2='C2', field3='C3')
|
||||
qs = UUIDPlainA.objects.all()
|
||||
if a.pk!= uuid.UUID or c.pk!= uuid.UUID:
|
||||
print('')
|
||||
print('# known type inconstency with custom primary key field detected (django problem?)')
|
||||
|
||||
|
||||
def create_model2abcd(self):
|
||||
"""
|
||||
Create the chain of objects of Model2,
|
||||
this is reused in various tests.
|
||||
"""
|
||||
Model2A.objects.create(field1='A1')
|
||||
Model2B.objects.create(field1='B1', field2='B2')
|
||||
Model2C.objects.create(field1='C1', field2='C2', field3='C3')
|
||||
Model2D.objects.create(field1='D1', field2='D2', field3='D3', field4='D4')
|
||||
|
||||
|
||||
def test_simple_inheritance(self):
|
||||
self.create_model2abcd()
|
||||
|
||||
objects = list(Model2A.objects.all())
|
||||
self.assertEqual(repr(objects[0]), '<Model2A: id 1, field1 (CharField)>')
|
||||
self.assertEqual(repr(objects[1]), '<Model2B: id 2, field1 (CharField), field2 (CharField)>')
|
||||
self.assertEqual(repr(objects[2]), '<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField)>')
|
||||
self.assertEqual(repr(objects[3]), '<Model2D: id 4, field1 (CharField), field2 (CharField), field3 (CharField), field4 (CharField)>')
|
||||
|
||||
|
||||
def test_manual_get_real_instance(self):
|
||||
self.create_model2abcd()
|
||||
|
||||
o = Model2A.objects.non_polymorphic().get(field1='C1')
|
||||
self.assertEqual(repr(o.get_real_instance()), '<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField)>')
|
||||
|
||||
|
||||
def test_non_polymorphic(self):
|
||||
self.create_model2abcd()
|
||||
|
||||
objects = list(Model2A.objects.all().non_polymorphic())
|
||||
self.assertEqual(repr(objects[0]), '<Model2A: id 1, field1 (CharField)>')
|
||||
self.assertEqual(repr(objects[1]), '<Model2A: id 2, field1 (CharField)>')
|
||||
self.assertEqual(repr(objects[2]), '<Model2A: id 3, field1 (CharField)>')
|
||||
self.assertEqual(repr(objects[3]), '<Model2A: id 4, field1 (CharField)>')
|
||||
|
||||
|
||||
def test_get_real_instances(self):
|
||||
self.create_model2abcd()
|
||||
qs = Model2A.objects.all().non_polymorphic()
|
||||
|
||||
# from queryset
|
||||
objects = qs.get_real_instances()
|
||||
self.assertEqual(repr(objects[0]), '<Model2A: id 1, field1 (CharField)>')
|
||||
self.assertEqual(repr(objects[1]), '<Model2B: id 2, field1 (CharField), field2 (CharField)>')
|
||||
self.assertEqual(repr(objects[2]), '<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField)>')
|
||||
self.assertEqual(repr(objects[3]), '<Model2D: id 4, field1 (CharField), field2 (CharField), field3 (CharField), field4 (CharField)>')
|
||||
|
||||
# from a manual list
|
||||
objects = Model2A.objects.get_real_instances(list(qs))
|
||||
self.assertEqual(repr(objects[0]), '<Model2A: id 1, field1 (CharField)>')
|
||||
self.assertEqual(repr(objects[1]), '<Model2B: id 2, field1 (CharField), field2 (CharField)>')
|
||||
self.assertEqual(repr(objects[2]), '<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField)>')
|
||||
self.assertEqual(repr(objects[3]), '<Model2D: id 4, field1 (CharField), field2 (CharField), field3 (CharField), field4 (CharField)>')
|
||||
|
||||
|
||||
def test_translate_polymorphic_q_object(self):
|
||||
self.create_model2abcd()
|
||||
|
||||
q = Model2A.translate_polymorphic_Q_object(Q(instance_of=Model2C))
|
||||
objects = Model2A.objects.filter(q)
|
||||
self.assertEqual(repr(objects[0]), '<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField)>')
|
||||
self.assertEqual(repr(objects[1]), '<Model2D: id 4, field1 (CharField), field2 (CharField), field3 (CharField), field4 (CharField)>')
|
||||
|
||||
|
||||
def test_base_manager(self):
|
||||
def show_base_manager(model):
|
||||
return "{0} {1}".format(
|
||||
repr(type(model._base_manager)),
|
||||
repr(model._base_manager.model)
|
||||
)
|
||||
|
||||
self.assertEqual(show_base_manager(PlainA), "<class 'django.db.models.manager.Manager'> <class 'polymorphic.tests.PlainA'>")
|
||||
self.assertEqual(show_base_manager(PlainB), "<class 'django.db.models.manager.Manager'> <class 'polymorphic.tests.PlainB'>")
|
||||
self.assertEqual(show_base_manager(PlainC), "<class 'django.db.models.manager.Manager'> <class 'polymorphic.tests.PlainC'>")
|
||||
|
||||
self.assertEqual(show_base_manager(Model2A), "<class 'polymorphic.manager.PolymorphicManager'> <class 'polymorphic.tests.Model2A'>")
|
||||
self.assertEqual(show_base_manager(Model2B), "<class 'django.db.models.manager.Manager'> <class 'polymorphic.tests.Model2B'>")
|
||||
self.assertEqual(show_base_manager(Model2C), "<class 'django.db.models.manager.Manager'> <class 'polymorphic.tests.Model2C'>")
|
||||
|
||||
self.assertEqual(show_base_manager(One2OneRelatingModel), "<class 'polymorphic.manager.PolymorphicManager'> <class 'polymorphic.tests.One2OneRelatingModel'>")
|
||||
self.assertEqual(show_base_manager(One2OneRelatingModelDerived), "<class 'django.db.models.manager.Manager'> <class 'polymorphic.tests.One2OneRelatingModelDerived'>")
|
||||
|
||||
|
||||
def test_foreignkey_field(self):
|
||||
self.create_model2abcd()
|
||||
|
||||
object2a = Model2A.base_objects.get(field1='C1')
|
||||
self.assertEqual(repr(object2a.model2b), '<Model2B: id 3, field1 (CharField), field2 (CharField)>')
|
||||
|
||||
object2b = Model2B.base_objects.get(field1='C1')
|
||||
self.assertEqual(repr(object2b.model2c), '<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField)>')
|
||||
|
||||
|
||||
def test_onetoone_field(self):
|
||||
self.create_model2abcd()
|
||||
|
||||
a = Model2A.base_objects.get(field1='C1')
|
||||
b = One2OneRelatingModelDerived.objects.create(one2one=a, field1='f1', field2='f2')
|
||||
|
||||
# this result is basically wrong, probably due to Django cacheing (we used base_objects), but should not be a problem
|
||||
self.assertEqual(repr(b.one2one), '<Model2A: id 3, field1 (CharField)>')
|
||||
|
||||
c = One2OneRelatingModelDerived.objects.get(field1='f1')
|
||||
self.assertEqual(repr(c.one2one), '<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField)>')
|
||||
self.assertEqual(repr(a.one2onerelatingmodel), '<One2OneRelatingModelDerived: One2OneRelatingModelDerived object>')
|
||||
|
||||
|
||||
def test_manytomany_field(self):
|
||||
# Model 1
|
||||
o = ModelShow1.objects.create(field1='abc')
|
||||
o.m2m.add(o)
|
||||
o.save()
|
||||
self.assertEqual(repr(ModelShow1.objects.all()), '[ <ModelShow1: id 1, field1 (CharField), m2m (ManyToManyField)> ]')
|
||||
|
||||
# Model 2
|
||||
o = ModelShow2.objects.create(field1='abc')
|
||||
o.m2m.add(o)
|
||||
o.save()
|
||||
self.assertEqual(repr(ModelShow2.objects.all()), '[ <ModelShow2: id 1, field1 "abc", m2m 1> ]')
|
||||
|
||||
# Model 3
|
||||
o=ModelShow3.objects.create(field1='abc')
|
||||
o.m2m.add(o)
|
||||
o.save()
|
||||
self.assertEqual(repr(ModelShow3.objects.all()), '[ <ModelShow3: id 1, field1 (CharField) "abc", m2m (ManyToManyField) 1> ]')
|
||||
self.assertEqual(repr(ModelShow1.objects.all().annotate(Count('m2m'))), '[ <ModelShow1: id 1, field1 (CharField), m2m (ManyToManyField) - Ann: m2m__count (int)> ]')
|
||||
self.assertEqual(repr(ModelShow2.objects.all().annotate(Count('m2m'))), '[ <ModelShow2: id 1, field1 "abc", m2m 1 - Ann: m2m__count 1> ]')
|
||||
self.assertEqual(repr(ModelShow3.objects.all().annotate(Count('m2m'))), '[ <ModelShow3: id 1, field1 (CharField) "abc", m2m (ManyToManyField) 1 - Ann: m2m__count (int) 1> ]')
|
||||
|
||||
# no pretty printing
|
||||
ModelShow1_plain.objects.create(field1='abc')
|
||||
ModelShow2_plain.objects.create(field1='abc', field2='def')
|
||||
self.assertEqual(repr(ModelShow1_plain.objects.all()), '[<ModelShow1_plain: ModelShow1_plain object>, <ModelShow2_plain: ModelShow2_plain object>]')
|
||||
|
||||
|
||||
def test_extra_method(self):
|
||||
self.create_model2abcd()
|
||||
|
||||
objects = list(Model2A.objects.extra(where=['id IN (2, 3)']))
|
||||
self.assertEqual(repr(objects[0]), '<Model2B: id 2, field1 (CharField), field2 (CharField)>')
|
||||
self.assertEqual(repr(objects[1]), '<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField)>')
|
||||
|
||||
objects = Model2A.objects.extra(select={"select_test": "field1 = 'A1'"}, where=["field1 = 'A1' OR field1 = 'B1'"], order_by=['-id'])
|
||||
self.assertEqual(repr(objects[0]), '<Model2B: id 2, field1 (CharField), field2 (CharField) - Extra: select_test (int)>')
|
||||
self.assertEqual(repr(objects[1]), '<Model2A: id 1, field1 (CharField) - Extra: select_test (int)>')
|
||||
self.assertEqual(len(objects), 2) # Placed after the other tests, only verifying whether there are no more additional objects.
|
||||
|
||||
ModelExtraA.objects.create(field1='A1')
|
||||
ModelExtraB.objects.create(field1='B1', field2='B2')
|
||||
ModelExtraC.objects.create(field1='C1', field2='C2', field3='C3')
|
||||
ModelExtraExternal.objects.create(topic='extra1')
|
||||
ModelExtraExternal.objects.create(topic='extra2')
|
||||
ModelExtraExternal.objects.create(topic='extra3')
|
||||
objects = ModelExtraA.objects.extra(tables=["polymorphic_modelextraexternal"], select={"topic":"polymorphic_modelextraexternal.topic"}, where=["polymorphic_modelextraa.id = polymorphic_modelextraexternal.id"])
|
||||
if six.PY3:
|
||||
self.assertEqual(repr(objects[0]), '<ModelExtraA: id 1, field1 (CharField) "A1" - Extra: topic (str) "extra1">')
|
||||
self.assertEqual(repr(objects[1]), '<ModelExtraB: id 2, field1 (CharField) "B1", field2 (CharField) "B2" - Extra: topic (str) "extra2">')
|
||||
self.assertEqual(repr(objects[2]), '<ModelExtraC: id 3, field1 (CharField) "C1", field2 (CharField) "C2", field3 (CharField) "C3" - Extra: topic (str) "extra3">')
|
||||
else:
|
||||
self.assertEqual(repr(objects[0]), '<ModelExtraA: id 1, field1 (CharField) "A1" - Extra: topic (unicode) "extra1">')
|
||||
self.assertEqual(repr(objects[1]), '<ModelExtraB: id 2, field1 (CharField) "B1", field2 (CharField) "B2" - Extra: topic (unicode) "extra2">')
|
||||
self.assertEqual(repr(objects[2]), '<ModelExtraC: id 3, field1 (CharField) "C1", field2 (CharField) "C2", field3 (CharField) "C3" - Extra: topic (unicode) "extra3">')
|
||||
self.assertEqual(len(objects), 3)
|
||||
|
||||
|
||||
def test_instance_of_filter(self):
|
||||
self.create_model2abcd()
|
||||
|
||||
objects = Model2A.objects.instance_of(Model2B)
|
||||
self.assertEqual(repr(objects[0]), '<Model2B: id 2, field1 (CharField), field2 (CharField)>')
|
||||
self.assertEqual(repr(objects[1]), '<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField)>')
|
||||
self.assertEqual(repr(objects[2]), '<Model2D: id 4, field1 (CharField), field2 (CharField), field3 (CharField), field4 (CharField)>')
|
||||
self.assertEqual(len(objects), 3)
|
||||
|
||||
objects = Model2A.objects.filter(instance_of=Model2B)
|
||||
self.assertEqual(repr(objects[0]), '<Model2B: id 2, field1 (CharField), field2 (CharField)>')
|
||||
self.assertEqual(repr(objects[1]), '<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField)>')
|
||||
self.assertEqual(repr(objects[2]), '<Model2D: id 4, field1 (CharField), field2 (CharField), field3 (CharField), field4 (CharField)>')
|
||||
self.assertEqual(len(objects), 3)
|
||||
|
||||
objects = Model2A.objects.filter(Q(instance_of=Model2B))
|
||||
self.assertEqual(repr(objects[0]), '<Model2B: id 2, field1 (CharField), field2 (CharField)>')
|
||||
self.assertEqual(repr(objects[1]), '<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField)>')
|
||||
self.assertEqual(repr(objects[2]), '<Model2D: id 4, field1 (CharField), field2 (CharField), field3 (CharField), field4 (CharField)>')
|
||||
self.assertEqual(len(objects), 3)
|
||||
|
||||
objects = Model2A.objects.not_instance_of(Model2B)
|
||||
self.assertEqual(repr(objects[0]), '<Model2A: id 1, field1 (CharField)>')
|
||||
self.assertEqual(len(objects), 1)
|
||||
|
||||
|
||||
def test_polymorphic___filter(self):
|
||||
self.create_model2abcd()
|
||||
|
||||
objects = Model2A.objects.filter(Q( Model2B___field2='B2') | Q( Model2C___field3='C3'))
|
||||
self.assertEqual(len(objects), 2)
|
||||
self.assertEqual(repr(objects[0]), '<Model2B: id 2, field1 (CharField), field2 (CharField)>')
|
||||
self.assertEqual(repr(objects[1]), '<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField)>')
|
||||
|
||||
|
||||
def test_delete(self):
|
||||
self.create_model2abcd()
|
||||
|
||||
oa = Model2A.objects.get(id=2)
|
||||
self.assertEqual(repr(oa), '<Model2B: id 2, field1 (CharField), field2 (CharField)>')
|
||||
self.assertEqual(Model2A.objects.count(), 4)
|
||||
|
||||
oa.delete()
|
||||
objects = Model2A.objects.all()
|
||||
self.assertEqual(repr(objects[0]), '<Model2A: id 1, field1 (CharField)>')
|
||||
self.assertEqual(repr(objects[1]), '<Model2C: id 3, field1 (CharField), field2 (CharField), field3 (CharField)>')
|
||||
self.assertEqual(repr(objects[2]), '<Model2D: id 4, field1 (CharField), field2 (CharField), field3 (CharField), field4 (CharField)>')
|
||||
self.assertEqual(len(objects), 3)
|
||||
|
||||
|
||||
def test_combine_querysets(self):
|
||||
ModelX.objects.create(field_x='x')
|
||||
ModelY.objects.create(field_y='y')
|
||||
|
||||
qs = Base.objects.instance_of(ModelX) | Base.objects.instance_of(ModelY)
|
||||
self.assertEqual(repr(qs[0]), '<ModelX: id 1, field_b (CharField), field_x (CharField)>')
|
||||
self.assertEqual(repr(qs[1]), '<ModelY: id 2, field_b (CharField), field_y (CharField)>')
|
||||
self.assertEqual(len(qs), 2)
|
||||
|
||||
|
||||
def test_multiple_inheritance(self):
|
||||
# multiple inheritance, subclassing third party models (mix PolymorphicModel with models.Model)
|
||||
|
||||
Enhance_Base.objects.create(field_b='b-base')
|
||||
Enhance_Inherit.objects.create(field_b='b-inherit', field_p='p', field_i='i')
|
||||
|
||||
qs = Enhance_Base.objects.all()
|
||||
self.assertEqual(repr(qs[0]), '<Enhance_Base: id 1, field_b (CharField) "b-base">')
|
||||
self.assertEqual(repr(qs[1]), '<Enhance_Inherit: id 2, field_b (CharField) "b-inherit", field_p (CharField) "p", field_i (CharField) "i">')
|
||||
self.assertEqual(len(qs), 2)
|
||||
|
||||
|
||||
def test_relation_base(self):
|
||||
# ForeignKey, ManyToManyField
|
||||
obase = RelationBase.objects.create(field_base='base')
|
||||
oa = RelationA.objects.create(field_base='A1', field_a='A2', fk=obase)
|
||||
ob = RelationB.objects.create(field_base='B1', field_b='B2', fk=oa)
|
||||
oc = RelationBC.objects.create(field_base='C1', field_b='C2', field_c='C3', fk=oa)
|
||||
oa.m2m.add(oa)
|
||||
oa.m2m.add(ob)
|
||||
|
||||
objects = RelationBase.objects.all()
|
||||
self.assertEqual(repr(objects[0]), '<RelationBase: id 1, field_base (CharField) "base", fk (ForeignKey) None, m2m (ManyToManyField) 0>')
|
||||
self.assertEqual(repr(objects[1]), '<RelationA: id 2, field_base (CharField) "A1", fk (ForeignKey) RelationBase, field_a (CharField) "A2", m2m (ManyToManyField) 2>')
|
||||
self.assertEqual(repr(objects[2]), '<RelationB: id 3, field_base (CharField) "B1", fk (ForeignKey) RelationA, field_b (CharField) "B2", m2m (ManyToManyField) 1>')
|
||||
self.assertEqual(repr(objects[3]), '<RelationBC: id 4, field_base (CharField) "C1", fk (ForeignKey) RelationA, field_b (CharField) "C2", field_c (CharField) "C3", m2m (ManyToManyField) 0>')
|
||||
self.assertEqual(len(objects), 4)
|
||||
|
||||
oa = RelationBase.objects.get(id=2)
|
||||
self.assertEqual(repr(oa.fk), '<RelationBase: id 1, field_base (CharField) "base", fk (ForeignKey) None, m2m (ManyToManyField) 0>')
|
||||
|
||||
objects = oa.relationbase_set.all()
|
||||
self.assertEqual(repr(objects[0]), '<RelationB: id 3, field_base (CharField) "B1", fk (ForeignKey) RelationA, field_b (CharField) "B2", m2m (ManyToManyField) 1>')
|
||||
self.assertEqual(repr(objects[1]), '<RelationBC: id 4, field_base (CharField) "C1", fk (ForeignKey) RelationA, field_b (CharField) "C2", field_c (CharField) "C3", m2m (ManyToManyField) 0>')
|
||||
self.assertEqual(len(objects), 2)
|
||||
|
||||
ob = RelationBase.objects.get(id=3)
|
||||
self.assertEqual(repr(ob.fk), '<RelationA: id 2, field_base (CharField) "A1", fk (ForeignKey) RelationBase, field_a (CharField) "A2", m2m (ManyToManyField) 2>')
|
||||
|
||||
oa = RelationA.objects.get()
|
||||
objects = oa.m2m.all()
|
||||
self.assertEqual(repr(objects[0]), '<RelationA: id 2, field_base (CharField) "A1", fk (ForeignKey) RelationBase, field_a (CharField) "A2", m2m (ManyToManyField) 2>')
|
||||
self.assertEqual(repr(objects[1]), '<RelationB: id 3, field_base (CharField) "B1", fk (ForeignKey) RelationA, field_b (CharField) "B2", m2m (ManyToManyField) 1>')
|
||||
self.assertEqual(len(objects), 2)
|
||||
|
||||
|
||||
def test_user_defined_manager(self):
|
||||
self.create_model2abcd()
|
||||
ModelWithMyManager.objects.create(field1='D1a', field4='D4a')
|
||||
ModelWithMyManager.objects.create(field1='D1b', field4='D4b')
|
||||
|
||||
objects = ModelWithMyManager.objects.all() # MyManager should reverse the sorting of field1
|
||||
self.assertEqual(repr(objects[0]), '<ModelWithMyManager: id 6, field1 (CharField) "D1b", field4 (CharField) "D4b">')
|
||||
self.assertEqual(repr(objects[1]), '<ModelWithMyManager: id 5, field1 (CharField) "D1a", field4 (CharField) "D4a">')
|
||||
self.assertEqual(len(objects), 2)
|
||||
|
||||
self.assertIs(type(ModelWithMyManager.objects), MyManager)
|
||||
self.assertIs(type(ModelWithMyManager._default_manager), MyManager)
|
||||
self.assertIs(type(ModelWithMyManager.base_objects), models.Manager)
|
||||
|
||||
|
||||
def test_manager_inheritance(self):
|
||||
# by choice of MRO, should be MyManager from MROBase1.
|
||||
self.assertIs(type(MRODerived.objects), MyManager)
|
||||
|
||||
# check for correct default manager
|
||||
self.assertIs(type(MROBase1._default_manager), MyManager)
|
||||
|
||||
# Django vanilla inheritance does not inherit MyManager as _default_manager here
|
||||
self.assertIs(type(MROBase2._default_manager), MyManager)
|
||||
|
||||
|
||||
def test_queryset_assignment(self):
|
||||
# This is just a consistency check for now, testing standard Django behavior.
|
||||
parent = PlainParentModelWithManager.objects.create()
|
||||
child = PlainChildModelWithManager.objects.create(fk=parent)
|
||||
self.assertIs(type(PlainParentModelWithManager._default_manager), models.Manager)
|
||||
self.assertIs(type(PlainChildModelWithManager._default_manager), PlainMyManager)
|
||||
self.assertIs(type(PlainChildModelWithManager.objects), PlainMyManager)
|
||||
self.assertIs(type(PlainChildModelWithManager.objects.all()), PlainMyManagerQuerySet)
|
||||
|
||||
# A related set is created using the model's _default_manager, so does gain extra methods.
|
||||
self.assertIs(type(parent.childmodel_set.my_queryset_foo()), PlainMyManagerQuerySet)
|
||||
|
||||
# For polymorphic models, the same should happen.
|
||||
parent = ParentModelWithManager.objects.create()
|
||||
child = ChildModelWithManager.objects.create(fk=parent)
|
||||
self.assertIs(type(ParentModelWithManager._default_manager), PolymorphicManager)
|
||||
self.assertIs(type(ChildModelWithManager._default_manager), MyManager)
|
||||
self.assertIs(type(ChildModelWithManager.objects), MyManager)
|
||||
self.assertIs(type(ChildModelWithManager.objects.my_queryset_foo()), MyManagerQuerySet)
|
||||
|
||||
# A related set is created using the model's _default_manager, so does gain extra methods.
|
||||
self.assertIs(type(parent.childmodel_set.my_queryset_foo()), MyManagerQuerySet)
|
||||
|
||||
|
||||
def test_proxy_models(self):
|
||||
# prepare some data
|
||||
for data in ('bleep bloop', 'I am a', 'computer'):
|
||||
ProxyChild.objects.create(some_data=data)
|
||||
|
||||
# this caches ContentType queries so they don't interfere with our query counts later
|
||||
list(ProxyBase.objects.all())
|
||||
|
||||
# one query per concrete class
|
||||
with self.assertNumQueries(1):
|
||||
items = list(ProxyBase.objects.all())
|
||||
|
||||
self.assertIsInstance(items[0], ProxyChild)
|
||||
|
||||
|
||||
def test_content_types_for_proxy_models(self):
|
||||
"""Checks if ContentType is capable of returning proxy models."""
|
||||
from django.db.models import Model
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
ct = ContentType.objects.get_for_model(ProxyChild, for_concrete_model=False)
|
||||
self.assertEqual(ProxyChild, ct.model_class())
|
||||
|
||||
|
||||
def test_proxy_model_inheritance(self):
|
||||
"""
|
||||
Polymorphic abilities should also work when the base model is a proxy object.
|
||||
"""
|
||||
# The managers should point to the proper objects.
|
||||
# otherwise, the whole excersise is pointless.
|
||||
self.assertEqual(ProxiedBase.objects.model, ProxiedBase)
|
||||
self.assertEqual(ProxyModelBase.objects.model, ProxyModelBase)
|
||||
self.assertEqual(ProxyModelA.objects.model, ProxyModelA)
|
||||
self.assertEqual(ProxyModelB.objects.model, ProxyModelB)
|
||||
|
||||
# Create objects
|
||||
ProxyModelA.objects.create(name="object1")
|
||||
ProxyModelB.objects.create(name="object2", field2="bb")
|
||||
|
||||
# Getting single objects
|
||||
object1 = ProxyModelBase.objects.get(name='object1')
|
||||
object2 = ProxyModelBase.objects.get(name='object2')
|
||||
self.assertEqual(repr(object1), '<ProxyModelA: id 1, name (CharField) "object1", field1 (CharField) "">')
|
||||
self.assertEqual(repr(object2), '<ProxyModelB: id 2, name (CharField) "object2", field2 (CharField) "bb">')
|
||||
self.assertIsInstance(object1, ProxyModelA)
|
||||
self.assertIsInstance(object2, ProxyModelB)
|
||||
|
||||
# Same for lists
|
||||
objects = list(ProxyModelBase.objects.all().order_by('name'))
|
||||
self.assertEqual(repr(objects[0]), '<ProxyModelA: id 1, name (CharField) "object1", field1 (CharField) "">')
|
||||
self.assertEqual(repr(objects[1]), '<ProxyModelB: id 2, name (CharField) "object2", field2 (CharField) "bb">')
|
||||
self.assertIsInstance(objects[0], ProxyModelA)
|
||||
self.assertIsInstance(objects[1], ProxyModelB)
|
||||
|
||||
|
||||
def test_fix_getattribute(self):
|
||||
### fixed issue in PolymorphicModel.__getattribute__: field name same as model name
|
||||
o = ModelFieldNameTest.objects.create(modelfieldnametest='1')
|
||||
self.assertEqual(repr(o), '<ModelFieldNameTest: id 1, modelfieldnametest (CharField)>')
|
||||
|
||||
# if subclass defined __init__ and accessed class members,
|
||||
# __getattribute__ had a problem: "...has no attribute 'sub_and_superclass_dict'"
|
||||
o = InitTestModelSubclass.objects.create()
|
||||
self.assertEqual(o.bar, 'XYZ')
|
||||
|
||||
|
||||
class RegressionTests(TestCase):
|
||||
|
||||
def test_for_query_result_incomplete_with_inheritance(self):
|
||||
""" https://github.com/bconstantin/django_polymorphic/issues/15 """
|
||||
|
||||
top = Top()
|
||||
top.save()
|
||||
middle = Middle()
|
||||
middle.save()
|
||||
bottom = Bottom()
|
||||
bottom.save()
|
||||
|
||||
expected_queryset = [top, middle, bottom]
|
||||
self.assertQuerysetEqual(Top.objects.all(), [repr(r) for r in expected_queryset])
|
||||
|
||||
expected_queryset = [middle, bottom]
|
||||
self.assertQuerysetEqual(Middle.objects.all(), [repr(r) for r in expected_queryset])
|
||||
|
||||
expected_queryset = [bottom]
|
||||
self.assertQuerysetEqual(Bottom.objects.all(), [repr(r) for r in expected_queryset])
|
||||
|
||||
146
awx/lib/site-packages/polymorphic/tools_for_tests.py
Normal file
146
awx/lib/site-packages/polymorphic/tools_for_tests.py
Normal file
@ -0,0 +1,146 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
####################################################################
|
||||
|
||||
import uuid
|
||||
|
||||
from django import forms
|
||||
from django.db import models
|
||||
from django.utils.encoding import smart_text
|
||||
from django.utils import six
|
||||
|
||||
class UUIDVersionError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class UUIDField(six.with_metaclass(models.SubfieldBase, models.CharField)):
|
||||
"""Encode and stores a Python uuid.UUID in a manner that is appropriate
|
||||
for the given datatabase that we are using.
|
||||
|
||||
For sqlite3 or MySQL we save it as a 36-character string value
|
||||
For PostgreSQL we save it as a uuid field
|
||||
|
||||
This class supports type 1, 2, 4, and 5 UUID's.
|
||||
"""
|
||||
|
||||
_CREATE_COLUMN_TYPES = {
|
||||
'postgresql_psycopg2': 'uuid',
|
||||
'postgresql': 'uuid'
|
||||
}
|
||||
|
||||
def __init__(self, verbose_name=None, name=None, auto=True, version=1, node=None, clock_seq=None, namespace=None, **kwargs):
|
||||
"""Contruct a UUIDField.
|
||||
|
||||
@param verbose_name: Optional verbose name to use in place of what
|
||||
Django would assign.
|
||||
@param name: Override Django's name assignment
|
||||
@param auto: If True, create a UUID value if one is not specified.
|
||||
@param version: By default we create a version 1 UUID.
|
||||
@param node: Used for version 1 UUID's. If not supplied, then the uuid.getnode() function is called to obtain it. This can be slow.
|
||||
@param clock_seq: Used for version 1 UUID's. If not supplied a random 14-bit sequence number is chosen
|
||||
@param namespace: Required for version 3 and version 5 UUID's.
|
||||
@param name: Required for version4 and version 5 UUID's.
|
||||
|
||||
See Also:
|
||||
- Python Library Reference, section 18.16 for more information.
|
||||
- RFC 4122, "A Universally Unique IDentifier (UUID) URN Namespace"
|
||||
|
||||
If you want to use one of these as a primary key for a Django
|
||||
model, do this::
|
||||
id = UUIDField(primary_key=True)
|
||||
This will currently I{not} work with Jython because PostgreSQL support
|
||||
in Jython is not working for uuid column types.
|
||||
"""
|
||||
self.max_length = 36
|
||||
kwargs['max_length'] = self.max_length
|
||||
if auto:
|
||||
kwargs['blank'] = True
|
||||
kwargs.setdefault('editable', False)
|
||||
|
||||
self.auto = auto
|
||||
self.version = version
|
||||
if version == 1:
|
||||
self.node, self.clock_seq = node, clock_seq
|
||||
elif version == 3 or version == 5:
|
||||
self.namespace, self.name = namespace, name
|
||||
|
||||
super(UUIDField, self).__init__(verbose_name=verbose_name,
|
||||
name=name, **kwargs)
|
||||
|
||||
def create_uuid(self):
|
||||
if not self.version or self.version == 4:
|
||||
return uuid.uuid4()
|
||||
elif self.version == 1:
|
||||
return uuid.uuid1(self.node, self.clock_seq)
|
||||
elif self.version == 2:
|
||||
raise UUIDVersionError("UUID version 2 is not supported.")
|
||||
elif self.version == 3:
|
||||
return uuid.uuid3(self.namespace, self.name)
|
||||
elif self.version == 5:
|
||||
return uuid.uuid5(self.namespace, self.name)
|
||||
else:
|
||||
raise UUIDVersionError("UUID version %s is not valid." % self.version)
|
||||
|
||||
def db_type(self, connection):
|
||||
from django.conf import settings
|
||||
full_database_type = settings.DATABASES['default']['ENGINE']
|
||||
database_type = full_database_type.split('.')[-1]
|
||||
return UUIDField._CREATE_COLUMN_TYPES.get(database_type, "char(%s)" % self.max_length)
|
||||
|
||||
def to_python(self, value):
|
||||
"""Return a uuid.UUID instance from the value returned by the database."""
|
||||
#
|
||||
# This is the proper way... But this doesn't work correctly when
|
||||
# working with an inherited model
|
||||
#
|
||||
if not value:
|
||||
return None
|
||||
if isinstance(value, uuid.UUID):
|
||||
return value
|
||||
# attempt to parse a UUID
|
||||
return uuid.UUID(smart_text(value))
|
||||
|
||||
#
|
||||
# If I do the following (returning a String instead of a UUID
|
||||
# instance), everything works.
|
||||
#
|
||||
|
||||
#if not value:
|
||||
# return None
|
||||
#if isinstance(value, uuid.UUID):
|
||||
# return smart_text(value)
|
||||
#else:
|
||||
# return value
|
||||
|
||||
def pre_save(self, model_instance, add):
|
||||
if self.auto and add:
|
||||
value = self.create_uuid()
|
||||
setattr(model_instance, self.attname, value)
|
||||
else:
|
||||
value = super(UUIDField, self).pre_save(model_instance, add)
|
||||
if self.auto and not value:
|
||||
value = self.create_uuid()
|
||||
setattr(model_instance, self.attname, value)
|
||||
return value
|
||||
|
||||
def get_db_prep_value(self, value, connection, prepared):
|
||||
"""Casts uuid.UUID values into the format expected by the back end for use in queries"""
|
||||
if isinstance(value, uuid.UUID):
|
||||
return smart_text(value)
|
||||
return value
|
||||
|
||||
def value_to_string(self, obj):
|
||||
val = self._get_val_from_obj(obj)
|
||||
if val is None:
|
||||
data = ''
|
||||
else:
|
||||
data = smart_text(val)
|
||||
return data
|
||||
|
||||
def formfield(self, **kwargs):
|
||||
defaults = {
|
||||
'form_class': forms.CharField,
|
||||
'max_length': self.max_length
|
||||
}
|
||||
defaults.update(kwargs)
|
||||
return super(UUIDField, self).formfield(**defaults)
|
||||
@ -62,6 +62,7 @@ Django-1.5.5.tar.gz
|
||||
#django-celery-3.1.1.tar.gz
|
||||
#django-extensions-1.2.5.tar.gz
|
||||
#django-jsonfield-0.9.12.tar.gz
|
||||
#django_polymorphic-0.5.3.tar.gz
|
||||
#django-split-settings-0.1.1.tar.gz
|
||||
#django-taggit-0.11.2.tar.gz
|
||||
#djangorestframework-2.3.10.tar.gz
|
||||
@ -93,3 +94,4 @@ ipython-1.1.0.tar.gz
|
||||
# - psycopg2 (via "yum install python-psycopg2")
|
||||
# - python-ldap (via "yum install python-ldap")
|
||||
# - readline-6.2.4.1.tar.gz (for the ipython shell)
|
||||
# - python-zmq (for using the job callback receiver)
|
||||
|
||||
BIN
requirements/django_polymorphic-0.5.3.tar.gz
Normal file
BIN
requirements/django_polymorphic-0.5.3.tar.gz
Normal file
Binary file not shown.
@ -10,6 +10,7 @@ Django>=1.4
|
||||
#django-celery
|
||||
#django-extensions
|
||||
#django-jsonfield
|
||||
#django-polymorphic
|
||||
#django-split-settings
|
||||
#django-taggit
|
||||
#djangorestframework>=2.3.0,<2.4.0
|
||||
@ -25,3 +26,4 @@ Django>=1.4
|
||||
# - ansible (via yum, pip or source checkout)
|
||||
# - psycopg2 (via "yum install python-psycopg2")
|
||||
# - python-ldap (via "yum install python-ldap")
|
||||
# - python-zmq (for using the job callback receiver)
|
||||
|
||||
@ -60,6 +60,7 @@ Django-1.5.5.tar.gz
|
||||
#django-celery-3.1.1.tar.gz
|
||||
#django-extensions-1.2.5.tar.gz
|
||||
#django-jsonfield-0.9.12.tar.gz
|
||||
#django_polymorphic-0.5.3.tar.gz
|
||||
#django-split-settings-0.1.1.tar.gz
|
||||
#django-taggit-0.11.2.tar.gz
|
||||
#djangorestframework-2.3.10.tar.gz
|
||||
@ -73,3 +74,4 @@ Django-1.5.5.tar.gz
|
||||
# - ansible (via yum, pip or source checkout)
|
||||
# - psycopg2 (via "yum install python-psycopg2")
|
||||
# - python-ldap (via "yum install python-ldap")
|
||||
# - python-zmq (for using the job callback receiver)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user