From 667f4d923a7c9ad3d30a70bba2b546a3afd7dec7 Mon Sep 17 00:00:00 2001 From: Steven Cummings Date: Fri, 18 Nov 2011 22:28:07 -0600 Subject: [PATCH] Enabled mobile site with very basic templates * Added middleware to detect mobile user-agents * Updated views to utilize mobile detection and switch between templates * Added a past-meetups URL * Added *very* basic mobile templates for next/past meetups * Added mobile.css, used by those templates * Removed some unnecessary middleware/apps --- pythonkc_site/settings.py | 16 +---- pythonkc_site/static/mobile.css | 57 ++++++++++++++++ .../templates/{index.html => home.html} | 0 pythonkc_site/templates/mobile/home.html | 67 +++++++++++++++++++ .../templates/mobile/past_meetups.html | 46 +++++++++++++ pythonkc_site/ua_detection/__init__.py | 32 +++++++++ pythonkc_site/ua_detection/middleware.py | 10 +++ pythonkc_site/ua_detection/types.py | 50 ++++++++++++++ pythonkc_site/urls.py | 20 +----- pythonkc_site/views.py | 55 ++++++++++++++- 10 files changed, 319 insertions(+), 34 deletions(-) create mode 100644 pythonkc_site/static/mobile.css rename pythonkc_site/templates/{index.html => home.html} (100%) create mode 100644 pythonkc_site/templates/mobile/home.html create mode 100644 pythonkc_site/templates/mobile/past_meetups.html create mode 100644 pythonkc_site/ua_detection/__init__.py create mode 100644 pythonkc_site/ua_detection/middleware.py create mode 100644 pythonkc_site/ua_detection/types.py diff --git a/pythonkc_site/settings.py b/pythonkc_site/settings.py index 244225a..3c48752 100644 --- a/pythonkc_site/settings.py +++ b/pythonkc_site/settings.py @@ -94,10 +94,8 @@ MIDDLEWARE_CLASSES = ( 'django.middleware.common.CommonMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', + 'pythonkc_site.ua_detection.middleware.UserAgentTypeDetectionMiddleware' ) ROOT_URLCONF = 'pythonkc_site.urls' @@ -107,27 +105,17 @@ ) INSTALLED_APPS = ( - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.sites', - 'django.contrib.messages', + #'django.contrib.contenttypes', 'django.contrib.staticfiles', - # Uncomment the next line to enable the admin: - #'django.contrib.admin', - # Uncomment the next line to enable admin documentation: - #'django.contrib.admindocs', 'south', 'pythonkc_site.contact', ) TEMPLATE_CONTEXT_PROCESSORS = ( - "django.contrib.auth.context_processors.auth", "django.core.context_processors.debug", "django.core.context_processors.i18n", "django.core.context_processors.media", "django.core.context_processors.static", - "django.contrib.messages.context_processors.messages", "django.core.context_processors.request", ) diff --git a/pythonkc_site/static/mobile.css b/pythonkc_site/static/mobile.css new file mode 100644 index 0000000..896f444 --- /dev/null +++ b/pythonkc_site/static/mobile.css @@ -0,0 +1,57 @@ +/* ---------- RESET ---------- */ + +html, body, div, span, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp, +small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, figcaption, figure, +footer, header, hgroup, menu, nav, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + vertical-align: baseline; +} + +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} + +blockquote, q { quotes: none; } + +blockquote:before, blockquote:after, +q:before, q:after { content: ""; content: none; } + +ins { background-color: #ff9; color: #000; text-decoration: none; } +mark { background-color: #ff9; color: #000; font-style: italic; font-weight: bold; } +del { text-decoration: line-through; } +abbr[title], dfn[title] { border-bottom: 1px dotted; cursor: help; } +table { border-collapse: collapse; border-spacing: 0; } +hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; } +textarea, input { border: 1px solid #ddd; background: #FFFFFF; font-weight: normal; color: #333; font-size: 13px; } +ul, li { list-style: none; } +a { text-decoration: none; color:#646565; } +a:hover { text-decoration: underline; } +.clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } +.clearfix { display: inline-block; } +* html .clearfix { height: 1%; } +.clearfix { display: block; } + +html { height: 100%; background: #87cded; } +body { + height: 100%; + margin: 0; + padding: 0; + font: 16px/1.4 'Helvetica Neue', Arial, Helvetica, sans-serif normal; + color: #646565; + background-image: -moz-radial-gradient(50% 28%, circle cover, rgba(255,255,255, .3) 0%,#87cded 18.0%); + background-image: -webkit-radial-gradient(50% 28%, circle cover, rgba(255,255,255, .3) 0%,#87cded 18.0%); + background-image: -o-radial-gradient(50% 28%, circle cover, rgba(255,255,255, .3) 0%,#87cded 18.0%); + background-image: -ms-radial-gradient(50% 28%, circle cover, rgba(255,255,255, .3) 0%,#87cded 18.0%); + background-image: radial-gradient(50% 28%, circle cover, rgba(255,255,255, .3) 0%,#87cded 18.0%); +} + diff --git a/pythonkc_site/templates/index.html b/pythonkc_site/templates/home.html similarity index 100% rename from pythonkc_site/templates/index.html rename to pythonkc_site/templates/home.html diff --git a/pythonkc_site/templates/mobile/home.html b/pythonkc_site/templates/mobile/home.html new file mode 100644 index 0000000..2c1c5ed --- /dev/null +++ b/pythonkc_site/templates/mobile/home.html @@ -0,0 +1,67 @@ + + + + + + + + + PythonKC || For all things Python in Kansas City. + + + + +
+
+
+
+
+
+ +
+

{{ next_event.name|safe }}

+ {% if next_event.venue.id %}
{{ next_event.venue.name }}{% if next_event.venue.address_1 %} // {{ next_event.venue.address_1 }}{% if next_event.venue.address_2 %}, {{ next_event.venue.address_2 }}{% endif %}{% endif %} // {{ next_event.venue.city }}, {{ next_event.venue.state }} {{ next_event.venue.zip }}
{% else %}Venue TBD{% endif %} + {% if next_event.description %}

{{ next_event.description|safe }}

{% endif %} +

RSVP at PythonKC Meetup RSVP for {{ next_event.name|safe }}

+
+
+
+
+ Previous meetups +
+ + + + + + + + + diff --git a/pythonkc_site/templates/mobile/past_meetups.html b/pythonkc_site/templates/mobile/past_meetups.html new file mode 100644 index 0000000..dc9575a --- /dev/null +++ b/pythonkc_site/templates/mobile/past_meetups.html @@ -0,0 +1,46 @@ + + + + + + + + + PythonKC || For all things Python in Kansas City. + + + + +
+
+

Past Events

+
    +
  1. + Go back +
  2. + {% for event in past_events %} +
  3. + + + {{ event.name }} + +
  4. + {% endfor %} +
+
+ + + + diff --git a/pythonkc_site/ua_detection/__init__.py b/pythonkc_site/ua_detection/__init__.py new file mode 100644 index 0000000..0826c07 --- /dev/null +++ b/pythonkc_site/ua_detection/__init__.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +""" +Overview +======== +Simple detection of user agent types, where "type" is defined as one of: + +* Standard +* Mobile + +The idea is to generically categorize user agents with simple user agent string +matching, instead of full determination of user agent capabilities. + +If we need to we could add tablet detection and that type as well. + +Usage +===== +Simply install the middleware:: + + pythonkc_site.ua_detection.middleware.UserAgentTypeDetectionMiddleware + +Then request objects will have the following additional attributes: + +is_mobile + Boolean value indicating whether or not the request was made by a mobile + user-agent. + +Derived from: + +* http://mobiforge.com/developing/story/build-a-mobile-and-desktop-friendly-application-django-15-minutes +* http://www.entzeroth.com/code + +""" diff --git a/pythonkc_site/ua_detection/middleware.py b/pythonkc_site/ua_detection/middleware.py new file mode 100644 index 0000000..24d7142 --- /dev/null +++ b/pythonkc_site/ua_detection/middleware.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- + + +from pythonkc_site.ua_detection.types import is_mobile_browser + + +class UserAgentTypeDetectionMiddleware(object): + + def process_request(self, request): + request.is_mobile = is_mobile_browser(request) diff --git a/pythonkc_site/ua_detection/types.py b/pythonkc_site/ua_detection/types.py new file mode 100644 index 0000000..f0a367d --- /dev/null +++ b/pythonkc_site/ua_detection/types.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +""" +Sources of prefix and hint list: + +* http://mobiforge.com/developing/story/build-a-mobile-and-desktop-friendly-application-django-15-minutes +* http://www.entzeroth.com/code +* http://www.useragentstring.com + +""" + + +mobile_ua_prefixes = [ + 'w3c ','acs-','alav','alca','amoi','audi','avan','benq','bird','blac', + 'blaz','brew','cell','cldc','cmd-','dang','doco','eric','hipt','inno', + 'ipaq','java','jigs','kddi','keji','leno','lg-c','lg-d','lg-g','lge-', + 'maui','maxo','midp','mits','mmef','mobi','mot-','moto','mwbp','nec-', + 'newt','noki','palm','pana','pant','phil','play','port','prox','qwap', + 'sage','sams','sany','sch-','sec-','send','seri','sgh-','shar','sie-', + 'siem','smal','smar','sony','sph-','symb','t-mo','teli','tim-','tosh', + 'tsm-','upg1','upsi','vk-v','voda','wap-','wapa','wapi','wapp','wapr', + 'webc','winw','winw','xda','xda-' +] + + +mobile_ua_hints = [ + 'SymbianOS', + 'Opera Mini', + 'iPhone', + 'Android', + 'Opera Mobi', + 'webOS', + 'BlackBerry', + # Portable game consoles + 'Bunjalloo', # Nintendo DS + 'PlayStation Portable' +] + + +def is_mobile_browser(request): + user_agent = request.META.get('HTTP_USER_AGENT', '') + ua_prefix = user_agent.lower()[:4] + + if ua_prefix in mobile_ua_prefixes: + return True + + for hint in mobile_ua_hints: + if user_agent.find(hint) >= 0: + return True + + return False diff --git a/pythonkc_site/urls.py b/pythonkc_site/urls.py index 9b998ce..3c8a827 100644 --- a/pythonkc_site/urls.py +++ b/pythonkc_site/urls.py @@ -4,24 +4,10 @@ from django.conf.urls.defaults import include from django.conf.urls.defaults import patterns from django.conf.urls.defaults import url -from pythonkc_site.views import PythonKCHome -# Uncomment the next two lines to enable the admin: -# from django.contrib import admin -# admin.autodiscover() urlpatterns = patterns('', - url(r'^/?$', PythonKCHome.as_view(), name='home'), - - # Examples: - # url(r'^$', 'pythonkc_site.views.home', name='home'), - # url(r'^pythonkc_site/', include('pythonkc_site.foo.urls')), - - # Uncomment the admin/doc line below to enable admin documentation: - # url(r'^admin/doc/', include('django.contrib.admindocs.urls')), - - # Uncomment the next line to enable the admin: - # url(r'^admin/', include(admin.site.urls)), + url(r'^/?$', 'pythonkc_site.views.home', name='home'), + url(r'^meetups/past/?$', 'pythonkc_site.views.past_meetups', + name='past-meetups'), ) - - diff --git a/pythonkc_site/views.py b/pythonkc_site/views.py index b92789a..fd26d16 100644 --- a/pythonkc_site/views.py +++ b/pythonkc_site/views.py @@ -1,15 +1,18 @@ # -*- coding: utf-8 -*- +from django.conf import settings from django.core.urlresolvers import reverse +from django.shortcuts import redirect from django.views.generic import FormView +from django.views.generic import TemplateView from pythonkc_site.contact.email import send_contact_form_email from pythonkc_site.contact.forms import ContactForm from pythonkc_site.meetups import events -class PythonKCHome(FormView): - template_name = 'index.html' +class StandardHome(FormView): + template_name = 'home.html' form_class = ContactForm def get_success_url(self): @@ -17,7 +20,7 @@ def get_success_url(self): def form_valid(self, form): send_contact_form_email(**form.cleaned_data) - return super(PythonKCHome, self).form_valid(form) + return super(StandardHome, self).form_valid(form) def get_context_data(self, **kwargs): return { @@ -25,3 +28,49 @@ def get_context_data(self, **kwargs): 'past_events': events.get_past_events(), 'form': kwargs.get('form', None) or ContactForm() } + + +class MobileHome(TemplateView): + template_name = 'mobile/home.html' + + def get_context_data(self, **kwargs): + return { + 'next_event': events.get_next_event(), + 'contact_email': settings.CONTACT_EMAIL_TO[0] + } + + +class MobilePastMeetups(TemplateView): + template_name = 'mobile/past_meetups.html' + + def get_context_data(self, **kwargs): + return { + 'past_events': events.get_past_events() + } + + +standard_home_view = StandardHome.as_view() +mobile_home_view = MobileHome.as_view() +mobile_past_view = MobilePastMeetups.as_view() + + +def home(request, *args, **kwargs): + """ + Standard browsers: show home page with next and past meetups + Mobile browsers: show home page with next meetup + + """ + if request.is_mobile: + return mobile_home_view(request, *args, **kwargs) + return standard_home_view(request, *args, **kwargs) + + +def past_meetups(request, *args, **kwargs): + """ + Standard browsers: redirect to home page + Mobile browsers: show list of past meetups with links to meetup.com + + """ + if request.is_mobile: + return mobile_past_view(request, *args, **kwargs) + return redirect(reverse('home'))