Initial commit
This commit is contained in:
commit
d5c605e4ad
22 changed files with 1372 additions and 0 deletions
0
__init__.py
Normal file
0
__init__.py
Normal file
1
fixtures/categories_default.json
Normal file
1
fixtures/categories_default.json
Normal 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
11
manage.py
Normal 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
BIN
redditors/.models.py.swp
Normal file
Binary file not shown.
BIN
redditors/.views.py.swp
Normal file
BIN
redditors/.views.py.swp
Normal file
Binary file not shown.
0
redditors/__init__.py
Normal file
0
redditors/__init__.py
Normal file
16
redditors/admin.py
Normal file
16
redditors/admin.py
Normal 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
62
redditors/models.py
Normal 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
23
redditors/tests.py
Normal 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
242
redditors/views.py
Normal 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
78
settings.py
Normal 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
291
templates/base.html
Normal 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>
|
||||
95
templates/locations/add.html
Normal file
95
templates/locations/add.html
Normal 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 %}
|
||||
32
templates/locations/checkin.html
Normal file
32
templates/locations/checkin.html
Normal 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 %}
|
||||
100
templates/locations/get_coords_nearby.html
Normal file
100
templates/locations/get_coords_nearby.html
Normal 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 %}
|
||||
45
templates/locations/home.html
Normal file
45
templates/locations/home.html
Normal 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 %}
|
||||
66
templates/locations/search.html
Normal file
66
templates/locations/search.html
Normal 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 %}
|
||||
44
templates/locations/show_nearby.html
Normal file
44
templates/locations/show_nearby.html
Normal 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 %}
|
||||
197
templates/locations/view.html
Normal file
197
templates/locations/view.html
Normal 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 & 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 %}
|
||||
28
templates/registration/login.html
Normal file
28
templates/registration/login.html
Normal 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 %}
|
||||
14
templates/registration/register.html
Normal file
14
templates/registration/register.html
Normal 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
27
urls.py
Normal 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'),
|
||||
)
|
||||
Reference in a new issue