diff --git a/django/contrib/admin/templatetags/admin_list.py b/django/contrib/admin/templatetags/admin_list.py index d209a918fb..69f7fa41cb 100644 --- a/django/contrib/admin/templatetags/admin_list.py +++ b/django/contrib/admin/templatetags/admin_list.py @@ -190,8 +190,8 @@ result_list = register.inclusion_tag("admin/change_list_results")(result_list) #@register.inclusion_tag("admin/date_hierarchy") def date_hierarchy(cl): - lookup_opts, params, lookup_params, lookup_mod = \ - cl.lookup_opts, cl.params, cl.lookup_params, cl.lookup_mod + lookup_opts, params, lookup_params, manager = \ + cl.lookup_opts, cl.params, cl.lookup_params, cl.manager if lookup_opts.admin.date_hierarchy: field_name = lookup_opts.admin.date_hierarchy @@ -208,7 +208,7 @@ def date_hierarchy(cl): return cl.get_query_string(d, [field_generic]) def get_dates(unit, params): - return getattr(lookup_mod, 'get_%s_list' % field_name)(unit, **params) + return getattr(manager, 'get_%s_list' % field_name)(unit, **params) if year_lookup and month_lookup and day_lookup: month_name = MONTHS[int(month_lookup)] diff --git a/django/contrib/admin/templatetags/adminapplist.py b/django/contrib/admin/templatetags/adminapplist.py index 90396891cb..a313cebbd2 100644 --- a/django/contrib/admin/templatetags/adminapplist.py +++ b/django/contrib/admin/templatetags/adminapplist.py @@ -17,6 +17,8 @@ class AdminApplistNode(template.Node): has_module_perms = user.has_module_perms(app_label) if has_module_perms: model_list = [] + #HACK + app_url = "/".join( [comp for comp in app.__name__.split('.') if comp != 'models' ]) for m in app._MODELS: if m._meta.admin: module_name = m._meta.module_name @@ -28,10 +30,12 @@ class AdminApplistNode(template.Node): # Check whether user has any perm for this module. # If so, add the module to the model_list. + + if True in perms.values(): model_list.append({ 'name': capfirst(m._meta.verbose_name_plural), - 'admin_url': '%s/%s/' % (app_label, m._meta.module_name), + 'admin_url': '%s/%s/' % (app_url, m.__name__.lower()), 'perms': perms, }) diff --git a/django/contrib/admin/urls/admin.py b/django/contrib/admin/urls/admin.py index 3f4dbab419..c08aaf47fe 100644 --- a/django/contrib/admin/urls/admin.py +++ b/django/contrib/admin/urls/admin.py @@ -48,11 +48,10 @@ if 'ellington.media' in INSTALLED_APPS: ) urlpatterns += ( - # Metasystem admin pages - ('^(?P[^/]+)/(?P[^/]+)/$', 'django.contrib.admin.views.main.change_list'), - ('^(?P[^/]+)/(?P[^/]+)/add/$', 'django.contrib.admin.views.main.add_stage'), - ('^(?P[^/]+)/(?P[^/]+)/(?P.+)/history/$', 'django.contrib.admin.views.main.history'), - ('^(?P[^/]+)/(?P[^/]+)/(?P.+)/delete/$', 'django.contrib.admin.views.main.delete_stage'), - ('^(?P[^/]+)/(?P[^/]+)/(?P.+)/$', 'django.contrib.admin.views.main.change_stage'), + ('^((?:[^/]+/)+?)add/$', 'django.contrib.admin.views.main.add_stage'), + ('^((?:[^/]+/)+?)([^/]+)/history/$', 'django.contrib.admin.views.main.history'), + ('^((?:[^/]+/)+?)([^/]+)/delete/$', 'django.contrib.admin.views.main.delete_stage'), + ('^((?:[^/]+/)+?)([^/]+)/change/$', 'django.contrib.admin.views.main.change_stage'), + ('^((?:[^/]+/)+?)$', 'django.contrib.admin.views.main.change_list' ), ) urlpatterns = patterns('', *urlpatterns) diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py index f67493745b..3624436445 100644 --- a/django/contrib/admin/views/main.py +++ b/django/contrib/admin/views/main.py @@ -1,4 +1,5 @@ # Generic admin views. +from django.conf import settings from django.contrib.admin.views.decorators import staff_member_required from django.contrib.admin.filterspecs import FilterSpec from django.core import formfields, template @@ -21,6 +22,7 @@ from django.utils import dateformat from django.utils.dates import MONTHS from django.utils.html import escape import operator +from itertools import izip # The system will display a "Show all" link only if the total result count # is less than or equal to this setting. @@ -41,7 +43,7 @@ EMPTY_CHANGELIST_VALUE = '(None)' def _get_mod_opts(app_label, module_name): "Helper function that returns a tuple of (module, opts), raising Http404 if necessary." try: - mod = models.get_module(app_label, module_name) + mod = models.get_app(app_label) except ImportError: raise Http404 # Invalid app or module name. Maybe it's not in INSTALLED_APPS. opts = mod.Klass._meta @@ -49,6 +51,52 @@ def _get_mod_opts(app_label, module_name): raise Http404 # This object is valid but has no admin interface. return mod, opts +def matches_app(mod, comps): + modcomps = mod.__name__.split('.')[:-1] #HACK: leave off 'models' + for c, mc in izip(comps, modcomps): + if c != mc: + return ([],False) + return (comps[len(modcomps):], True) + +def find_model(mod, remaining): + # print "finding ", mod, remaining + if len(remaining) == 0: + # print "no comps left" + raise Http404 + if len(remaining) == 1: + if hasattr(mod, '_MODELS'): + name = remaining[0] + for model in mod._MODELS: + print "'%s'" % name, model + if model.__name__.lower() == name: + return model + raise Http404 + else: + raise Http404 + else: + child = getattr(mod, remaining[0], None) + # print mod, remaining[0], child + if child: + return find_model(child, remaining[1:]) + else: + raise Http404 + +def get_app_label(mod): + modcomps = mod.__name__.split('.') + return modcomps[-2] + +def get_model_and_app(path): + comps = path.split('/') + comps = comps[:-1] # remove '' after final / + for mod in models.get_installed_models(): + remaining, matched = matches_app(mod, comps) + if matched and len(remaining) > 0: + # print "matched ", mod + # print "left", remaining + return ( find_model(mod, remaining), get_app_label(mod) ) + + raise Http404 # Couldn't find app + def index(request): return render_to_response('admin/index', {'title': _('Site administration')}, context_instance=Context(request)) index = staff_member_required(index) @@ -57,8 +105,8 @@ class IncorrectLookupParameters(Exception): pass class ChangeList(object): - def __init__(self, request, app_label, module_name): - self.get_modules_and_options(app_label, module_name, request) + def __init__(self, request, path): + self.resolve_model(path, request) self.get_search_parameters(request) self.get_ordering() self.query = request.GET.get(SEARCH_VAR, '') @@ -94,12 +142,16 @@ class ChangeList(object): p[k] = v return '?' + '&'.join(['%s=%s' % (k, v) for k, v in p.items()]).replace(' ', '%20') - def get_modules_and_options(self, app_label, module_name, request): - self.mod, self.opts = _get_mod_opts(app_label, module_name) - if not request.user.has_perm(app_label + '.' + self.opts.get_change_permission()): + def resolve_model(self, path, request): + self.model, self.app_label = get_model_and_app(path) + # _get_mod_opts(app_label, module_name) + self.opts = self.model._meta + + if not request.user.has_perm(self.app_label + '.' + self.opts.get_change_permission()): raise PermissionDenied - self.lookup_mod, self.lookup_opts = self.mod, self.opts + self.lookup_opts = self.opts + self.manager = self.model._default_manager def get_search_parameters(self, request): # Get search parameters from the query string. @@ -114,11 +166,11 @@ class ChangeList(object): del self.params[PAGE_VAR] def get_results(self, request): - lookup_mod, lookup_params, show_all, page_num = \ - self.lookup_mod, self.lookup_params, self.show_all, self.page_num + manager, lookup_params, show_all, page_num = \ + self.manager, self.lookup_params, self.show_all, self.page_num # Get the results. try: - paginator = ObjectPaginator(lookup_mod, lookup_params, DEFAULT_RESULTS_PER_PAGE) + paginator = ObjectPaginator(manager, lookup_params, DEFAULT_RESULTS_PER_PAGE) # Naked except! Because we don't have any other way of validating "params". # They might be invalid if the keyword arguments are incorrect, or if the # values are not in the correct type (which would result in a database @@ -130,7 +182,7 @@ class ChangeList(object): real_lookup_params = lookup_params.copy() del real_lookup_params['order_by'] if real_lookup_params: - full_result_count = lookup_mod.get_count() + full_result_count = manager.get_count() else: full_result_count = paginator.hits del real_lookup_params @@ -140,7 +192,7 @@ class ChangeList(object): # Get the list of objects to display on this page. if (show_all and can_show_all) or not multi_page: - result_list = lookup_mod.get_list(**lookup_params) + result_list = manager.get_list(**lookup_params) else: try: result_list = paginator.get_page(page_num) @@ -231,9 +283,10 @@ class ChangeList(object): lookup_params.update(opts.one_to_one_field.rel.limit_choices_to) self.lookup_params = lookup_params -def change_list(request, app_label, module_name): +def change_list(request, path): + print "change_list:", path try: - cl = ChangeList(request, app_label, module_name) + cl = ChangeList(request, path) except IncorrectLookupParameters: return HttpResponseRedirect(request.path) @@ -242,9 +295,9 @@ def change_list(request, app_label, module_name): 'is_popup': cl.is_popup, 'cl' : cl }) - c.update({'has_add_permission': c['perms'][app_label][cl.opts.get_add_permission()]}), - return render_to_response(['admin/%s/%s/change_list' % (app_label, cl.opts.object_name.lower()), - 'admin/%s/change_list' % app_label, + c.update({'has_add_permission': c['perms'][cl.app_label][cl.opts.get_add_permission()]}), + return render_to_response(['admin/%s/%s/change_list' % (cl.app_label, cl.opts.object_name.lower()), + 'admin/%s/change_list' % cl.app_label, 'admin/change_list'], context_instance=c) change_list = staff_member_required(change_list) @@ -464,12 +517,15 @@ def log_change_message(user, opts,manipulator,new_object): change_message = _('No fields changed.') LogEntry.objects.log_action(user.id, opts.get_content_type_id(), pk_value, str(new_object), CHANGE, change_message) -def change_stage(request, app_label, module_name, object_id): - mod, opts = _get_mod_opts(app_label, module_name) +def change_stage(request, path, object_id): + print "change_stage", path, object_id + model, app_label = get_model_and_app(path) + opts = model._meta + #mod, opts = _get_mod_opts(app_label, module_name) if not request.user.has_perm(app_label + '.' + opts.get_change_permission()): raise PermissionDenied if request.POST and request.POST.has_key("_saveasnew"): - return add_stage(request, app_label, module_name, form_url='../add/') + return add_stage(request, path, form_url='../add/') try: manipulator = mod.ChangeManipulator(object_id) except ObjectDoesNotExist: diff --git a/django/core/paginator.py b/django/core/paginator.py index a4dbfebaae..4320d645fc 100644 --- a/django/core/paginator.py +++ b/django/core/paginator.py @@ -1,23 +1,28 @@ from copy import copy from math import ceil +from django.db.models import ModelBase class InvalidPage(Exception): pass class ObjectPaginator: """ - This class makes pagination easy. Feed it a module (an object with - get_count() and get_list() methods) and a dictionary of arguments - to be passed to those methods, plus the number of objects you want - on each page. Then read the hits and pages properties to see how - many pages it involves. Call get_page with a page number (starting - at 0) to get back a list of objects for that page. - + This class makes pagination easy. Feed it a manager (an object with + get_count() and get_list() methods) or a model which has a default manager, + and a dictionary of arguments to be passed to those methods, plus the + number of objects you want on each page. Then read the hits and pages + properties to see how many pages it involves. Call get_page with a page + number (starting at 0) to get back a list of objects for that page. + Finally, check if a page number has a next/prev page using has_next_page(page_number) and has_previous_page(page_number). """ - def __init__(self, module, args, num_per_page, count_method='get_count', list_method='get_list'): - self.module, self.args = module, args + def __init__(self, manager_or_model, args, num_per_page, count_method='get_count', list_method='get_list'): + if hasattr(manager_or_model, '_default_manager'): + manager = manager_or_model._default_manager + else: + manager = manager_or_model + self.manager, self.args = manager, args self.num_per_page = num_per_page self.count_method, self.list_method = count_method, list_method self._hits, self._pages = None, None @@ -35,7 +40,7 @@ class ObjectPaginator: # Retrieve one extra record, and check for the existence of that extra # record to determine whether there's a next page. args['limit'] = self.num_per_page + 1 - object_list = getattr(self.module, self.list_method)(**args) + object_list = getattr(self.manager, self.list_method)(**args) if not object_list: raise InvalidPage self._has_next[page_number] = (len(object_list) > self.num_per_page) @@ -48,7 +53,7 @@ class ObjectPaginator: args = copy(self.args) args['offset'] = (page_number + 1) * self.num_per_page args['limit'] = 1 - object_list = getattr(self.module, self.list_method)(**args) + object_list = getattr(self.manager, self.list_method)(**args) self._has_next[page_number] = (object_list != []) else: self._has_next[page_number] = page_number < (self.pages - 1) @@ -64,7 +69,7 @@ class ObjectPaginator: del order_args['order_by'] if order_args.has_key('select_related'): del order_args['select_related'] - self._hits = getattr(self.module, self.count_method)(**order_args) + self._hits = getattr(self.manager, self.count_method)(**order_args) return self._hits def _get_pages(self): diff --git a/django/db/models/__init__.py b/django/db/models/__init__.py index 10c3b35add..d408c971d0 100644 --- a/django/db/models/__init__.py +++ b/django/db/models/__init__.py @@ -1051,6 +1051,17 @@ class Model(object): delete.alters_data = True + def __get_add_manipulator(cls): + cls.AddManipulator = get_manipulator + + AddManipulator = property(classmethod(__get_add_manipulator)) + + def __get_change_manipulator(cls): + + ChangeManipulator = property(classmethod(__get_change_manipulator)) + + + def __get_FIELD_display(self, field): value = getattr(self, field.attname) return dict(field.choices).get(value, value) @@ -1259,6 +1270,8 @@ class Model(object): connection.commit() + + ############################################ # HELPER FUNCTIONS (CURRIED MODEL METHODS) # ############################################ @@ -1503,6 +1516,73 @@ def get_manipulator(opts, klass, extra_methods, add=False, change=False): setattr(man, k, v) return man +class AutomaticManipulator(formfields.Manipulator): + def __classinit__(cls, model): + cls.model = model + cls.manager = model._default_manager + __classinit__ = classmethod(__classinit__) + + def __init__(self, follow=None): + self.follow = self.model._meta.get_follow(follow) + self.fields = [] + + for f in opts.fields + opts.many_to_many: + if self.follow.get(f.name, False): + self.fields.extend(f.get_manipulator_fields(opts, self, change)) + + # Add fields for related objects. + for f in opts.get_all_related_objects(): + if self.follow.get(f.name, False): + fol = self.follow[f.name] + self.fields.extend(f.get_manipulator_fields(opts, self, change, fol)) + + def save(self): + pass + + def get_related_objects(opts, klass, add, change, self): + return opts.get_followed_related_objects(self.follow) + + def flatten_data(opts, klass, add, change, self): + new_data = {} + obj = change and self.original_object or None + for f in opts.get_data_holders(self.follow): + fol = self.follow.get(f.name) + new_data.update(f.flatten_data(fol, obj)) + return new_data + +class ModelAddManipulator(AutomaticManipulator): + pass + +class ModelSaveManipulator(AutomaticManipulator): + def __init__(self, obj_key=None, follow=None): + + assert obj_key is not None, "ChangeManipulator.__init__() must be passed obj_key parameter." + self.obj_key = obj_key + try: + self.original_object = self.manager.get_object(pk=obj_key) + except ObjectDoesNotExist: + # If the object doesn't exist, this might be a manipulator for a + # one-to-one related object that hasn't created its subobject yet. + # For example, this might be a Restaurant for a Place that doesn't + # yet have restaurant information. + if opts.one_to_one_field: + # Sanity check -- Make sure the "parent" object exists. + # For example, make sure the Place exists for the Restaurant. + # Let the ObjectDoesNotExist exception propogate up. + lookup_kwargs = opts.one_to_one_field.rel.limit_choices_to + lookup_kwargs['%s__exact' % opts.one_to_one_field.rel.field_name] = obj_key + _ = opts.one_to_one_field.rel.to._meta.get_model_module().get_object(**lookup_kwargs) + params = dict([(f.attname, f.get_default()) for f in opts.fields]) + params[opts.pk.attname] = obj_key + self.original_object = opts.get_model_module().Klass(**params) + else: + raise + + super(ModelSaveManipulator, self).__init__(self, follow=follow) + + if opts.get_ordered_objects(): + self.fields.append(formfields.CommaSeparatedIntegerField(field_name="order_")) + def manipulator_init(opts, add, change, self, obj_key=None, follow=None): self.follow = opts.get_follow(follow) @@ -1691,16 +1771,7 @@ def manipulator_save(opts, klass, add, change, self, new_data): getattr(new_object, 'set_%s_order' % rel_opts.object_name.lower())(order) return new_object -def manipulator_get_related_objects(opts, klass, add, change, self): - return opts.get_followed_related_objects(self.follow) -def manipulator_flatten_data(opts, klass, add, change, self): - new_data = {} - obj = change and self.original_object or None - for f in opts.get_data_holders(self.follow): - fol = self.follow.get(f.name) - new_data.update(f.flatten_data(fol, obj)) - return new_data def manipulator_validator_unique_together(field_name_list, opts, self, field_data, all_data): from django.utils.text import get_text_list