commit d5c605e4ade39eaa67f8633a3eb5646bfb229f6d Author: Ryan McGrath Date: Wed Oct 27 14:53:51 2010 -0400 Initial commit diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fixtures/categories_default.json b/fixtures/categories_default.json new file mode 100644 index 0000000..d0c7c37 --- /dev/null +++ b/fixtures/categories_default.json @@ -0,0 +1 @@ +[{"pk": 1, "model": "redditors.locationcategory", "fields": {"name": "Dive Bar"}}, {"pk": 2, "model": "redditors.locationcategory", "fields": {"name": "Lounge"}}, {"pk": 3, "model": "redditors.locationcategory", "fields": {"name": "Restaurant"}}, {"pk": 4, "model": "redditors.locationcategory", "fields": {"name": "Drunk Food"}}, {"pk": 5, "model": "redditors.locationcategory", "fields": {"name": "Open Area"}}, {"pk": 6, "model": "redditors.locationcategory", "fields": {"name": "House"}}, {"pk": 7, "model": "redditors.locationcategory", "fields": {"name": "Store"}}] \ No newline at end of file diff --git a/manage.py b/manage.py new file mode 100644 index 0000000..5e78ea9 --- /dev/null +++ b/manage.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python +from django.core.management import execute_manager +try: + import settings # Assumed to be in the same directory. +except ImportError: + import sys + 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__) + sys.exit(1) + +if __name__ == "__main__": + execute_manager(settings) diff --git a/redditors/.models.py.swp b/redditors/.models.py.swp new file mode 100644 index 0000000..0500c39 Binary files /dev/null and b/redditors/.models.py.swp differ diff --git a/redditors/.views.py.swp b/redditors/.views.py.swp new file mode 100644 index 0000000..3dde176 Binary files /dev/null and b/redditors/.views.py.swp differ diff --git a/redditors/__init__.py b/redditors/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/redditors/admin.py b/redditors/admin.py new file mode 100644 index 0000000..8b2f174 --- /dev/null +++ b/redditors/admin.py @@ -0,0 +1,16 @@ +from django.contrib import admin +from drinkkit.redditors.models import LocationCategory, Location, Checkin, Tip + +class CheckinInline(admin.StackedInline): + model = Checkin + extra = 1 + +class TipInline(admin.StackedInline): + model = Tip + extra = 1 + +class LocationAdmin(admin.ModelAdmin): + inlines = [CheckinInline, TipInline] + +admin.site.register(Location, LocationAdmin) +admin.site.register(LocationCategory) diff --git a/redditors/models.py b/redditors/models.py new file mode 100644 index 0000000..8f519d4 --- /dev/null +++ b/redditors/models.py @@ -0,0 +1,62 @@ +from datetime import datetime, timedelta +from django.contrib.gis.db import models +from django.contrib.auth.models import User + +class LocationCategory(models.Model): + """ + Table/class to hold categories for locations to fall under. Let this + be fairly agnostic of everything. + """ + name = models.CharField(max_length = 200) + + def __str__(self): + return "%s" % str(self.name) + +class Location(models.Model): + """ + Locations, yo. Street Address is optional, don't make the user + have to fuck with something they don't know offhand unless they care. + + Category is self explanatory, geometry is a GeoDjango model type to handle + storing lat/long coordinates for distance equations. + """ + name = models.CharField(max_length=200) + street_address = models.CharField(max_length=200, blank=True) + category = models.ForeignKey(LocationCategory, blank=True, null=True) + geometry = models.PointField(srid=4326) + objects = models.GeoManager() + + def __str__(self): + return "%s" % str(self.name) + + def get_recent_checkins_count(self): + """ + Tally up how many checkins this location had in the past day. + """ + print self.checkin_set.filter(timestamp__gte = datetime.now() + timedelta(days=-1)) + return self.checkin_set.filter(timestamp__gte = datetime.now() + timedelta(days=-1)).count() + + def address_for_geocode(self): + return self.street_address.replace(" ", "+") + +class Checkin(models.Model): + """ + Checkin model - pretty self explanatory all around. + """ + user = models.ForeignKey(User) + location = models.ForeignKey(Location) + timestamp = models.DateTimeField(auto_now=True) + estimated_time_here = models.CharField(max_length=200, blank=True, null=True) + identify_by = models.CharField(max_length=200, blank=True, null=True) + +class Tip(models.Model): + """ + Tips - again, fairly self explanatory. + """ + tip = models.TextField(blank=False) + user = models.ForeignKey(User) + timestamp = models.DateTimeField(auto_now=True) + location = models.ForeignKey(Location) + + def __str__(self): + return "%s" % str(self.title) diff --git a/redditors/tests.py b/redditors/tests.py new file mode 100644 index 0000000..2247054 --- /dev/null +++ b/redditors/tests.py @@ -0,0 +1,23 @@ +""" +This file demonstrates two different styles of tests (one doctest and one +unittest). These will both pass when you run "manage.py test". + +Replace these with more appropriate tests for your application. +""" + +from django.test import TestCase + +class SimpleTest(TestCase): + def test_basic_addition(self): + """ + Tests that 1 + 1 always equals 2. + """ + self.failUnlessEqual(1 + 1, 2) + +__test__ = {"doctest": """ +Another way to test that 1 + 1 is equal to 2. + +>>> 1 + 1 == 2 +True +"""} + diff --git a/redditors/views.py b/redditors/views.py new file mode 100644 index 0000000..a84fbe9 --- /dev/null +++ b/redditors/views.py @@ -0,0 +1,242 @@ +from datetime import datetime, timedelta + +from django.contrib.gis.geos import * +from django.contrib.gis.measure import D + +from django import forms +from django.contrib.auth.forms import UserCreationForm +from django.http import HttpResponse, HttpResponseRedirect +from django.shortcuts import render_to_response +from django.utils import simplejson +from django.contrib.auth.models import User +from django.template import RequestContext, Context +from django.contrib.auth.decorators import login_required +from django.core.paginator import Paginator, InvalidPage, EmptyPage + +from drinkkit.redditors.models import LocationCategory, Location, Tip, Checkin + +def home(request): + """ + Main page shows the checkins that have occured over the past day or so. Paginate it + so we don't completely crush things. + """ + queryset = Checkin.objects.filter(timestamp__gte = datetime.now() + timedelta(days=-1)) + paginator = Paginator(queryset, 15) + + try: + page = int(request.GET.get('page', '1')) + except ValueError: + page = 1 + + try: + checkins = paginator.page(page) + except (EmptyPage, InvalidPage): + checkins = paginator.page(paginator.num_pages) + + return render_to_response('locations/home.html', + context_instance = RequestContext(request, {'checkins': checkins}) + ) + +def register(request): + """ + Registration junk for a new user. + """ + if request.POST: + form = UserCreationForm(request.POST) + if form.is_valid(): + # Create User and junk + new_user = form.save() + return HttpResponseRedirect('/') + else: + # You done goofed + return render_to_response('registration/register.html', + context_instance = RequestContext(request, {'form': form}) + ) + else: + form = UserCreationForm() + return render_to_response('registration/register.html', + context_instance = RequestContext(request, {'form': form}) + ) + +def nearby_locations(request): + """ + This is a bit of an interesting method. To deal with older phones, + we employ a somewhat odd trick here. + + We first direct the phone to a page that grabs their coordinates (whether + by geolocation API, or IP location). We do this for phones where we can go ahead and + get the IP location, but they might not support AJAX requests or some shit like that. + + The page then submits automatically with JS after it gets the coordinates, with a button + for the user to hit if the browser is also gonna hang on stupid shit like that. + """ + if request.POST: + if not request.POST['lat'] or not request.POST['long']: + return render_to_response('locations/get_coords_nearby.html', + context_instance = RequestContext(request, { + 'error': "Seems we're unable to get ahold of the GPS/location for where you are. Try again in a few minutes!" + }) + ) + + searchpnts = fromstr('POINT(%s %s)' % (request.POST['lat'], request.POST['long']), srid=4326) + nearby = Location.objects.filter(geometry__distance_lte=(searchpnts, D(mi=2))) + return render_to_response('locations/show_nearby.html', + context_instance = RequestContext(request, {'nearby': nearby}) + ) + else: + return render_to_response('locations/get_coords_nearby.html', + context_instance = RequestContext(request) + ) + +def find_locations(request): + """ + A weak and very basic search. Should be rewritten down the road if this goes anywhere. + """ + if request.POST: + queryset = Location.objects.filter(name__icontains = request.POST['search_query']) + paginator = Paginator(queryset, 10) + performed_search = False + + try: + page = int(request.GET.get('page', '1')) + except ValueError: + page = 1 + + try: + results = paginator.page(page) + performed_search = True + except (EmptyPage, InvalidPage): + results = paginator.page(paginator.num_pages) + + return render_to_response('locations/search.html', + context_instance = RequestContext(request, { + 'search_query': request.POST['search_query'], + 'results': results, + 'performed_search': performed_search # sucks, but whatever for now + }) + ) + else: + return render_to_response('locations/search.html', + context_instance = RequestContext(request) + ) + +@login_required +def checkin_location(request, location_id): + """ + Check a user into a location. + """ + try: + location = Location.objects.get(id = location_id) + except Location.DoesNotExist: + return HttpResponse(status=400) + + if request.POST: + new_checkin = Checkin() + new_checkin.user = request.user + new_checkin.location = location + new_checkin.estimated_time_here = request.POST['estimated_time_here'] + new_checkin.identify_by = request.POST['identify_by'] + new_checkin.save() + return HttpResponseRedirect('/locations/%s/' % location_id) + else: + return render_to_response('locations/checkin.html', + context_instance = RequestContext(request, {'location': location}) + ) + +@login_required +def add_location(request): + """ + Add a new location to be checked into by others. + + Fairly custom logic, kind of ugly, 5:30AM, I don't care right now. None of these form fields are bound, + but considering that only two of them are mandatory, I'm fine with this for a first release. It should be + fixed at some point. ;P + """ + if request.POST: + if request.POST['location_name'] and request.POST['lat'] and request.POST['long']: + new_location = Location() + new_location.name = request.POST['location_name'] + new_location.geometry = 'POINT(%s %s)' %(request.POST['lat'], request.POST['long']) + + # If they've supplied a street address, sweet, use it. + if request.POST['street_address']: + new_location.street_address = request.POST['street_address'] + + # If they set a location, let's record it... (5AM code) + if request.POST['location_type']: + try: + category = LocationCategory.objects.get(id = request.POST['location_type']) + new_location.category = category + except LocationCategory.DoesNotExist: + pass + + new_location.save() + return HttpResponseRedirect('/locations/%s/' % str(new_location.id)) + else: + if not request.POST['lat'] or not request.POST['lat']: + errmsg = "We weren't able to get coordinates for where you are right now. Does your phone or device have GPS?" + if not request.POST['location_name']: + errmsg = "You didn't even bother to enter a name for this location. Wtf?" + return render_to_response('locations/add.html', + context_instance = RequestContext(request, {'error': errmsg, 'category_choices': LocationCategory.objects.all()}) + ) + else: + return render_to_response('locations/add.html', + context_instance = RequestContext(request, {'category_choices': LocationCategory.objects.all()}) + ) + +def view_location(request, location_id): + """ + Serve up information about a location. + + Note: this could probably be more efficient. + """ + try: + location = Location.objects.get(id = location_id) + + checkins = location.checkin_set + recent_checkins = checkins.all().reverse()[:5] + + allow_checkin = True + + # Only one checkin in a day long period. + if request.user.is_authenticated(): + if checkins.filter(user = request.user).filter(timestamp__gte = datetime.now() + timedelta(days=-1)): + allow_checkin = False + + return render_to_response('locations/view.html', + context_instance = RequestContext(request, { + 'location': location, + 'recent_checkins': recent_checkins, + 'allow_checkin': allow_checkin + }) + ) + except Location.DoesNotExist: + return HttpResponse(status=404) + +@login_required +def add_tip(request, location_id): + """ + Add a new tip about a location. + """ + if request.POST: + try: + location = Location.objects.get(id = location_id) + + new_tip = Tip() + new_tip.tip = request.POST['tip_body'] + new_tip.user = request.user + new_tip.location = location + new_tip.save() + + return HttpResponseRedirect('/locations/%s/' % location.id) + except Location.DoesNotExist: + return HttpResponse(status=404) + else: + return HttpResponse(status=404) + +def find_redditors(request): + """ + Handles locating all Redditors in a given area who recently checked in. + """ + pass diff --git a/settings.py b/settings.py new file mode 100644 index 0000000..d5e3139 --- /dev/null +++ b/settings.py @@ -0,0 +1,78 @@ +import os.path +# Django settings for drinkkit project. + +DEBUG = True +TEMPLATE_DEBUG = DEBUG + +ADMINS = ( + ('You', 'you@you.com'), +) + +FIXTURE_DIRS = ( + 'fixtures', +) + +if DEBUG: + CACHE_BACKEND = 'dummy:///' +else: + CACHE_BACKEND = 'memcached://127.0.0.1:11211/' + +AUTH_PROFILE_MODULE = 'redditors.Redditor' +LOGIN_REDIRECT_URL = '/' +LOGIN_URL = '/auth/' +LOGOUT_URL = '/' + +MANAGERS = ADMINS + +DATABASES = { + 'default': { + 'ENGINE': 'django.contrib.gis.db.backends.postgis', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'. + 'NAME': '', # Or path to database file if using sqlite3. + 'USER': '', # Not used with sqlite3. + 'PASSWORD': '', # Not used with sqlite3. + 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. + 'PORT': '', # Set to empty string for default. Not used with sqlite3. + } +} + +TIME_ZONE = 'America/New_York' +LANGUAGE_CODE = 'en-us' +SITE_ID = 1 +USE_I18N = True +USE_L10N = True +MEDIA_ROOT = '' +MEDIA_URL = '' +ADMIN_MEDIA_PREFIX = '/media/' +SECRET_KEY = '-2_vd&w^n^3l2+jg3bxrqv8$7w4%lolnok(hf_dl5-()a=3xl5' + +TEMPLATE_LOADERS = ( + 'django.template.loaders.filesystem.Loader', + 'django.template.loaders.app_directories.Loader', +) + +MIDDLEWARE_CLASSES = ( + 'django.middleware.cache.UpdateCacheMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + 'django.middleware.cache.FetchFromCacheMiddleware', +) + +ROOT_URLCONF = 'drinkkit.urls' + +TEMPLATE_DIRS = ( + os.path.join(os.path.dirname(__file__), 'templates'), +) + +INSTALLED_APPS = ( + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'django.contrib.messages', + 'django.contrib.gis', + 'django.contrib.admin', + 'redditors', +) diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..5f36405 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,291 @@ + + + + Drinkkit - Find other drinking Redditors! {% block title %}{% endblock %} + + + + + {% block extra_head %}{% endblock %} + + + + + +
+ {% if not user.is_authenticated %} + Login + Register + {% else %} + About + Logout + {% endif %} + drinkkit +
+
+ + Home + Nearby + Search + {% if user.is_authenticated %} + Add Location + {% endif %} +
+ +
+

