├── __init__.py ├── mobile ├── __init__.py ├── templatetags │ ├── __init__.py │ ├── mobile_extras.py │ └── whitespaceoptimize.py ├── templates │ ├── 404.html │ ├── 500.html │ ├── club_class_day_map.html │ ├── home2.html │ ├── calendar.html │ ├── search.html │ ├── instructor.html │ ├── geomap.html │ ├── home.html │ ├── club_class_day.html │ ├── base.html │ └── club_page.html ├── context_processors.py ├── middleware.py ├── tests.py ├── geo.py ├── urls.py ├── test_views.py ├── feeds.py ├── models.py ├── ukpostcode.py └── views.py ├── pagination ├── __init__.py ├── models.py ├── templatetags │ ├── __init__.py │ └── pagination_tags.py ├── middleware.py ├── locale │ └── de │ │ └── LC_MESSAGES │ │ └── django.po ├── templates │ └── pagination │ │ └── pagination.html └── tests.py ├── README ├── .gitignore ├── media └── images │ └── favicon.ico ├── default_googlemaps_api.key ├── default_yahoo_api.key ├── manage.py ├── urls.py ├── profilingmiddleware.py ├── settings.py ├── _models.py └── slimmer.py /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /mobile/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pagination/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /pagination/models.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /mobile/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | # 2 | -------------------------------------------------------------------------------- /pagination/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | The Django code for http://m.fwckungfu.com 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | settings_local.py 2 | default_googlemaps_api.key 3 | tags 4 | .venv 5 | 6 | -------------------------------------------------------------------------------- /media/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterbe/fwc_mobile/master/media/images/favicon.ico -------------------------------------------------------------------------------- /default_googlemaps_api.key: -------------------------------------------------------------------------------- 1 | ABQIAAAAnfs7bKE82qgb3Zc2YyS-oBT2yXp_ZAY8_ufC3CFXhHIE1NvwkxSySz_REpPq-4WZA27OwgbtyR3VcA -------------------------------------------------------------------------------- /default_yahoo_api.key: -------------------------------------------------------------------------------- 1 | u1SsEsLV34H1noY4L8gFtZ0yGHjjBkbmlELZfViUoPLwGqIUkHRbQ2dvsMDZTCOUN3J5cMiKfLdt18SGzjX3wg-- -------------------------------------------------------------------------------- /mobile/templates/404.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | 4 | {% block title %}Page not found{% endblock %} 5 | 6 | {% block main %} 7 | 8 |
Page not found :( 9 | 10 |
Here's a link to the homepage. You know, just in 11 | case.
12 | 13 | {% endblock %} -------------------------------------------------------------------------------- /mobile/context_processors.py: -------------------------------------------------------------------------------- 1 | def context(request): 2 | data = dict() 3 | 4 | user_agent = request.META.get('HTTP_USER_AGENT', '') 5 | 6 | data['iphone_version'] = False 7 | if user_agent.count('iPhone') and user_agent.count('AppleWebKit'): 8 | data['iphone_version'] = True 9 | 10 | 11 | return data -------------------------------------------------------------------------------- /mobile/middleware.py: -------------------------------------------------------------------------------- 1 | from django.utils.html import strip_spaces_between_tags as short 2 | 3 | 4 | ## http://www.davidcramer.net/code/369/spaceless-html-in-django.html 5 | class SpacelessMiddleware(object): 6 | def process_response(self, request, response): 7 | if 'text/html' in response['Content-Type']: 8 | response.content = short(response.content) 9 | return response -------------------------------------------------------------------------------- /mobile/tests.py: -------------------------------------------------------------------------------- 1 | # http://www.peterbe.com/plog/nasty-surprise-of-django-cache#c081210hpm1 2 | # By using locmem instead of whatever is in settings we can be certain that 3 | # the cache is reset when the test run because when you start the testrunner 4 | # you're starting a new python process and that's how locmem cache is reset 5 | from django.conf import settings 6 | settings.CACHE_BACKEND = 'locmem:///' 7 | 8 | from test_views import ViewsTestCase -------------------------------------------------------------------------------- /pagination/middleware.py: -------------------------------------------------------------------------------- 1 | class PaginationMiddleware(object): 2 | """ 3 | Inserts a variable representing the current page onto the request object if 4 | it exists in either **GET** or **POST** portions of the request. 5 | """ 6 | def process_request(self, request): 7 | try: 8 | request.page = int(request.REQUEST['page']) 9 | except (KeyError, ValueError, TypeError): 10 | request.page = 1 -------------------------------------------------------------------------------- /mobile/templates/500.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Page unavailable{% endblock %} 4 | 5 | {% block main %} 6 | 7 |Page unavailable 8 | 9 |
We're sorry, but the requested page is currently unavailable.
10 | 11 |We're messing around with things internally, and the server had 12 | a bit of a hiccup.
13 | 14 |Please try again later.
15 | 16 | {% endblock %} -------------------------------------------------------------------------------- /mobile/templatetags/mobile_extras.py: -------------------------------------------------------------------------------- 1 | # python 2 | import re 3 | 4 | # django 5 | from django import template 6 | from django.template.defaultfilters import stringfilter 7 | from django.utils.safestring import mark_safe 8 | 9 | # app 10 | 11 | # mandatory thing to have for magic to work 12 | register = template.Library() 13 | 14 | @register.filter() 15 | @stringfilter 16 | def tellink(value): 17 | value = value.strip().replace(' ','') 18 | if value.startswith('07'): 19 | value = '0044' + value[1:] 20 | return 'tel:%s' % value 21 | 22 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from django.core.management import execute_manager 3 | try: 4 | import settings # Assumed to be in the same directory. 5 | except ImportError: 6 | import sys 7 | sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__) 8 | sys.exit(1) 9 | 10 | if __name__ == "__main__": 11 | execute_manager(settings) 12 | -------------------------------------------------------------------------------- /mobile/geo.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | 3 | from geopy import geocoders 4 | 5 | class AddressNotFound(Exception): 6 | pass 7 | 8 | def geopy_geocode(address, google_key=settings.GOOGLEMAPS_API_KEY, 9 | domain='maps.google.co.uk', exactly_one=True): 10 | g = geocoders.Google(google_key, domain=domain) 11 | return g.geocode(address, exactly_one=exactly_one) 12 | 13 | def geopy_geocode_yahoo(address, yahoo_key=settings.YAHOO_API_KEY, 14 | exactly_one=True): 15 | g = geocoders.Yahoo(yahoo_key) 16 | return g.geocode(address, exactly_one=exactly_one) 17 | 18 | 19 | -------------------------------------------------------------------------------- /mobile/templates/club_class_day_map.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% block title %}{{ club.name }} {{ day }} - {{ block.super }}{% endblock %} 3 | {% block header_title_outer %}Sorry, no class on this day.
10 | {% endif %} 11 | 12 | {% if google_maps_url %} 13 |Sorry, unable to connect to Google Maps at the moment
23 | {% endif %} 24 | 25 |
26 | Back to class and venue page
27 | Back to {{ club.name }} club page
28 |
Clubs
23 | 24 |Calendar for the rest of this year only
32 | {% else %} 33 |34 | Calendar for whole of 35 | {% for year in whole_year_options %} 36 | {{ year }} 37 | {% endfor %} 38 |
39 | {% endif %} 40 | 41 | {% endblock %} -------------------------------------------------------------------------------- /mobile/templates/search.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block header_title_outer %}{{ count_results }} result{{ count_results|pluralize }}
18 | 19 | {% for result in searchresults %} 20 |
21 | {{ result.title }} ({{ result.type }})
22 | {% if result.description %}
23 | {{ result.description|safe }}
24 | {% endif %}
25 |
Sorry, nothing found.
31 | {% endif %} 32 | 33 | {% endif %} 34 | 35 | {% endblock %} 36 | 37 | {% block extrajs %} 38 | {% if q %} 39 | {% else %} 40 | 45 | {% endif %} 46 | {% endblock %} -------------------------------------------------------------------------------- /mobile/templates/instructor.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% load whitespaceoptimize %} 3 | {% block title %}Instructor {{ instructor.full_name }} - {{ block.super }}{% endblock %} 4 | {% block header_title_outer %}{{ instructor.full_name }}{#, {{ instructor.type }}#}
18 | 19 |Clubs
37 | 38 |Sorry, no class on this day.
47 | {% endif %} 48 | 49 | 50 |Back to {{ club.name }} club page
51 | {% endblock %} -------------------------------------------------------------------------------- /mobile/templatetags/whitespaceoptimize.py: -------------------------------------------------------------------------------- 1 | # python 2 | from slimmer import css_slimmer, guessSyntax, html_slimmer, js_slimmer 3 | 4 | # django 5 | from django import template 6 | 7 | class WhitespaceOptimizeNode(template.Node): 8 | def __init__(self, nodelist, format=None): 9 | self.nodelist = nodelist 10 | self.format = format 11 | def render(self, context): 12 | code = self.nodelist.render(context) 13 | if self.format == 'css': 14 | return css_slimmer(code) 15 | elif self.format in ('js', 'javascript'): 16 | return js_slimmer(code) 17 | elif self.format == 'html': 18 | return html_slimmer(code) 19 | else: 20 | format = guessSyntax(code) 21 | if format: 22 | self.format = format 23 | return self.render(context) 24 | 25 | return code 26 | 27 | 28 | register = template.Library() 29 | @register.tag(name='whitespaceoptimize') 30 | def do_whitespaceoptimize(parser, token): 31 | nodelist = parser.parse(('endwhitespaceoptimize',)) 32 | parser.delete_first_token() 33 | 34 | _split = token.split_contents() 35 | format = '' 36 | if len(_split) > 1: 37 | tag_name, format = _split 38 | if not (format[0] == format[-1] and format[0] in ('"', "'")): 39 | raise template.TemplateSyntaxError, \ 40 | "%r tag's argument should be in quotes" % tag_name 41 | 42 | return WhitespaceOptimizeNode(nodelist, format[1:-1]) 43 | 44 | -------------------------------------------------------------------------------- /pagination/tests.py: -------------------------------------------------------------------------------- 1 | """ 2 | >>> from django.core.paginator import Paginator 3 | >>> from pagination.templatetags.pagination_tags import paginate 4 | >>> from django.template import Template, Context 5 | 6 | >>> p = Paginator(range(15), 2) 7 | >>> paginate({'paginator': p, 'page_obj': p.page(1)})['pages'] 8 | [1, 2, 3, 4, 5, 6, 7, 8] 9 | 10 | >>> p = Paginator(range(17), 2) 11 | >>> paginate({'paginator': p, 'page_obj': p.page(1)})['pages'] 12 | [1, 2, 3, 4, 5, 6, 7, 8, 9] 13 | 14 | >>> p = Paginator(range(19), 2) 15 | >>> paginate({'paginator': p, 'page_obj': p.page(1)})['pages'] 16 | [1, 2, 3, 4, None, 7, 8, 9, 10] 17 | 18 | >>> p = Paginator(range(21), 2) 19 | >>> paginate({'paginator': p, 'page_obj': p.page(1)})['pages'] 20 | [1, 2, 3, 4, None, 8, 9, 10, 11] 21 | 22 | # Testing orphans 23 | >>> p = Paginator(range(5), 2, 1) 24 | >>> paginate({'paginator': p, 'page_obj': p.page(1)})['pages'] 25 | [1, 2] 26 | 27 | >>> p = Paginator(range(21), 2, 1) 28 | >>> paginate({'paginator': p, 'page_obj': p.page(1)})['pages'] 29 | [1, 2, 3, 4, None, 7, 8, 9, 10] 30 | 31 | >>> t = Template("{% load pagination_tags %}{% autopaginate var 2 %}{% paginate %}") 32 | 33 | # WARNING: Please, please nobody read this portion of the code! 34 | >>> class GetProxy(object): 35 | ... def __iter__(self): yield self.__dict__.__iter__ 36 | ... def copy(self): return self 37 | ... def urlencode(self): return u'' 38 | ... def keys(self): return [] 39 | >>> class RequestProxy(object): 40 | ... page = 1 41 | ... GET = GetProxy() 42 | >>> 43 | # ENDWARNING 44 | 45 | >>> t.render(Context({'var': range(21), 'request': RequestProxy()})) 46 | u'\\n\\nInstructor announcements
27 | 28 |Classes {{ each.tonight_or_today }} {% now "l" %} - {{ each.classes_today_venue }}
40 | 41 |Class times
66 | 67 |" + \ 86 | " ---- By file ----\n\n" + self.get_summary(mystats,sum) + "\n" + \ 87 | " ---- By group ---\n\n" + self.get_summary(mygroups,sum) + \ 88 | "" 89 | 90 | def process_response(self, request, response): 91 | if (settings.DEBUG or request.user.is_superuser) and request.GET.has_key('prof'): 92 | self.prof.close() 93 | 94 | out = StringIO.StringIO() 95 | old_stdout = sys.stdout 96 | sys.stdout = out 97 | 98 | stats = hotshot.stats.load(self.tmpfile) 99 | stats.sort_stats('time', 'calls') 100 | stats.print_stats() 101 | 102 | sys.stdout = old_stdout 103 | stats_str = out.getvalue() 104 | 105 | if response and response.content and stats_str: 106 | response.content = "
" + stats_str + "" 107 | 108 | response.content = "\n".join(response.content.split("\n")[:40]) 109 | 110 | response.content += self.summary_for_files(stats_str) 111 | 112 | os.unlink(self.tmpfile) 113 | 114 | return response 115 | -------------------------------------------------------------------------------- /settings.py: -------------------------------------------------------------------------------- 1 | # Django settings for fwc_mobile project. 2 | 3 | import os 4 | HERE = os.path.abspath(os.path.dirname(__file__)) 5 | path = lambda *x: os.path.join(HERE, *x) 6 | 7 | DEBUG = TEMPLATE_DEBUG = False 8 | 9 | HOME = HERE 10 | 11 | DATABASES = { 12 | 'default': { 13 | 'ENGINE': 'django.db.backends.', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. 14 | 'NAME': '', # Or path to database file if using sqlite3. 15 | 'USER': '', # Not used with sqlite3. 16 | 'PASSWORD': '', # Not used with sqlite3. 17 | 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. 18 | 'PORT': '', # Set to empty string for default. Not used with sqlite3. 19 | } 20 | } 21 | 22 | ADMINS = ( 23 | ) 24 | 25 | EMAIL_HOST = '' 26 | 27 | EMAIL_SUBJECT_PREFIX = '[FWC Mobile]' 28 | 29 | MANAGERS = ADMINS 30 | 31 | 32 | # Local time zone for this installation. Choices can be found here: 33 | # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name 34 | # although not all choices may be available on all operating systems. 35 | # If running in a Windows environment this must be set to the same as your 36 | # system time zone. 37 | TIME_ZONE = 'Europe/London' 38 | 39 | # Language code for this installation. All choices can be found here: 40 | # http://www.i18nguy.com/unicode/language-identifiers.html 41 | LANGUAGE_CODE = 'en-us' 42 | 43 | SITE_ID = 1 44 | 45 | # If you set this to False, Django will make some optimizations so as not 46 | # to load the internationalization machinery. 47 | USE_I18N = False 48 | 49 | # Absolute path to the directory that holds media. 50 | # Example: "/home/media/media.lawrence.com/" 51 | MEDIA_ROOT = HOME + '/media' 52 | 53 | # URL that handles the media served from MEDIA_ROOT. Make sure to use a 54 | # trailing slash if there is a path component (optional in other cases). 55 | # Examples: "http://media.lawrence.com", "http://example.com/media/" 56 | MEDIA_URL = '' 57 | 58 | # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a 59 | # trailing slash. 60 | # Examples: "http://foo.com/media/", "/media/". 61 | ADMIN_MEDIA_PREFIX = '/media/' 62 | 63 | # Make this unique, and don't share it with anybody. 64 | SECRET_KEY = 'you must set this in your settings_local.py' 65 | 66 | # List of callables that know how to import templates from various sources. 67 | TEMPLATE_LOADERS = ( 68 | 'django.template.loaders.filesystem.Loader', 69 | 'django.template.loaders.app_directories.Loader', 70 | # 'django.template.loaders.eggs.Loader', 71 | ) 72 | 73 | MIDDLEWARE_CLASSES = ( 74 | 'django.middleware.common.CommonMiddleware', 75 | 'django.contrib.sessions.middleware.SessionMiddleware', 76 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 77 | 'pagination.middleware.PaginationMiddleware', 78 | 'mobile.middleware.SpacelessMiddleware', 79 | #'djangobile.middleware.DjangoMobileMiddleware', 80 | ) 81 | 82 | #ROOT_URLCONF = 'fwc_mobile.urls' 83 | ROOT_URLCONF = '%s.urls' % os.path.basename(HERE) 84 | 85 | TEMPLATE_DIRS = ( 86 | # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". 87 | # Always use forward slashes, even on Windows. 88 | # Don't forget to use absolute paths, not relative paths. 89 | path('mobile', '/templates'), 90 | path('templates'), 91 | ) 92 | 93 | INSTALLED_APPS = ( 94 | 'django.contrib.auth', 95 | 'django.contrib.contenttypes', 96 | 'django.contrib.sessions', 97 | 'django.contrib.sites', 98 | 'mobile', 99 | 'pagination', 100 | ) 101 | 102 | TEMPLATE_STRING_IF_INVALID = '' 103 | 104 | CACHES = { 105 | 'default': { 106 | 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 107 | 'LOCATION': '127.0.0.1:11211', 108 | 'TIMEOUT': 500, 109 | 'KEY_PREFIX': 'fwcm', 110 | } 111 | } 112 | 113 | 114 | 115 | xxxTEMPLATE_CONTEXT_PROCESSORS = ( 116 | 'django.core.context_processors.auth', 117 | 'django.core.context_processors.debug', 118 | 'django.core.context_processors.i18n', 119 | 'django.core.context_processors.media', 120 | 'mobile.context_processors.context', 121 | ) 122 | 123 | TEMPLATE_CONTEXT_PROCESSORS = ( 124 | 'django.contrib.auth.context_processors.auth', 125 | 'django.core.context_processors.debug', 126 | 'django.core.context_processors.i18n', 127 | 'django.core.context_processors.media', 128 | 'django.core.context_processors.static', 129 | 'django.core.context_processors.tz', 130 | 'mobile.context_processors.context', 131 | # 'django.contrib.messages.context_processors.messages' 132 | ) 133 | 134 | 135 | 136 | try: 137 | from settings_local import * 138 | except ImportError: 139 | pass 140 | 141 | try: 142 | GOOGLEMAPS_API_KEY 143 | except NameError: 144 | GOOGLEMAPS_API_KEY = open(HOME +'/m.fwckungfu.com.googlemaps_api.key').read() 145 | 146 | try: 147 | YAHOO_API_KEY 148 | except NameError: 149 | YAHOO_API_KEY = open(HOME +'/m.fwckungfu.com.yahoo_api.key').read() 150 | -------------------------------------------------------------------------------- /pagination/templatetags/pagination_tags.py: -------------------------------------------------------------------------------- 1 | try: 2 | set 3 | except NameError: 4 | from sets import Set as set 5 | from django import template 6 | from django.db.models.query import QuerySet 7 | from django.core.paginator import Paginator, InvalidPage 8 | from django.conf import settings 9 | 10 | register = template.Library() 11 | 12 | DEFAULT_PAGINATION = getattr(settings, 'PAGINATION_DEFAULT_PAGINATION', 20) 13 | DEFAULT_WINDOW = getattr(settings, 'PAGINATION_DEFAULT_WINDOW', 4) 14 | DEFAULT_ORPHANS = getattr(settings, 'PAGINATION_DEFAULT_ORPHANS', 0) 15 | 16 | def do_autopaginate(parser, token): 17 | """ 18 | Splits the arguments to the autopaginate tag and formats them correctly. 19 | """ 20 | split = token.split_contents() 21 | if len(split) == 2: 22 | return AutoPaginateNode(split[1]) 23 | elif len(split) == 3: 24 | try: 25 | paginate_by = int(split[2]) 26 | except ValueError: 27 | raise template.TemplateSyntaxError(u'Got %s, but expected integer.' % split[2]) 28 | return AutoPaginateNode(split[1], paginate_by=paginate_by) 29 | elif len(split) == 4: 30 | try: 31 | paginate_by = int(split[2]) 32 | except ValueError: 33 | raise template.TemplateSyntaxError(u'Got %s, but expected integer.' % split[2]) 34 | try: 35 | orphans = int(split[3]) 36 | except ValueError: 37 | raise template.TemplateSyntaxError(u'Got %s, but expected integer.' % split[3]) 38 | return AutoPaginateNode(split[1], paginate_by=paginate_by, orphans=orphans) 39 | else: 40 | raise template.TemplateSyntaxError('%r tag takes one required argument and one optional argument' % split[0]) 41 | 42 | class AutoPaginateNode(template.Node): 43 | """ 44 | Emits the required objects to allow for Digg-style pagination. 45 | 46 | First, it looks in the current context for the variable specified, and using 47 | that object, it emits a simple ``Paginator`` and the current page object 48 | into the context names ``paginator`` and ``page_obj``, respectively. 49 | 50 | It will then replace the variable specified with only the objects for the 51 | current page. 52 | 53 | .. note:: 54 | 55 | It is recommended to use *{% paginate %}* after using the autopaginate 56 | tag. If you choose not to use *{% paginate %}*, make sure to display the 57 | list of available pages, or else the application may seem to be buggy. 58 | """ 59 | def __init__(self, queryset_var, paginate_by=DEFAULT_PAGINATION, orphans=DEFAULT_ORPHANS): 60 | self.queryset_var = template.Variable(queryset_var) 61 | self.paginate_by = paginate_by 62 | self.orphans = orphans 63 | 64 | def render(self, context): 65 | key = self.queryset_var.var 66 | value = self.queryset_var.resolve(context) 67 | paginator = Paginator(value, self.paginate_by, self.orphans) 68 | try: 69 | page_obj = paginator.page(context['request'].page) 70 | except InvalidPage: 71 | context[key] = [] 72 | context['invalid_page'] = True 73 | return u'' 74 | context[key] = page_obj.object_list 75 | context['paginator'] = paginator 76 | context['page_obj'] = page_obj 77 | return u'' 78 | 79 | def paginate(context, window=DEFAULT_WINDOW): 80 | """ 81 | Renders the ``pagination/pagination.html`` template, resulting in a 82 | Digg-like display of the available pages, given the current page. If there 83 | are too many pages to be displayed before and after the current page, then 84 | elipses will be used to indicate the undisplayed gap between page numbers. 85 | 86 | Requires one argument, ``context``, which should be a dictionary-like data 87 | structure and must contain the following keys: 88 | 89 | ``paginator`` 90 | A ``Paginator`` or ``QuerySetPaginator`` object. 91 | 92 | ``page_obj`` 93 | This should be the result of calling the page method on the 94 | aforementioned ``Paginator`` or ``QuerySetPaginator`` object, given 95 | the current page. 96 | 97 | This same ``context`` dictionary-like data structure may also include: 98 | 99 | ``getvars`` 100 | A dictionary of all of the **GET** parameters in the current request. 101 | This is useful to maintain certain types of state, even when requesting 102 | a different page. 103 | """ 104 | try: 105 | paginator = context['paginator'] 106 | page_obj = context['page_obj'] 107 | page_range = paginator.page_range 108 | # First and last are simply the first *n* pages and the last *n* pages, 109 | # where *n* is the current window size. 110 | first = set(page_range[:window]) 111 | last = set(page_range[-window:]) 112 | # Now we look around our current page, making sure that we don't wrap 113 | # around. 114 | current_start = page_obj.number-1-window 115 | if current_start < 0: 116 | current_start = 0 117 | current_end = page_obj.number-1+window 118 | if current_end < 0: 119 | current_end = 0 120 | current = set(page_range[current_start:current_end]) 121 | pages = [] 122 | # If there's no overlap between the first set of pages and the current 123 | # set of pages, then there's a possible need for elusion. 124 | if len(first.intersection(current)) == 0: 125 | first_list = sorted(first) 126 | second_list = sorted(current) 127 | pages.extend(first_list) 128 | diff = second_list[0] - first_list[-1] 129 | # If there is a gap of two, between the last page of the first 130 | # set and the first page of the current set, then we're missing a 131 | # page. 132 | if diff == 2: 133 | pages.append(second_list[0] - 1) 134 | # If the difference is just one, then there's nothing to be done, 135 | # as the pages need no elusion and are correct. 136 | elif diff == 1: 137 | pass 138 | # Otherwise, there's a bigger gap which needs to be signaled for 139 | # elusion, by pushing a None value to the page list. 140 | else: 141 | pages.append(None) 142 | pages.extend(second_list) 143 | else: 144 | pages.extend(sorted(first.union(current))) 145 | # If there's no overlap between the current set of pages and the last 146 | # set of pages, then there's a possible need for elusion. 147 | if len(current.intersection(last)) == 0: 148 | second_list = sorted(last) 149 | diff = second_list[0] - pages[-1] 150 | # If there is a gap of two, between the last page of the current 151 | # set and the first page of the last set, then we're missing a 152 | # page. 153 | if diff == 2: 154 | pages.append(second_list[0] - 1) 155 | # If the difference is just one, then there's nothing to be done, 156 | # as the pages need no elusion and are correct. 157 | elif diff == 1: 158 | pass 159 | # Otherwise, there's a bigger gap which needs to be signaled for 160 | # elusion, by pushing a None value to the page list. 161 | else: 162 | pages.append(None) 163 | pages.extend(second_list) 164 | else: 165 | pages.extend(sorted(last.difference(current))) 166 | to_return = { 167 | 'pages': pages, 168 | 'page_obj': page_obj, 169 | 'paginator': paginator, 170 | 'is_paginated': paginator.count > paginator.per_page, 171 | } 172 | if 'request' in context: 173 | getvars = context['request'].GET.copy() 174 | if 'page' in getvars: 175 | del getvars['page'] 176 | if len(getvars.keys()) > 0: 177 | to_return['getvars'] = "&%s" % getvars.urlencode() 178 | else: 179 | to_return['getvars'] = '' 180 | return to_return 181 | except KeyError: 182 | return {} 183 | register.inclusion_tag('pagination/pagination.html', takes_context=True)(paginate) 184 | register.tag('autopaginate', do_autopaginate) 185 | -------------------------------------------------------------------------------- /mobile/test_views.py: -------------------------------------------------------------------------------- 1 | # python 2 | #import unittest 3 | from datetime import datetime, timedelta, date 4 | from pprint import pprint 5 | 6 | # django 7 | from django.test.client import Client 8 | from django.test import TestCase 9 | from django.shortcuts import render_to_response 10 | from django.template import RequestContext 11 | from django.core.cache import cache 12 | from django.utils.html import escape 13 | 14 | # project 15 | from mobile.models import * 16 | from mobile.views import club_page 17 | 18 | 19 | class ViewsTestCase(TestCase): 20 | """ 21 | Test the views 22 | """ 23 | 24 | 25 | def test_club_page(self): 26 | """ test rendering the club page """ 27 | 28 | dave = Instructor.objects.create(full_name=u'Dave Courtney Jones', 29 | first_name=u'Dave', 30 | last_name=u'Courtney Jones') 31 | 32 | city = Club.objects.create(head_instructor=dave, 33 | name=u'FWC City & Islington', 34 | ) 35 | 36 | # and some classes 37 | class1 = ClubClass.objects.create(club=city, 38 | day=datetime.now().strftime('%A'), 39 | start_time='18:00', end_time='19:00', 40 | description1='Kung Fu', 41 | address1='Clarmont Hall', 42 | address2='White Lion Street', 43 | style='Kung fu') 44 | 45 | client = Client() 46 | club_url = city.get_absolute_url() 47 | response = client.get(club_url) 48 | assert response.status_code==200 49 | instructor_url = dave.get_absolute_url() 50 | assert response.content.count(escape(instructor_url)) 51 | class_day_url = class1.get_absolute_url(without_time=True) 52 | assert response.content.count(escape(class_day_url)) 53 | assert not response.content.count('Sparring') 54 | 55 | 56 | # If you add a new class and re-render the club page for 57 | # this club a cache will still only show the one class 58 | class2 = ClubClass.objects.create(club=city, 59 | day=datetime.now().strftime('%A'), 60 | start_time='19:00', end_time='20:00', 61 | address1='Clarmont Hall', 62 | address2='White Lion Street', 63 | style='Sparring') 64 | 65 | response = client.get(club_url) 66 | assert response.status_code==200 67 | instructor_url = dave.get_absolute_url() 68 | assert response.content.count(escape(instructor_url)) 69 | class_day_url = class1.get_absolute_url(without_time=True) 70 | assert response.content.count(escape(class_day_url)) 71 | 72 | #print response.content 73 | # The newly added 'Sparring' class shouldn't be there yet 74 | # because the page is cached 75 | assert response.content.find('Sparring') == -1 76 | 77 | # add another club and it shouldn't be there for this club 78 | eddie = Instructor.objects.create(full_name=u'Eddie Walsh', 79 | first_name=u'Eddie', 80 | last_name=u'Walsh') 81 | 82 | ireland = Club.objects.create(head_instructor=dave, 83 | name=u'FWC Ireland', 84 | ) 85 | 86 | ireland_class1 = ClubClass.objects.create(club=ireland, 87 | day=datetime.now().strftime('%A'), 88 | start_time='18:00', end_time='19:00', 89 | address1='Elsewhere', 90 | address2='Street 2', 91 | style='Sparring') 92 | 93 | response = client.get(club_url) 94 | assert response.status_code==200 95 | 96 | assert not response.content.count('Eddie') 97 | assert not response.content.count('Ireland') 98 | assert not response.content.count('Elsewhere') 99 | 100 | 101 | ## def test_feeds(self): 102 | ## """ test publishing the feeds """ 103 | ## dave = Instructor.objects.create(full_name=u'Dave Courtney Jones', 104 | ## first_name=u'Dave', 105 | ## last_name=u'Courtney Jones') 106 | ## 107 | ## city = Club.objects.create(head_instructor=dave, 108 | ## name=u'FWC City & Islington', 109 | ## ) 110 | ## 111 | ## # and some classes 112 | ## class1 = ClubClass.objects.create(club=city, 113 | ## day=datetime.now().strftime('%A'), 114 | ## start_time='18:00', end_time='19:00', 115 | ## description1='Kung Fu', 116 | ## address1='Clarmont Hall', 117 | ## address2='White Lion Street', 118 | ## address3='London', 119 | ## address4='N1 9PD', 120 | ## style='Kung fu') 121 | ## 122 | ## client = Client() 123 | ## res = client.get('/feeds/all-clubs/') 124 | ## assert res.status_code == 200, res.status_code 125 | ## #print res.content.replace('><','>\n<') 126 | 127 | def test_geo_feeds(self): 128 | """ test publishing the geo feeds """ 129 | dave = Instructor.objects.create(full_name=u'Dave Courtney Jones', 130 | first_name=u'Dave', 131 | last_name=u'Courtney Jones') 132 | 133 | assert Club.objects.all().count() == 0 134 | city = Club.objects.create(head_instructor=dave, 135 | name=u'FWC City & Islington', 136 | ) 137 | 138 | assert ClubClass.objects.filter(club=city).count() == 0 139 | 140 | # and some classes 141 | class1 = ClubClass.objects.create(club=city, 142 | day='Monday', 143 | start_time='18:00', end_time='19:00', 144 | description1='Kung Fu', 145 | address1='Clarmont Halll', 146 | address2='White Lion Street', 147 | address3='London', 148 | address4='N1 9PD', 149 | style='Kung fu') 150 | class2 = ClubClass.objects.create(club=city, 151 | day='Monday', 152 | start_time='19:00', end_time='20:00', 153 | description1='Suang Yang', 154 | address1='Clarmont Halll', 155 | address2='White Lion Street', 156 | address3='London', 157 | address4='N1 9PD', 158 | style='Sparring') 159 | 160 | class3 = ClubClass.objects.create(club=city, 161 | day='Friday', 162 | start_time='18:00', end_time='19:00', 163 | description1='Patterns', 164 | address1='Studio 1', 165 | address2='Saddlers Sports Centre', 166 | address3='122 Goswell Road', 167 | address4='London', 168 | address5='', 169 | style='Patterns') 170 | 171 | client = Client() 172 | res = client.get('/feeds/all-classes/') 173 | assert res.status_code == 200, res.status_code 174 | print res.content.replace('><','>\n<') 175 | 176 | assert '