Initial commit

This commit is contained in:
Ryan McGrath 2010-10-27 14:53:51 -04:00
commit d5c605e4ad
22 changed files with 1372 additions and 0 deletions

0
__init__.py Normal file
View file

View file

@ -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"}}]

11
manage.py Normal file
View file

@ -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)

BIN
redditors/.models.py.swp Normal file

Binary file not shown.

BIN
redditors/.views.py.swp Normal file

Binary file not shown.

0
redditors/__init__.py Normal file
View file

16
redditors/admin.py Normal file
View file

@ -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)

62
redditors/models.py Normal file
View file

@ -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)

23
redditors/tests.py Normal file
View file

@ -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
"""}

242
redditors/views.py Normal file
View file

@ -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

78
settings.py Normal file
View file

@ -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',
)

291
templates/base.html Normal file
View file

@ -0,0 +1,291 @@
<!DOCTYPE html>
<html>
<head>
<title>Drinkkit - Find other drinking Redditors! {% block title %}{% endblock %}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, maximum-scale=1.0">
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
{% block extra_head %}{% endblock %}
<style type="text/css">
html { width: 100%; margin: 0; padding: 0; }
body {
width: 100%;
color: #555;
font-family:Helvetica, "Helvetica Neue", Arial, sans-serif;
height: 100%;
padding: 0;
margin: 0;
background:#c5ccd3;
-webkit-font-smoothing: antialiased;
}
ul {
list-style-type: none;
padding: 10px 12px;
margin: 0;
}
#title {
width: 100%;
position: relative;
background: #cee3f8;
background: -webkit-gradient(linear, left top, left bottom, from(#cee3f8), to(#a8c4e0));
padding: 7px 0 6px 0;
margin: 0;
font-size: 16px;
font-weight: bold;
color: #333;
border-bottom: 1px solid #7599bd;
text-align: center;
}
#global_options {
width: 100%;
position: relative;
background: #cdcdcd;
background: -webkit-gradient(linear, left top, left bottom, from(#fafafa), to(#cdcdcd));
padding: 4px 0 6px;
font-size: 1.2em;
color: #444;
border-bottom: 1px solid #8f8f8f;
}
#junk_in_trunk, #about {
width: 96%;
margin: 10px auto;
background-color: #fff;
-webkit-border-radius: 4px;
border: 1px solid #8f8f8f;
}
.button {
background-color: #80A2C4;
border: 1px solid #517191;
background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#bfd0e0), to(#80a2c4));
-moz-border-radius: 6px;
-webkit-border-radius: 6px;
text-decoration: none;
color: #fff;
font-size: 11px;
padding: 3px 7px;
}
.large {
font-size: 14px;
padding: 4px 10px;
}
#about_button, #register_button, #login_button { position: absolute; top: 5px; }
#login_button { right: 10px; }
#about_button, #register_button { left: 10px; }
#about { display: none; position: relative; }
#home_link, #nearby_link, #search_link, #find_link, #add_location {
background-color: #80A2C4;
border: 1px solid #517191;
background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#bfd0e0), to(#80a2c4));
-moz-border-radius: 6px;
-webkit-border-radius: 6px;
display: inline-block;
text-decoration: none;
color: #fff;
font-size: 12px;
font-weight: bold;
padding: 3px 7px;
margin-right: 5px;
}
#home_link { margin-left: 10px; }
h2 {
font-size: 18px;
/*color: #ff7041;*/
color: #6c7ca1;
margin: 2px;
padding: 8px;
background-color: #ebf3fc;
background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#fafcfe), to(#ebf3fc));
border: 1px solid #c4dbf1;
}
p {
font-size: 13px;
margin: 10px 0;
padding: 0 10px;
}
label {
display: block;
font-weight: bold;
margin-bottom: 3px;
}
p input, select {
width: 97%;
padding: 5px;
font-size: 16px;
border: 1px solid #b2b2b2;
-webkit-border-radius: 4px;
color: #555;
}
#button_center { text-align: center; }
/* Oh god I'm so tired... */
.go_location {
width: 70px !important;
position: absolute;
top: 0px;
right: 10px;
color: #fff !important;
font-size: 14px !important;
font-weight: bold;
padding: 5px 12px !important;
text-align: center !important;
border: 1px solid #517191 !important;
-webkit-border-bottom-left-radius: 6px !important;
-webkit-border-bottom-right-radius: 6px !important;
}
.top_level {
margin-bottom: 10px;
position: relative;
}
#results li a, #results li a:visited {
border: 1px solid #c9c9c9;
background-color: #f9f9f9;
-webkit-border-radius: 2px;
padding: 10px;
display: block;
color: #555;
text-decoration: none;
padding-bottom: 3px;
margin-bottom: 5px;
width: 92%;
}
.location_name { font-weight: bold; color: #ff8070; }
.top_level ul { padding: 5px 0 0 0; }
.top_level li { margin-bottom: 5px;}
</style>
<script type="text/javascript">
/* Meh, sure, why not */
var isEventSupported = (function(){
var TAGNAMES = {
'select':'input','change':'input',
'submit':'form','reset':'form',
'error':'img','load':'img','abort':'img'
}
function isEventSupported(eventName) {
var el = document.createElement(TAGNAMES[eventName] || 'div');
eventName = 'on' + eventName;
var isSupported = (eventName in el);
if(!isSupported) {
el.setAttribute(eventName, 'return;');
isSupported = typeof el[eventName] == 'function';
}
el = null;
return isSupported;
}
return isEventSupported;
})();
var bareddit = {
toggleAbout: function() {
if(bareddit.about.style.display !== "block") {
this.innerHTML = "Close About";
bareddit.about.style.display = "block";
} else {
this.innerHTML = "About"
bareddit.about.style.display = "none";
}
return false;
}
};
function whenReady() {
/* Fire this when the DOM is "ready", if it exists and we need it. */
if(typeof _GPS !== "undefined") _GPS.getLocation();
bareddit.about = document.getElementById("about");
/* Use the annoying event handler syntax in the hopes of hitting all odd mobile browsers.
touchstart events are faster in the browsers that support them, so fork for it */
if(isEventSupported('touchstart')) {
document.getElementById("about_button").ontouchstart = bareddit.toggleAbout;
} else {
document.getElementById("about_button").onclick = bareddit.toggleAbout;
}
}
</script>
</head>
<body onload="whenReady();">
<div id="title">
{% if not user.is_authenticated %}
<a href="/auth/" id="login_button" class="button">Login</a>
<a href="/register/" id="register_button" class="button">Register</a>
{% else %}
<a href="#" id="about_button" class="button">About</a>
<a href="/unauth/" id="login_button" class="button">Logout</a>
{% endif %}
drinkkit
</div>
<div id="global_options">
<!-- I, as a habit, throw out all notions of "semantic markup" in many mobile situations. In *my* mind, less nodes
on a page means less for the damn phone to process, and more room for me to be awesome.
Your opinion may differ. I don't honestly care at the moment.
- Ryan, 3:45AM
-->
<a href="/" id="home_link" class="button">Home</a>
<a href="/locations/nearby/" id="nearby_link" class="button">Nearby</a>
<a href="/locations/search/" id="search_link" class="button">Search</a>
{% if user.is_authenticated %}
<a href="/locations/add/" id="add_location" class="button">Add Location</a>
{% endif %}
</div>
<div id="about">
<h2>What the hell is drinkkit?</h2>
<p>
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.
</p>
<p>
The difference is that this is limited to <strong>just Redditors</strong>. 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!
</p>
<h2>Disclaimer</h2>
<p>
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.
</p>
<p>
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.
</p>
</div>
<div id="junk_in_trunk">
{% block content %}{% endblock %}
<div>
</body>
</html>

View file

@ -0,0 +1,95 @@
{% extends "base.html" %}
{% block extra_head %}
<style type="text/css">
#add_location_submit {
margin: 10px auto;
padding: 8px 12px !important;
}
</style>
<!--
Load this in case geoLocation isn't supported... we'll drop back to trying to match by IP address. :\
Yes, load this *now*, don't get fancy with some JS-appending solution. Mobile browsers blow, and blow moreso
when there's no GPS to begin with. We'll take the resource hit.
-->
<script type="text/javascript" src="http://www.google.com/jsapi?key=ABQIAAAAaTpZjip8OppqbtRhg6vhDhTp1xR-A-7MLZUGcs3iDfyOaBUtiRTos5hyCGDaGxeW2svzPD8IOHrMPg"></script>
<script type="text/javascript">
var _GPS = {
attempts: 0,
positionSuccess: function(x) {
document.getElementById("lat").value = x.coords.latitude;
document.getElementById("long").value = x.coords.longitude;
}
}
_GPS.getLocation = function() {
++_GPS.attempts;
if(navigator && navigator.geolocation) {
navigator.geolocation.getCurrentPosition(_GPS.positionSuccess, function() { return; }, { enableHighAccuracy:true });
} else if(google.loader.ClientLocation.latitude && google.loader.ClientLocation.longitude) {
/* Little known trick - these get filled when you download jsapi, like we do above. Free geolocation, fairly reliable. ;D */
positionSuccess({
coords: {
latitude: google.loader.ClientLocation.latitude,
longitude: google.loader.ClientLocation.longitude
},
});
} else if(_GPS.attempts < 3) {
setTimeout(_GPS.getLocation, 1000);
} else {
alert("Your device doesn't seem to support GPS! We'll still let you add something, but please be as accurate as possible since we can't get your coordinates.");
}
};
</script>
{% endblock %}
{% block content %}
{% if error %}
<div class="error">
<p>{{ error }}</p>
</div>
{% endif %}
<form method="post" action="/locations/add/">
{% csrf_token %}
<h2>What's the name?</h2>
<p>
<label for="location_name">Ex: Churchkey, Buffalo Billiards, Fili's House</label>
<input type="text" name="location_name" value="">
</p>
<h2>Know the street address?</h2>
<p>
<label for="street_address">Optional, but helps narrow it down if GPS isn't accurate enough.</label>
<input type="text" name="street_address" value="">
</p>
<h2>What type of location is this?</h2>
<p>
<label for="location_type">Optional, but useful for people to know.</label>
<select name="location_type">
<option value="">Choose a category</option>
{% for type in category_choices %}
<option value="{{ type.id }}">{{ type.name }}</option>
{% endfor %}
</select>
</p>
<input type="hidden" name="lat" id="lat" value="">
<input type="hidden" name="long" id="long" value="">
<div id="button_center">
<input type="submit" value="Add This Location" id="add_location_submit" class="button large">
</div>
</form>
{% endblock %}

View file

@ -0,0 +1,32 @@
{% extends "base.html" %}
{% block extra_head %}
{% endblock %}
{% block content %}
<h2>Checkin To: {{ location.name }}</h2>
<p>
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.
</p>
<form method="post" action="/locations/{{ location.id }}/checkin/">
{% csrf_token %}
<h2>How long do you think you'll be here?</h2>
<p>
<label for="estimated_time_here">Ex: 2 hours, until the sun rises</label>
<input type="text" name="estimated_time_here" value="">
</p>
<h2>How can other Redditors identify you?</h2>
<p>
<label for="identify_by">Ex: At the bar, wearing peace sign earings</label>
<input type="text" name="identify_by" value="">
</p>
<div id="button_center" style="margin-bottom: 14px;">
<input type="submit" value="Check In Here!" class="button large">
</div>
</form>
{% endblock %}

View file

@ -0,0 +1,100 @@
{% extends "base.html" %}
{% block extra_head %}
<style type="text/css">
#add_location_submit {
margin: 10px auto;
padding: 8px 12px !important;
}
</style>
<!--
Load this in case geoLocation isn't supported... we'll drop back to trying to match by IP address. :\
Yes, load this *now*, don't get fancy with some JS-appending solution. Mobile browsers blow, and blow moreso
when there's no GPS to begin with. We'll take the resource hit.
-->
<script type="text/javascript" src="http://www.google.com/jsapi?key=ABQIAAAAaTpZjip8OppqbtRhg6vhDhTp1xR-A-7MLZUGcs3iDfyOaBUtiRTos5hyCGDaGxeW2svzPD8IOHrMPg"></script>
<script type="text/javascript">
var _GPS = {
attempts: 0,
positionSuccess: function(x) {
document.getElementById("lat").value = x.coords.latitude;
document.getElementById("long").value = x.coords.longitude;
/*
Try to force-submit the form after we've got our values; if it fails, hide the loading text
so the user is "prompted" to submit it.
*/
try {
document.getElementById("bertform").submit();
} catch(e) {
document.getElementById("bert").style.display = "none";
}
}
}
_GPS.getLocation = function() {
++_GPS.attempts;
if(navigator && navigator.geolocation) {
navigator.geolocation.getCurrentPosition(_GPS.positionSuccess, function() { return; }, { enableHighAccuracy:true });
} else if(google.loader.ClientLocation.latitude && google.loader.ClientLocation.longitude) {
/* Little known trick - these get filled when you download jsapi, like we do above. Free geolocation, fairly reliable. ;D */
positionSuccess({
coords: {
latitude: google.loader.ClientLocation.latitude,
longitude: google.loader.ClientLocation.longitude
},
});
} else if(_GPS.attempts < 3) {
setTimeout(_GPS.getLocation, 1000);
} else {
alert("Your device doesn't seem to support GPS! We'll still let you add something, but please be as accurate as possible since we can't get your coordinates.");
}
};
</script>
{% endblock %}
{% block content %}
<!--
If you complain about this stuff in a mobile environment that's targetting browsers
worse than IE6, you should just stop using the internet now. ;P
-->
<div style="text-align: center;">
{% if error %}
<div class="error">
<p>{{ error }}</p>
</div>
{% endif %}
<form id="bertform" method="post" action="/locations/nearby/">
{% csrf_token %}
<h2>Getting your current position...</h2>
<p>
This should automagically redirect you to nearby results.
</p>
<p>
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 (<em>older phones may need to do this</em>).
</p>
<input type="hidden" name="lat" id="lat" value="">
<input type="hidden" name="long" id="long" value="">
<div id="button_center">
<input type="submit" value="Add This Location" id="add_location_submit" class="button large">
</div>
<div id="bert" style="margin-bottom: 10px;">
Loading...
</div>
</form>
</div>
{% endblock %}

View file

@ -0,0 +1,45 @@
{% extends "base.html" %}
{% block extra_head %}
{% endblock %}
{% block content %}
<h2>Where all the DC Redditors at?</h2>
<p>
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.
</p>
<p>
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 <a href="http://reddit.com/user/ryanmcgrath">Ryan McGrath</a>; if you have questions or comments, feel free
to message him on Reddit.
</p>
<h2>Redditors around DC</h2>
<ul id="results">
{% for checkin in checkins.object_list %}
<li class="top_level">
<a href="/locations/{{ checkin.location.id }}/" class="go_location button">View</a>
<a href="/locations/{{ checkin.location.id }}/">
<span class="location_name">{{ checkin.location.name }}</span>
<ul>
<li><strong>{{ checkin.user.username }}</strong> checked in here {{ checkin.timestamp|timesince }} ago</li>
</ul>
</a>
</li>
{% endfor %}
</ul>
<ul id="pagination">
{% if checkins.has_previous %}
<li><a href="?page={{ results.previous_page_number }}">Previous Results</a></li>
{% endif %}
{% if checkins.has_next %}
<li><a href="?page={{ results.previous_page_number }}">More Results</a></li>
{% endif %}
</ul>
{% endblock %}

View file

@ -0,0 +1,66 @@
{% extends "base.html" %}
{% block extra_head %}
<style type="text/css">
#search_btn {
margin: 10px auto;
padding: 8px 12px !important;
}
</style>
{% endblock %}
{% block content %}
<form method="post" action="/locations/search/">
{% csrf_token %}
<h2>Find Locations in DC</h2>
<p>
<label for="location_name">Ex: Churchkey, Buffalo Billiards, Rocket Bar</label>
<input type="text" name="search_query" value="{{ search_query|default:"" }}">
</p>
<div id="button_center">
<input type="submit" value="Find Locations" id="search_btn" class="button large">
</div>
</form>
{% if performed_search %}
{% if results.object_list %}
<h2>Results for {{ search_query }}</h2>
<ul id="results">
{% for location in results.object_list %}
<li class="top_level">
<a href="/locations/{{ checkin.location.id }}/" class="go_location button">View</a>
<a href="/locations/{{ location.id }}/">
<span class="location_name">{{ location.name }}</span>
<ul>
<li><strong>Recent Checkins:</strong> {{ location.get_recent_checkins_count }}</li>
<li><strong>Address:</strong> {{ location.street_address|default:"No address set" }}</li>
<li><strong>Category:</strong> {{ location.category|default:"No category set" }}</li>
</ul>
</a>
</li>
{% endfor %}
</ul>
<ul id="pagination">
{% if results.has_previous %}
<li><a href="?page={{ results.previous_page_number }}">Previous Results</a></li>
{% endif %}
{% if results.has_next %}
<li><a href="?page={{ results.previous_page_number }}">More Results</a></li>
{% endif %}
</ul>
{% else %}
<h2>No Results for {{ search_query }}</h2>
<p>
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!
</p>
{% endif %}
{% endif %}
{% endblock %}

View file

@ -0,0 +1,44 @@
{% extends "base.html" %}
{% block extra_head %}
<style type="text/css">
#search_btn {
margin: 10px auto;
padding: 8px 12px !important;
}
</style>
{% endblock %}
{% block content %}
{% if nearby %}
<h2>Nearby you right now</h2>
<ul id="results">
{% for location in nearby %}
<li class="top_level">
<a href="/locations/{{ checkin.location.id }}/" class="go_location button">View</a>
<a href="/locations/{{ location.id }}/">
<span class="location_name">{{ location.name }}</span>
<ul>
<li><strong>Recent Checkins:</strong> {{ location.get_recent_checkins_count }}</li>
<li><strong>Address:</strong> {{ location.street_address|default:"No address set" }}</li>
<li><strong>Category:</strong> {{ location.category|default:"No category set" }}</li>
</ul>
</a>
</li>
{% endfor %}
</ul>
{% else %}
<h2>All's quiet on the Eastern Front :(</h2>
<p>
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.
</p>
<p>
The third option, of course, is that it's very possible nobody's out and about. They are Redditors, after all. ;)
</p>
{% endif %}
{% endblock %}

View file

@ -0,0 +1,197 @@
{% extends "base.html" %}
{% block extra_head %}
<style type="text/css">
#map {
margin: 10px auto;
width: 98%;
height: 300px;
}
#bert { text-align: center; }
#tip_body {
display: block;
padding: 3px;
width: 95%;
height: 100px;
border-color: #b2b2b2;
margin: 10px auto;
-webkit-border-radius: 2px;
}
#submit_tip { margin-bottom: 10px; }
#tips li, #checkins li {
margin-bottom: 15px;
font-size: 13px;
}
.comment_header {
display: block;
border-bottom: 1px dotted #555;
padding-bottom: 2px;
margin-bottom: 2px;
}
#tips strong { font-size: 14px; }
#checkins span strong { font-size: 14px; color: #ff8070;}
#location_details { position: relative; }
.pointers { font-weight: bold; color: #666; }
#checkin_here_link, #checked_in_already {
position: absolute;
top: 10px;
right: 10px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
text-decoration: none;
color: #fff;
font-weight: bold;
}
#checkin_here_link {
background-color: #ff4500;
border: 1px solid #ff4500;
background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#ff9f7b), to(#ff4500));
font-size: 20px;
padding: 7px 12px;
}
#checked_in_already {
background-color: #cdcdcd;
border: 1px solid #8f8f8f;
background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#f2f2f2), to(#cdcdcd));
font-size: 14px;
padding: 7px 10px;
color: #888;
}
#annotations { padding-bottom: 0px !important; }
#annotations li {
margin-bottom: 10px;
font-size: 12px;
}
#annoations strong { display: block; }
</style>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=true"></script>
<script type="text/javascript">
var geocoder, map;
function generateMap() {
geocoder = new google.maps.Geocoder();
var latlng = new google.maps.LatLng({{ location.geometry.x }}, {{ location.geometry.y }});
var opts = {
zoom: 16,
center: latlng,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
document.getElementById("bad_browser_map").style.display = "none";
map = new google.maps.Map(document.getElementById("map"), opts);
{% if location.street_address %}
/*
Since we happen to have an address stored, we'll geocode it instead of using the stored/reported
GPS coordinates, as that'll be more accurate overall. We can still use the GPS coordinates to center
the map ahead of time, though, which we do above (see: opts/center).
*/
geocoder.geocode({'address': "{{ location.address_for_geocode}},Washington,DC"}, function(results, status) {
if(status == google.maps.GeocoderStatus.OK) {
map.setCenter(results[0].geometry.location);
var marker = new google.maps.Marker({
map: map,
position: results[0].geometry.location
});
} else {
alert("Geocode was not successful for the following reason: " + status);
}
});
{% else %}
var marker = new google.maps.Marker({
position: latlng,
map: map,
title: '{{ location.name }}'
});
{% endif %}
}
</script>
{% endblock %}
{% block content %}
<h2>{{ location.name }}</h2>
<div id="location_details">
<ul id="annotations">
<li><strong>Category:</strong> {{ location.category|default:"Not Specified :(" }}</li>
<li><strong>Address:</strong> {{ location.street_address|default:"Not Specified :(" }}</li>
</ul>
{% if user.is_authenticated %}
{% if allow_checkin %}
<a href="/locations/{{ location.id }}/checkin/" id="checkin_here_link">Check In Here</a>
{% else %}
<div id="checked_in_already">Already Checked In</div>
{% endif %}
{% endif %}
</div>
<h2>Map of the area</h2>
<div id="map">
{% if location.street_address %}
<img id="bad_browser_map" src="http://maps.google.com/maps/api/staticmap?center={{ location.address_for_geocode}},Washington,DC&sensor=true&zoom=16&size=300x300">
{% else %}
<img id="bad_browser_map" src="http://maps.google.com/maps/api/staticmap?center={{ location.geometry.x }},{{ location.geometry.y }}&sensor=true&zoom=16&size=300x300">
{% endif %}
</div>
<script type="text/javascript">
try { generateMap(); } catch(e) { /* Bad browser, most likely. Oh well, they still got a static map. ;P */ }
</script>
<h2>Recently checked in here</h2>
{% if recent_checkins %}
<ul id="checkins">
{% for checkin in recent_checkins %}
<li>
<span class="comment_header"><strong>{{ checkin.user.username }}</strong> <em>{{ checkin.timestamp|timesince }} ago</em></span>
<span class="pointers">Can be identified by:</span> {{ checkin.identify_by|default:"Doesn't want to be found. :(" }}<br>
<span class="pointers">Estimated time spent here:</span> {{ checkin.estimated_time_here|default:"They're flying by the seat of their vintage pants (no idea)."}}
</li>
{% endfor %}
</ul>
{% else %}
<p class="none_yet">No checkins yet. You could be the first!</p>
{% endif %}
{% if user.is_authenticated %}
<h2>Leave a Tip or Todo</h2>
<form action="/locations/{{ location.id }}/add_tip/" method="post" id="bert">
{% csrf_token %}
<textarea name="tip_body" id="tip_body"></textarea>
<input type="submit" value="Post Tip" id="submit_tip" class="button large">
</form>
{% endif %}
<h2>Tips &amp; Todos from Redditors</h2>
{% if location.tip_set.all %}
<ul id="tips">
{% for tip in location.tip_set.all %}
<li>
<span class="comment_header"><strong>{{ tip.user.username }}</strong> <em>{{ tip.timestamp|timesince }} ago</em></span>
{{ tip.tip }}
</li>
{% endfor %}
</ul>
{% else %}
<p class="none_yet">No tips yet. Be the first and add one!</p>
{% endif %}
{% endblock %}

View file

@ -0,0 +1,28 @@
{% extends "base.html" %}
{% block content %}
{% if form.errors %}
<p>Your username and password didn't match. Please try again.</p>
{% endif %}
<form method="post" action="{% url django.contrib.auth.views.login %}">
{% csrf_token %}
<h2>Your Username</h2>
<p>
{{ form.username }}
</p>
<h2>Your Password</h2>
<p>
{{ form.password }}
</p>
<div id="button_center">
<input type="submit" value="Login to Drinkkit" class="button large" style="margin-bottom: 10px;">
<input type="hidden" name="next" value="{{ next }}" />
</div>
</form>
{% endblock %}

View file

@ -0,0 +1,14 @@
{% extends "base.html" %}
{% block content %}
<h2>Create an account</h2>
<form action="/register/" method="post">
{% csrf_token %}
{{ form.as_p }}
<div id="button_center">
<input type="submit" value="Create Your Drinkkit Account" class="button large" style="margin-bottom: 10px;">
</div>
</form>
{% endblock %}

27
urls.py Normal file
View file

@ -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<location_id>[a-zA-Z0-9_.-]+)/add_tip/$', 'drinkkit.redditors.views.add_tip'),
(r'^locations/(?P<location_id>[a-zA-Z0-9_.-]+)/checkin/$', 'drinkkit.redditors.views.checkin_location'),
(r'^locations/(?P<location_id>[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'),
)