What the hell is drinkkit?

+

+ On October 30th, 2010, two figures are planning to hold some rallies in Washington DC. Much of this was spurred on by Reddit.com. + Washington DC has its own awesome sauce group of Redditors, and we happen to know all the appropriately awesome bars. This is an app + for smartphones that lets Reddit users "check in" to bars and places in DC during the rally, much like Foursquare or Gowalla. +

+ +

+ The difference is that this is limited to just Redditors. If you check out venues here, you're guaranteed to find a + Redditor hanging out there if you drop by. Redditors can also leave tips on venues for other Redditors - hold a scavenger hunt, + awkward conversations and marriage proposals, whatever. To get started, just log in or register! +

+ +

Disclaimer

+

+ This is meant to be for fun, but you should always exercise caution when meeting people from the internet. In case you haven't noticed, + they're sometimes... odd. By using this app, you waive all rights to come back at me if you lack the common sense to be aware of your + surroundings and not take candy from strangers. +

+ +

+ This app is in no way affiliated with Reddit.com, Conde Nast, Wired, or any of the DC locations listed on here. We just like beer. +

+
+ +
+ {% block content %}{% endblock %} +
+ + diff --git a/templates/locations/add.html b/templates/locations/add.html new file mode 100644 index 0000000..7f0c95f --- /dev/null +++ b/templates/locations/add.html @@ -0,0 +1,95 @@ +{% extends "base.html" %} + +{% block extra_head %} + + + + + + +{% endblock %} + + +{% block content %} + {% if error %} +
+

