# HG changeset patch # User Pawel Solyga # Date 1243197159 -7200 # Node ID 58d2310330d3a637c512ac9f17803cb7cdfead1b # Parent 366e64ecba91f3fcdc91d198c558a96cb796e99c# Parent 221482a54238e2ffdbe1019ac3c953679adff999 Merge with Sverre's recent module architecture commit. diff -r 366e64ecba91 -r 58d2310330d3 app/main.py --- a/app/main.py Sun May 24 22:29:54 2009 +0200 +++ b/app/main.py Sun May 24 22:32:39 2009 +0200 @@ -87,6 +87,12 @@ # Create a Django application for WSGI. application = django.core.handlers.wsgi.WSGIHandler() + from soc.modules import callback + from soc.modules import core + + callback.registerCore(core.Core()) + callback.getCore().registerModuleCallbacks() + # Run the WSGI CGI handler with that application. util.run_wsgi_app(application) diff -r 366e64ecba91 -r 58d2310330d3 app/settings.py --- a/app/settings.py Sun May 24 22:29:54 2009 +0200 +++ b/app/settings.py Sun May 24 22:32:39 2009 +0200 @@ -110,3 +110,6 @@ # 'django.contrib.sessions', # 'django.contrib.sites', ) + +MODULE_FMT = 'soc.modules.%s' +MODULES = [] diff -r 366e64ecba91 -r 58d2310330d3 app/soc/modules/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/soc/modules/__init__.py Sun May 24 22:32:39 2009 +0200 @@ -0,0 +1,17 @@ +# +# Copyright 2009 the Melange authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""This is the main modules module. +""" diff -r 366e64ecba91 -r 58d2310330d3 app/soc/modules/callback.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/soc/modules/callback.py Sun May 24 22:32:39 2009 +0200 @@ -0,0 +1,40 @@ +# Copyright 2009 the Melange authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Module containing Melange callbacks. +""" + +__authors__ = [ + '"Sverre Rabbelier" ', + '"Lennard de Rijk" ', + ] + + +CORE = None + + +def registerCore(core): + """Registers the specified callback as core. + """ + + global CORE + CORE = core + + +def getCore(): + """Returns the Core handler. + """ + + global CORE + return CORE diff -r 366e64ecba91 -r 58d2310330d3 app/soc/modules/core.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/soc/modules/core.py Sun May 24 22:32:39 2009 +0200 @@ -0,0 +1,235 @@ +# Copyright 2009 the Melange authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""The Melange Core module. +""" + +__authors__ = [ + '"Sverre Rabbelier" ', + '"Lennard de Rijk" ', + ] + + +from django.conf.urls import defaults + +import settings +import soc.cache.sidebar + + +class Error(Exception): + """Error class for the callback module. + """ + + pass + + +class APIVersionMismatch(Error): + """Error raised when API version mismatches. + """ + + MISMATCH_MSG_FMT = "API mismatch, expected '%d', got '%d'." + + def __init__(self, expected, actual): + """Instantiates a new exception with a customized message. + """ + + msg = self.MISMATCH_MSG_FMT % (expected, actual) + super(APIVersionMismatch, self).__init__(msg) + + +class MissingService(Error): + """Error raised when a required service is missing. + """ + + MISSING_SERVICE_FMT = "Required service '%s' is not registered, known: %s" + + def __init__(self, service, services): + """Instantiates a new exception with a customized message. + """ + + msg = self.MISSING_SERVICE_FMT % (service, services) + super(MissingService, self).__init__(msg) + + +class NonUniqueService(Error): + """Error raised when a required service is missing. + """ + + NON_UNIQUE_SERVICE_FMT = "Unique service '%s' called a second time, known: %s." + + def __init__(self, service, services): + """Instantiates a new exception with a customized message. + """ + + msg = self.NON_UNIQUE_SERVICE_FMT % (service, services) + super(NonUniqueService, self).__init__(msg) + + +class Core(object): + """The core handler that controls the Melange API. + """ + + def __init__(self): + """Creates a new instance of the Core. + """ + + self.API_VERSION = 1 + + self.registered_callbacks = [] + self.capability = [] + self.services = [] + + self.sitemap = [] + self.sidebar = [] + + ## + ## internal + ## + + def getService(self, callback, service): + """Retrieves the specified service from the callback if supported. + + Args: + callback: the callback to retrieve the capability from + service: the service to retrieve + """ + + if not hasattr(callback, service): + return False + + func = getattr(callback, service) + + if not callable(func): + return False + + return func + + ## + ## Core code + ## + + def getPatterns(self): + """Returns the Django patterns for this site. + """ + + self.callService('registerWithSitemap', True) + return defaults.patterns(None, *self.sitemap) + + @soc.cache.sidebar.cache + def getSidebar(self, id, user): + """Constructs a sidebar for the current user. + """ + + self.callService('registerWithSidebar', True) + + sidebar = [] + + for i in self.sidebar: + menus = i(id, user) + + for menu in (menus if menus else []): + sidebar.append(menu) + + return sorted(sidebar, key=lambda x: x.get('group')) + + def callService(self, service, unique, *args, **kwargs): + """Calls the specified service on all callbacks. + """ + + if unique and (service in self.services): + return + + results = [] + + for callback in self.registered_callbacks: + func = self.getService(callback, service) + if not func: + continue + + result = func(*args, **kwargs) + results.append(result) + + self.services.append(service) + return results + + def registerModuleCallbacks(self): + """Retrieves all callbacks for the modules of this site. + + Callbacks for modules without a version number or the wrong API_VERSION + number are dropped. They won't be called. + """ + + fmt = settings.MODULE_FMT + modules = ['soc_core'] + settings.MODULES + modules = [__import__(fmt % i, fromlist=['']) for i in modules] + + for callback_class in [i.getCallback() for i in modules]: + if callback_class.API_VERSION != self.API_VERSION: + raise callback.APIVersionMismatch(self.API_VERSION, + callback_class.API_VERSION) + + + callback = callback_class(self) + self.registered_callbacks.append(callback) + + return True + + ## + ## Module code + ## + + def registerCapability(self, capability): + """Registers the specified capability. + """ + + self.capabilities.append(capability) + + def requireCapability(self, capability): + """Requires that the specified capability is present. + """ + + if capability in self.capabilities: + return True + + raise MissingCapability(capability, self.capability) + + def requireService(self, service): + """Requires that the specified service has been called. + """ + + if service in self.services: + return True + + raise MissingService(service, self.services) + + def requireUniqueService(self, service): + """Requires that the specified service is called exactly once. + """ + + if service not in self.services: + return True + + raise NonUniqueService(service, self.services) + + def registerSitemapEntry(self, entries): + """Registers the specified entries with the sitemap. + """ + + self.sitemap.extend(entries) + + def registerSidebarEntry(self, entry): + """Registers the specified entry with the sidebar. + """ + + self.sidebar.append(entry) diff -r 366e64ecba91 -r 58d2310330d3 app/soc/modules/soc_core/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/soc/modules/soc_core/__init__.py Sun May 24 22:32:39 2009 +0200 @@ -0,0 +1,29 @@ +# +# Copyright 2009 the Melange authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""This is the main modules module. +""" + +__authors__ = [ + '"Sverre Rabbelier" ', + '"Lennard de Rijk" ', + ] + + + +from soc.modules.soc_core import callback + +def getCallback(): + return callback.Callback diff -r 366e64ecba91 -r 58d2310330d3 app/soc/modules/soc_core/callback.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/soc/modules/soc_core/callback.py Sun May 24 22:32:39 2009 +0200 @@ -0,0 +1,129 @@ +# Copyright 2009 the Melange authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Module containing the core callback. +""" + +__authors__ = [ + '"Sverre Rabbelier" ', + '"Lennard de Rijk" ', + ] + + +from soc.modules import callback + +from soc.views.models import club +from soc.views.models import club_app +from soc.views.models import club_admin +from soc.views.models import club_member +from soc.views.models import cron +from soc.views.models import document +from soc.views.models import host +from soc.views.models import job +from soc.views.models import mentor +from soc.views.models import notification +from soc.views.models import organization +from soc.views.models import org_admin +from soc.views.models import org_app +from soc.views.models import priority_group +from soc.views.models import program +from soc.views.models import request +from soc.views.models import site +from soc.views.models import sponsor +from soc.views.models import student +from soc.views.models import student_project +from soc.views.models import student_proposal +from soc.views.models import timeline +from soc.views.models import user +from soc.views.models import user_self + + +class Callback(object): + """Callback object that handles interaction between the core. + """ + + API_VERSION = 1 + + def __init__(self, core): + """Initializes a new Callback object for the specified core. + """ + + self.core = core + + # disable clubs + self.enable_clubs = False + + def registerWithSitemap(self): + """Called by the server when sitemap entries should be registered. + """ + + self.core.requireUniqueService('registerWithSitemap') + + if self.enable_clubs: + self.core.registerSitemapEntry(club.view.getDjangoURLPatterns()) + self.core.registerSitemapEntry(club_admin.view.getDjangoURLPatterns()) + self.core.registerSitemapEntry(club_app.view.getDjangoURLPatterns()) + self.core.registerSitemapEntry(club_member.view.getDjangoURLPatterns()) + + self.core.registerSitemapEntry(cron.view.getDjangoURLPatterns()) + self.core.registerSitemapEntry(document.view.getDjangoURLPatterns()) + self.core.registerSitemapEntry(host.view.getDjangoURLPatterns()) + self.core.registerSitemapEntry(job.view.getDjangoURLPatterns()) + self.core.registerSitemapEntry(mentor.view.getDjangoURLPatterns()) + self.core.registerSitemapEntry(notification.view.getDjangoURLPatterns()) + self.core.registerSitemapEntry(organization.view.getDjangoURLPatterns()) + self.core.registerSitemapEntry(org_admin.view.getDjangoURLPatterns()) + self.core.registerSitemapEntry(org_app.view.getDjangoURLPatterns()) + self.core.registerSitemapEntry(priority_group.view.getDjangoURLPatterns()) + self.core.registerSitemapEntry(program.view.getDjangoURLPatterns()) + self.core.registerSitemapEntry(request.view.getDjangoURLPatterns()) + self.core.registerSitemapEntry(site.view.getDjangoURLPatterns()) + self.core.registerSitemapEntry(sponsor.view.getDjangoURLPatterns()) + self.core.registerSitemapEntry(student.view.getDjangoURLPatterns()) + self.core.registerSitemapEntry(student_project.view.getDjangoURLPatterns()) + self.core.registerSitemapEntry(student_proposal.view.getDjangoURLPatterns()) + self.core.registerSitemapEntry(timeline.view.getDjangoURLPatterns()) + self.core.registerSitemapEntry(user_self.view.getDjangoURLPatterns()) + self.core.registerSitemapEntry(user.view.getDjangoURLPatterns()) + + def registerWithSidebar(self): + """Called by the server when sidebar entries should be registered. + """ + + self.core.requireUniqueService('registerWithSidebar') + + if self.enable_clubs: + self.core.registerSidebarEntry(club.view.getSidebarMenus) + self.core.registerSidebarEntry(club.view.getExtraMenus) + self.core.registerSidebarEntry(club_admin.view.getSidebarMenus) + self.core.registerSidebarEntry(club_member.view.getSidebarMenus) + self.core.registerSidebarEntry(club_app.view.getSidebarMenus) + + self.core.registerSidebarEntry(user_self.view.getSidebarMenus) + self.core.registerSidebarEntry(site.view.getSidebarMenus) + self.core.registerSidebarEntry(user.view.getSidebarMenus) + self.core.registerSidebarEntry(sponsor.view.getSidebarMenus) + self.core.registerSidebarEntry(sponsor.view.getExtraMenus) + self.core.registerSidebarEntry(host.view.getSidebarMenus) + self.core.registerSidebarEntry(request.view.getSidebarMenus) + self.core.registerSidebarEntry(program.view.getSidebarMenus) + self.core.registerSidebarEntry(program.view.getExtraMenus) + self.core.registerSidebarEntry(student.view.getSidebarMenus) + self.core.registerSidebarEntry(student_project.view.getSidebarMenus) + self.core.registerSidebarEntry(student_proposal.view.getSidebarMenus) + self.core.registerSidebarEntry(organization.view.getSidebarMenus) + self.core.registerSidebarEntry(organization.view.getExtraMenus) + self.core.registerSidebarEntry(org_admin.view.getSidebarMenus) + self.core.registerSidebarEntry(mentor.view.getSidebarMenus) + self.core.registerSidebarEntry(org_app.view.getSidebarMenus) diff -r 366e64ecba91 -r 58d2310330d3 app/soc/views/helper/responses.py --- a/app/soc/views/helper/responses.py Sun May 24 22:29:54 2009 +0200 +++ b/app/soc/views/helper/responses.py Sun May 24 22:32:39 2009 +0200 @@ -33,6 +33,7 @@ from soc.logic import system from soc.logic.models import site from soc.logic.models.user import logic as user_logic +from soc.modules import callback from soc.views import helper from soc.views.helper import redirects from soc.views.helper import templates @@ -125,7 +126,7 @@ context['sign_in'] = users.create_login_url(request.path) context['sign_out'] = users.create_logout_url(request.path) - context['sidebar_menu_items'] = sidebar.getSidebar(account, user) + context['sidebar_menu_items'] = callback.getCore().getSidebar(account, user) context['gae_version'] = system.getAppVersion() context['soc_release'] = system.getMelangeVersion() diff -r 366e64ecba91 -r 58d2310330d3 app/soc/views/models/base.py --- a/app/soc/views/models/base.py Sun May 24 22:29:54 2009 +0200 +++ b/app/soc/views/models/base.py Sun May 24 22:32:39 2009 +0200 @@ -41,7 +41,8 @@ from soc.views.helper import redirects from soc.views.helper import requests from soc.views.helper import responses -from soc.views import sitemap +from soc.views.sitemap import sidebar +from soc.views.sitemap import sitemap import soc.cache.logic import soc.logic @@ -952,7 +953,7 @@ of _getSidebarItems on how it uses it. """ - return sitemap.sidebar.getSidebarMenus(id, user, params=params) + return sidebar.getSidebarMenus(id, user, params=params) @decorators.merge_params def getDjangoURLPatterns(self, params=None): @@ -967,5 +968,5 @@ params: a dict with params for this View """ - return sitemap.sitemap.getDjangoURLPatterns(params) + return sitemap.getDjangoURLPatterns(params) diff -r 366e64ecba91 -r 58d2310330d3 app/soc/views/sitemap/build.py --- a/app/soc/views/sitemap/build.py Sun May 24 22:29:54 2009 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,112 +0,0 @@ -#!/usr/bin/python2.5 -# -# Copyright 2008 the Melange authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Module that constructs the sitemap. -""" - -__authors__ = [ - '"Sverre Rabbelier" ', - ] - - -from django.conf.urls import defaults - -#from soc.views.models import club -#from soc.views.models import club_app -#from soc.views.models import club_admin -#from soc.views.models import club_member -from soc.views.models import cron -from soc.views.models import document -from soc.views.models import host -from soc.views.models import job -from soc.views.models import mentor -from soc.views.models import notification -from soc.views.models import organization -from soc.views.models import org_admin -from soc.views.models import org_app -from soc.views.models import priority_group -from soc.views.models import program -from soc.views.models import request -from soc.views.models import site -from soc.views.models import sponsor -from soc.views.models import student -from soc.views.models import student_project -from soc.views.models import student_proposal -from soc.views.models import timeline -from soc.views.models import user -from soc.views.models import user_self - -from soc.views.sitemap import sidebar -from soc.views.sitemap import sitemap - - -# TODO: instead of commenting out club stuff, make it depend on a setting - - -sidebar.addMenu(user_self.view.getSidebarMenus) -#sidebar.addMenu(club.view.getSidebarMenus) -#sidebar.addMenu(club.view.getExtraMenus) -#sidebar.addMenu(club_admin.view.getSidebarMenus) -#sidebar.addMenu(club_member.view.getSidebarMenus) -#sidebar.addMenu(club_app.view.getSidebarMenus) -sidebar.addMenu(site.view.getSidebarMenus) -sidebar.addMenu(user.view.getSidebarMenus) -#sidebar.addMenu(document.view.getSidebarMenus) -sidebar.addMenu(sponsor.view.getSidebarMenus) -sidebar.addMenu(sponsor.view.getExtraMenus) -sidebar.addMenu(host.view.getSidebarMenus) -sidebar.addMenu(request.view.getSidebarMenus) -sidebar.addMenu(program.view.getSidebarMenus) -sidebar.addMenu(program.view.getExtraMenus) -sidebar.addMenu(student.view.getSidebarMenus) -sidebar.addMenu(student_project.view.getSidebarMenus) -sidebar.addMenu(student_proposal.view.getSidebarMenus) -sidebar.addMenu(organization.view.getSidebarMenus) -sidebar.addMenu(organization.view.getExtraMenus) -sidebar.addMenu(org_admin.view.getSidebarMenus) -sidebar.addMenu(mentor.view.getSidebarMenus) -sidebar.addMenu(org_app.view.getSidebarMenus) - -#sitemap.addPages(club.view.getDjangoURLPatterns()) -#sitemap.addPages(club_admin.view.getDjangoURLPatterns()) -#sitemap.addPages(club_app.view.getDjangoURLPatterns()) -#sitemap.addPages(club_member.view.getDjangoURLPatterns()) -sitemap.addPages(cron.view.getDjangoURLPatterns()) -sitemap.addPages(document.view.getDjangoURLPatterns()) -sitemap.addPages(host.view.getDjangoURLPatterns()) -sitemap.addPages(job.view.getDjangoURLPatterns()) -sitemap.addPages(mentor.view.getDjangoURLPatterns()) -sitemap.addPages(notification.view.getDjangoURLPatterns()) -sitemap.addPages(organization.view.getDjangoURLPatterns()) -sitemap.addPages(org_admin.view.getDjangoURLPatterns()) -sitemap.addPages(org_app.view.getDjangoURLPatterns()) -sitemap.addPages(priority_group.view.getDjangoURLPatterns()) -sitemap.addPages(program.view.getDjangoURLPatterns()) -sitemap.addPages(request.view.getDjangoURLPatterns()) -sitemap.addPages(site.view.getDjangoURLPatterns()) -sitemap.addPages(sponsor.view.getDjangoURLPatterns()) -sitemap.addPages(student.view.getDjangoURLPatterns()) -sitemap.addPages(student_project.view.getDjangoURLPatterns()) -sitemap.addPages(student_proposal.view.getDjangoURLPatterns()) -sitemap.addPages(timeline.view.getDjangoURLPatterns()) -sitemap.addPages(user_self.view.getDjangoURLPatterns()) -sitemap.addPages(user.view.getDjangoURLPatterns()) - - -def getPatterns(): - """Retrieves all the url patterns of this site. - """ - return defaults.patterns(None, *sitemap.SITEMAP) diff -r 366e64ecba91 -r 58d2310330d3 app/soc/views/sitemap/sidebar.py --- a/app/soc/views/sitemap/sidebar.py Sun May 24 22:29:54 2009 +0200 +++ b/app/soc/views/sitemap/sidebar.py Sun May 24 22:32:39 2009 +0200 @@ -24,38 +24,11 @@ from soc.views import out_of_band -import soc.cache.sidebar - -SIDEBAR = [] SIDEBAR_ACCESS_ARGS = ['SIDEBAR_CALLING'] SIDEBAR_ACCESS_KWARGS = {'SIDEBAR_CALLING': True} -def addMenu(callback): - """Adds a callback to the menu builder. - - The callback should return a list of menu's when called. - """ - global SIDEBAR - SIDEBAR.append(callback) - - -@soc.cache.sidebar.cache -def getSidebar(id, user): - """Constructs a sidebar for the current user. - """ - - sidebar = [] - - for callback in SIDEBAR: - menus = callback(id, user) - - for menu in (menus if menus else []): - sidebar.append(menu) - - return sorted(sidebar, key=lambda x: x.get('group')) - def getSidebarItems(params): """Retrieves a list of sidebar entries for this view. diff -r 366e64ecba91 -r 58d2310330d3 app/soc/views/sitemap/sitemap.py --- a/app/soc/views/sitemap/sitemap.py Sun May 24 22:29:54 2009 +0200 +++ b/app/soc/views/sitemap/sitemap.py Sun May 24 22:32:39 2009 +0200 @@ -22,17 +22,6 @@ ] -SITEMAP = [] - - -def addPages(pages): - """Adds the specified pages to the sitemap. - """ - - global SITEMAP - SITEMAP += pages - - def getDjangoURLPatterns(params): """Retrieves a list of sidebar entries for this View. diff -r 366e64ecba91 -r 58d2310330d3 app/urls.py --- a/app/urls.py Sun May 24 22:29:54 2009 +0200 +++ b/app/urls.py Sun May 24 22:32:39 2009 +0200 @@ -18,15 +18,15 @@ __authors__ = [ '"Augie Fackler" ', '"Todd Larsen" ', + '"Sverre Rabbelier" ', '"Lennard de Rijk" ', '"Pawel Solyga" ', ] -from soc.views.sitemap import build +from soc.modules import callback - -urlpatterns = build.getPatterns() +urlpatterns = callback.getCore().getPatterns() # define the error handlers handler404 = 'django.views.defaults.page_not_found'