from otree.extensions import get_extensions_modules, get_extensions_data_export_views import inspect from importlib import import_module from django.conf import urls from django.contrib.staticfiles.urls import staticfiles_urlpatterns from django.views.generic.base import RedirectView from django.conf import settings from django.contrib.auth.decorators import login_required from otree import common ALWAYS_UNRESTRICTED = { 'AssignVisitorToRoom', 'InitializeParticipant', 'MTurkLandingPage', 'MTurkStart', 'JoinSessionAnonymously', 'OutOfRangeNotification', 'ParticipantRoomHeartbeat', 'ParticipantHeartbeatGBAT', 'RESTCreateSession', 'RESTSessionVars', 'PostParticipantVarsThroughREST', 'CreateBrowserBotsSession', 'CloseBrowserBotsSession', 'BrowserBotStartLink', 'SaveDB', } UNRESTRICTED_IN_DEMO_MODE = ALWAYS_UNRESTRICTED.union( { 'AdminReport', 'AdvanceSession', 'CreateDemoSession', 'DemoIndex', 'SessionSplitScreen', 'SessionDescription', 'SessionMonitor', 'SessionPayments', 'SessionData', 'SessionStartLinks', } ) def view_classes_from_module(module_name): views_module = import_module(module_name) # what about custom views? return [ ViewCls for _, ViewCls in inspect.getmembers(views_module) if hasattr(ViewCls, 'url_pattern') and inspect.getmodule(ViewCls) == views_module ] def url_patterns_from_app_pages(module_name, name_in_url): views_module = import_module(module_name) view_urls = [] for ViewCls in views_module.page_sequence: url_pattern = ViewCls.url_pattern(name_in_url) url_name = ViewCls.url_name() view_urls.append(urls.url(url_pattern, ViewCls.as_view(), name=url_name)) return view_urls def url_patterns_from_builtin_module(module_name: str): all_views = view_classes_from_module(module_name) view_urls = [] for ViewCls in all_views: # automatically assign URL name for reverse(), it defaults to the # class's name url_name = getattr(ViewCls, 'url_name', ViewCls.__name__) unrestricted = { 'STUDY': url_name in ALWAYS_UNRESTRICTED, 'DEMO': url_name in UNRESTRICTED_IN_DEMO_MODE, '': True, None: True, }[settings.AUTH_LEVEL] if unrestricted: as_view = ViewCls.as_view() else: # i want to use # staff_member_required decorator # but then .test_auth_level fails on client.get(): # NoReverseMatch: 'admin' is not a registered namespace as_view = login_required(ViewCls.as_view()) url_pattern = ViewCls.url_pattern if callable(url_pattern): url_pattern = url_pattern() view_urls.append(urls.url(url_pattern, as_view, name=url_name)) return view_urls def extensions_urlpatterns(): urlpatterns = [] for url_module in get_extensions_modules('urls'): urlpatterns += getattr(url_module, 'urlpatterns', []) return urlpatterns def extensions_export_urlpatterns(): view_classes = get_extensions_data_export_views() view_urls = [] for ViewCls in view_classes: if settings.AUTH_LEVEL in {'DEMO', 'STUDY'}: as_view = login_required(ViewCls.as_view()) else: as_view = ViewCls.as_view() view_urls.append(urls.url(ViewCls.url_pattern, as_view, name=ViewCls.url_name)) return view_urls import django.contrib.auth.views as auth_views class LoginView(auth_views.LoginView): template_name = 'otree/login.html' class LogoutView(auth_views.LogoutView): next_page = 'DemoIndex' def get_urlpatterns(): urlpatterns = [ urls.url(r'^$', RedirectView.as_view(url='/demo', permanent=True)), urls.url(r'^accounts/login/$', LoginView.as_view(), name='login'), urls.url(r'^accounts/logout/$', LogoutView.as_view(), name='logout'), urls.url(r'^favicon\.ico$', RedirectView.as_view(url='/static/favicon.ico')), ] urlpatterns += staticfiles_urlpatterns() used_names_in_url = set() for app_name in settings.INSTALLED_OTREE_APPS: models_module = common.get_models_module(app_name) name_in_url = models_module.Constants.name_in_url if name_in_url in used_names_in_url: msg = ( "App {} has Constants.name_in_url='{}', " "which is already used by another app" ).format(app_name, name_in_url) raise ValueError(msg) used_names_in_url.add(name_in_url) views_module = common.get_pages_module(app_name) urlpatterns += url_patterns_from_app_pages(views_module.__name__, name_in_url) urlpatterns += url_patterns_from_builtin_module('otree.views.participant') urlpatterns += url_patterns_from_builtin_module('otree.views.demo') urlpatterns += url_patterns_from_builtin_module('otree.views.admin') urlpatterns += url_patterns_from_builtin_module('otree.views.room') urlpatterns += url_patterns_from_builtin_module('otree.views.mturk') urlpatterns += url_patterns_from_builtin_module('otree.views.export') urlpatterns += url_patterns_from_builtin_module('otree.views.rest') urlpatterns += extensions_urlpatterns() urlpatterns += extensions_export_urlpatterns() # serve an empty favicon? # otherwise, the logs will contain: # [WARNING] django.request > Not Found: /favicon.ico # Not Found: /favicon.ico # don't want to add a in base template because even if it exists, # browsers will still request /favicon.ico. # plus it makes the HTML noisier # can't use the static() function here because maybe collectstatic # has not been run yet # and it seems an empty HttpResponse or even a 204 response makes the browser # just keep requesting the file with every page load # hmmm...now it seems that chrome is not re-requesting with every page load # but firefox does. but if i remove the favicon, there's 1 404 then FF doesn't # ask for it again. # import os # dir_path = os.path.dirname(os.path.realpath(__file__)) # with open(os.path.join(dir_path, 'favicon_invisible.ico'), 'rb') as f: # #with open('favicon.ico', 'rb') as f: # favicon_content = f.read() # # # urlpatterns.append( # urls.url( # r'^favicon\.ico$', # lambda request: HttpResponse(favicon_content, content_type="image/x-icon") # ) # ) return urlpatterns urlpatterns = get_urlpatterns()