{{ error }}

+
+ {% endif %} + +
+ {% csrf_token %} + +

What's the name?

+

+ + +

+ +

Know the street address?

+

+ + +

+ +

What type of location is this?

+ +

+ + +

+ + + + +
+ +
+
+ +{% endblock %} diff --git a/templates/locations/checkin.html b/templates/locations/checkin.html new file mode 100644 index 0000000..f1c532e --- /dev/null +++ b/templates/locations/checkin.html @@ -0,0 +1,32 @@ +{% extends "base.html" %} + +{% block extra_head %} +{% endblock %} + +{% block content %} +

Checkin To: {{ location.name }}

+

+ The following two questions are optional, but helpful + if you want to be found by other Redditors. If you don't want to answer them, just hit the checkin button below. +

+ +
+ {% csrf_token %} + +

How long do you think you'll be here?

+

+ + +

+ +

How can other Redditors identify you?

+

+ + +

+ +
+ +
+
+{% endblock %} diff --git a/templates/locations/get_coords_nearby.html b/templates/locations/get_coords_nearby.html new file mode 100644 index 0000000..aa74fc6 --- /dev/null +++ b/templates/locations/get_coords_nearby.html @@ -0,0 +1,100 @@ +{% extends "base.html" %} + +{% block extra_head %} + + + + + + +{% endblock %} + + +{% block content %} + +
+ {% if error %} +
+

{{ error }}

+
+ {% endif %} + +
+ {% csrf_token %} + +

Getting your current position...

+

+ This should automagically redirect you to nearby results. +

+ +

+ If it doesn't, please wait until the "loading..." text below disappears, + and then click the button below to force your browser to get results (older phones may need to do this). +

+ + + + +
+ +
+ +
+ Loading... +
+
+
+{% endblock %} diff --git a/templates/locations/home.html b/templates/locations/home.html new file mode 100644 index 0000000..1b0470f --- /dev/null +++ b/templates/locations/home.html @@ -0,0 +1,45 @@ +{% extends "base.html" %} + +{% block extra_head %} +{% endblock %} + + +{% block content %} +

Where all the DC Redditors at?

+

+ Check out where other DC Redditors are drinking! If you'd like to join in, register or log in using the buttons + at the top right of the screen. +

+ +

+ By using this, you should be fully aware that other Redditors can find out where you're at. Consider this a little "social experiment". + This was built out of boredom by Ryan McGrath; if you have questions or comments, feel free + to message him on Reddit. +

+ +

Redditors around DC

+ + + + +{% endblock %} diff --git a/templates/locations/search.html b/templates/locations/search.html new file mode 100644 index 0000000..8e692f0 --- /dev/null +++ b/templates/locations/search.html @@ -0,0 +1,66 @@ +{% extends "base.html" %} + +{% block extra_head %} + +{% endblock %} + + +{% block content %} +
+ {% csrf_token %} + +

Find Locations in DC

+

+ + +

+ +
+ +
+
+ + {% if performed_search %} + {% if results.object_list %} +

Results for {{ search_query }}

+ + + + + {% else %} +

No Results for {{ search_query }}

+

+ No locations in the Drinkkit database match what you're looking for. If where you are isn't listed, + you should feel free to add it so other Redditors can find it! +

+ {% endif %} + {% endif %} + +{% endblock %} diff --git a/templates/locations/show_nearby.html b/templates/locations/show_nearby.html new file mode 100644 index 0000000..cacfe65 --- /dev/null +++ b/templates/locations/show_nearby.html @@ -0,0 +1,44 @@ +{% extends "base.html" %} + +{% block extra_head %} + +{% endblock %} + + +{% block content %} + {% if nearby %} +

Nearby you right now

+ + {% else %} +

All's quiet on the Eastern Front :(

+

+ We couldn't find anything going on near you. The GPS in your device might not be accurate enough for this feature, or we + may be experiencing some difficulty. If you think it's the former (you know your device better than I do (that's what she said)), + please feel free to retry this feature in a few minutes. +

+ +

+ The third option, of course, is that it's very possible nobody's out and about. They are Redditors, after all. ;) +

+ {% endif %} + +{% endblock %} diff --git a/templates/locations/view.html b/templates/locations/view.html new file mode 100644 index 0000000..dfcffa6 --- /dev/null +++ b/templates/locations/view.html @@ -0,0 +1,197 @@ +{% extends "base.html" %} + +{% block extra_head %} + + + + + +{% endblock %} + +{% block content %} +

{{ location.name }}

+ +
+
    +
  • Category: {{ location.category|default:"Not Specified :(" }}
  • +
  • Address: {{ location.street_address|default:"Not Specified :(" }}
  • +
+ + {% if user.is_authenticated %} + {% if allow_checkin %} + Check In Here + {% else %} +
Already Checked In
+ {% endif %} + {% endif %} +
+ +

Map of the area

+
+ {% if location.street_address %} + + {% else %} + + {% endif %} +
+ + + +

Recently checked in here

+ {% if recent_checkins %} +
    + {% for checkin in recent_checkins %} +
  • + {{ checkin.user.username }} {{ checkin.timestamp|timesince }} ago + Can be identified by: {{ checkin.identify_by|default:"Doesn't want to be found. :(" }}
    + Estimated time spent here: {{ checkin.estimated_time_here|default:"They're flying by the seat of their vintage pants (no idea)."}} +
  • + {% endfor %} +
+ {% else %} +

No checkins yet. You could be the first!

+ {% endif %} + + {% if user.is_authenticated %} +

Leave a Tip or Todo

+
+ {% csrf_token %} + + + +
+ {% endif %} + +

Tips & Todos from Redditors

+ {% if location.tip_set.all %} +
    + {% for tip in location.tip_set.all %} +
  • + {{ tip.user.username }} {{ tip.timestamp|timesince }} ago + {{ tip.tip }} +
  • + {% endfor %} +
+ {% else %} +

No tips yet. Be the first and add one!

+ {% endif %} +{% endblock %} diff --git a/templates/registration/login.html b/templates/registration/login.html new file mode 100644 index 0000000..25c9b84 --- /dev/null +++ b/templates/registration/login.html @@ -0,0 +1,28 @@ +{% extends "base.html" %} + +{% block content %} + +{% if form.errors %} +

Your username and password didn't match. Please try again.

+{% endif %} + +
+ {% csrf_token %} + +

Your Username

+

+ {{ form.username }} +

+ +

Your Password

+

+ {{ form.password }} +

+ +
+ + +
+
+ +{% endblock %} diff --git a/templates/registration/register.html b/templates/registration/register.html new file mode 100644 index 0000000..a14b513 --- /dev/null +++ b/templates/registration/register.html @@ -0,0 +1,14 @@ +{% extends "base.html" %} + +{% block content %} +

Create an account

+ +
+ {% csrf_token %} + {{ form.as_p }} + +
+ +
+
+{% endblock %} diff --git a/urls.py b/urls.py new file mode 100644 index 0000000..947a397 --- /dev/null +++ b/urls.py @@ -0,0 +1,27 @@ +from django.conf.urls.defaults import * +from django.contrib import admin + +# Set up the admin shit +admin.autodiscover() + +urlpatterns = patterns('', + (r'^admin/', include(admin.site.urls)), + + # Viewing and adding tips/locations + (r'^locations/add/$', 'drinkkit.redditors.views.add_location'), + (r'^locations/search/$', 'drinkkit.redditors.views.find_locations'), + (r'^locations/nearby/$', 'drinkkit.redditors.views.nearby_locations'), + + (r'^locations/(?P[a-zA-Z0-9_.-]+)/add_tip/$', 'drinkkit.redditors.views.add_tip'), + (r'^locations/(?P[a-zA-Z0-9_.-]+)/checkin/$', 'drinkkit.redditors.views.checkin_location'), + (r'^locations/(?P[a-zA-Z0-9_.-]+)/$', 'drinkkit.redditors.views.view_location'), + + # Registration + (r'^register/$', 'drinkkit.redditors.views.register'), + + # User forms - password, logout, login, etc. + (r'^password_reset/$', 'django.contrib.auth.views.password_reset'), + (r'^unauth/$', 'django.contrib.auth.views.logout_then_login'), + (r'^auth/$', 'django.contrib.auth.views.login'), + (r'^/*', 'drinkkit.redditors.views.home'), +)