From d503e3fe08875171927921cf530697db35d70b09 Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Fri, 29 Oct 2010 05:10:41 -0400 Subject: [PATCH] A plethora of fixes to make this more usable on non-mobile devices; should gracefully degrade a bit better now... if it can't get any GPS coordinates, the user can enter an address and bypass that requirement, and we'll do a reverse lookup and get some GPS coordinates based on said address. Users can also now view the checkin history of other people - someone wanna implement friendships/privacy? We have an open source 4Square here. ;D --- redditors/models.py | 3 +- redditors/views.py | 95 ++++++++++---- templates/base.html | 143 +++++++++++++-------- templates/locations/add.html | 2 +- templates/locations/checkin.html | 2 +- templates/locations/get_coords_nearby.html | 2 +- templates/locations/home.html | 17 +-- templates/locations/search.html | 19 ++- templates/locations/view.html | 29 +++-- templates/redditors/view_profile.html | 22 ++++ urls.py | 5 +- 11 files changed, 223 insertions(+), 116 deletions(-) create mode 100644 templates/redditors/view_profile.html diff --git a/redditors/models.py b/redditors/models.py index 8f519d4..6aeddbe 100644 --- a/redditors/models.py +++ b/redditors/models.py @@ -23,7 +23,7 @@ class Location(models.Model): 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) + geometry = models.PointField(srid=4326, blank=True, null=True) objects = models.GeoManager() def __str__(self): @@ -33,7 +33,6 @@ class Location(models.Model): """ 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): diff --git a/redditors/views.py b/redditors/views.py index a84fbe9..571fbec 100644 --- a/redditors/views.py +++ b/redditors/views.py @@ -1,17 +1,26 @@ +import urllib2 +from urllib2 import HTTPError + +try: + # Python 2.6 and up + import json as simplejson +except ImportError: + # This case gets rarer by the day, but if we need to, we can pull it from Django provided it's there. + from django.utils import simplejson + 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 django.contrib.auth.models import User +from django.contrib.auth.forms import UserCreationForm from drinkkit.redditors.models import LocationCategory, Location, Tip, Checkin @@ -153,28 +162,57 @@ def add_location(request): 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']) + # Somewhat ugly, but meh. + if request.POST['location_name'] and (request.POST['street_address'] or (request.POST['lat'] and request.POST['long'])): + new_location = Location() + new_location.name = request.POST['location_name'] + + if request.POST['lat'] and request.POST['long']: + new_location.geometry = 'POINT(%s %s)' %(request.POST['lat'], request.POST['long']) + else: + # If we don't have a lat/long pair, we know they got here by providing a street address, so + # let's do some geocoding via Google APIs and find the lat/long ourselves. + # + # Note: We assume Washington, DC here because of the region this application is suited to. If + # you wanted to expand on this, you'd probably wanna get the region/IP-(lat/long) (which isn't as accurate) + # and use it to sandbox your results. This is a pretty hacky 5AM thing. ;P + fixed_address = request.POST['street_address'].replace(" ", "+") + api_url = "http://maps.googleapis.com/maps/api/geocode/json?&address=%s,Washington,DC&sensor=false" % fixed_address + + try: + # Download and parse the JSON response into native Python objects. + geocode_request = urllib2.Request(api_url) + geocoded_data = simplejson.load(urllib2.urlopen(geocode_request)) + + # Store latitude and longitude, for readability + for result in geocoded_data["results"]: + latitude = result["geometry"]["location"]["lat"] + longitude = result["geometry"]["location"]["lng"] + + # Save our determined geometry coordinates + new_location.geometry = 'POINT(%s %s)' %(latitude, longitude) + except HTTPError: + # You're boned, Google's either blocking you or you done goofed. Ordinarily, you'd + # wanna handle errors properly here, but in my case I want a hard failure. YMMV. + pass + + # 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'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 type/category, 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 - # 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)) + 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?" + errmsg = "We weren't able to get coordinates for where you are right now. Does your phone or device have GPS? If not, specify an address and we'll use that instead!" 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', @@ -235,8 +273,17 @@ def add_tip(request, location_id): else: return HttpResponse(status=404) -def find_redditors(request): +def view_redditor(request, redditor_name): """ - Handles locating all Redditors in a given area who recently checked in. + View a Redditor "profile" - right now, all we do is show their checkin + history (where they've been). Stalking is fun, right? + + ...right? """ - pass + try: + redditor = User.objects.get(username = redditor_name) + return render_to_response('redditors/view_profile.html', + context_instance = RequestContext(request, {'redditor': redditor}) + ) + except User.DoesNotExist: + return HttpResponse(status=404) diff --git a/templates/base.html b/templates/base.html index 5f36405..f9da1c7 100644 --- a/templates/base.html +++ b/templates/base.html @@ -13,11 +13,11 @@ body { width: 100%; color: #555; - font-family:Helvetica, "Helvetica Neue", Arial, sans-serif; + font-family: Helvetica, "Helvetica Neue", Arial, sans-serif; height: 100%; padding: 0; margin: 0; - background:#c5ccd3; + background: #c5ccd3; -webkit-font-smoothing: antialiased; } @@ -26,11 +26,28 @@ padding: 10px 12px; margin: 0; } + + .rounded_6px { + -moz-border-radius: 6px; + -webkit-border-radius: 6px; + -khtml-border-radius: 6px; + border-radius: 6px; + } + + .rounded_4px { + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + -khtml-border-radius: 4px; + border-radius: 4px; + } + + #sandbox { clear: both; } #title { width: 100%; position: relative; background: #cee3f8; + background:-moz-linear-gradient(-90deg, #CEE3F8, #A8C4E0) repeat scroll 0 0 transparent; background: -webkit-gradient(linear, left top, left bottom, from(#cee3f8), to(#a8c4e0)); padding: 7px 0 6px 0; margin: 0; @@ -45,6 +62,7 @@ width: 100%; position: relative; background: #cdcdcd; + background:-moz-linear-gradient(-90deg, #fafafa, #cdcdcd) repeat scroll 0 0 transparent; background: -webkit-gradient(linear, left top, left bottom, from(#fafafa), to(#cdcdcd)); padding: 4px 0 6px; font-size: 1.2em; @@ -58,20 +76,27 @@ background-color: #fff; -webkit-border-radius: 4px; border: 1px solid #8f8f8f; + padding-bottom: 5px; } .button { background-color: #80A2C4; border: 1px solid #517191; + background:-moz-linear-gradient(-90deg, #bfd0e0, #80a2c4) repeat scroll 0 0 transparent; 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; } + .orange_button { + border: 1px solid #ff4500; + background-color: #ff4500; + background:-moz-linear-gradient(-90deg, #ff9f7b, #ff4500) repeat scroll 0 0 transparent; + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#ff9f7b), to(#ff4500)); + } + .large { font-size: 14px; padding: 4px 10px; @@ -86,9 +111,8 @@ #home_link, #nearby_link, #search_link, #find_link, #add_location { background-color: #80A2C4; border: 1px solid #517191; + background:-moz-linear-gradient(-90deg, #bfd0e0, #80a2c4) repeat scroll 0 0 transparent; 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; @@ -102,11 +126,11 @@ h2 { font-size: 18px; - /*color: #ff7041;*/ color: #6c7ca1; margin: 2px; padding: 8px; background-color: #ebf3fc; + background:-moz-linear-gradient(-90deg, #fafcfe, #ebf3fc) repeat scroll 0 0 transparent; background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#fafcfe), to(#ebf3fc)); border: 1px solid #c4dbf1; } @@ -133,47 +157,49 @@ } #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; -} + .top_level { + border: 1px solid #c4dbf1; + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; + float: left; + width: 99%; + padding-bottom: 5px; + margin-bottom: 10px; + } -#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;} + .top_level h3 { + background-color: #ebf3fc; + background:-moz-linear-gradient(-90deg, #fafcfe, #ebf3fc) repeat scroll 0 0 transparent; + background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#fafcfe), to(#ebf3fc)); + border-bottom: 1px solid #c4dbf1; + margin: 0; + padding: 5px; + font-size: 13px; + color: #6C7CA1; + } + .top_level ul { padding: 5px; font-size: 12px; } + .top_level ul li { margin-bottom: 3px !important; } + .checkin_list_timestamp { color: #555; font-weight: normal; float: right; } + #results li a, #results li a:visited { text-decoration: none; } + .anchored_action { + float: right; + clear: right; + margin: 5px 5px 0 0; + width: auto !important; + color: #fff !important; + font-size: 12px !important; + font-weight: bold; + padding: 3px 9px !important; + text-align: center !important; + -moz-border-radius: 6px; + -webkit-border-radius: 6px; + border-radius: 6px; + min-width: 100px; + } @@ -234,11 +263,11 @@
{% if not user.is_authenticated %} - Login - Register + Login + Register {% else %} - About - Logout + About + Logout {% endif %} drinkkit
@@ -250,11 +279,11 @@ - Ryan, 3:45AM --> - Home - Nearby - Search + Home + Nearby + Search {% if user.is_authenticated %} - Add Location + Add Location {% endif %} @@ -287,5 +316,7 @@
{% block content %}{% endblock %}
+ +
diff --git a/templates/locations/add.html b/templates/locations/add.html index 7f0c95f..df7b3df 100644 --- a/templates/locations/add.html +++ b/templates/locations/add.html @@ -88,7 +88,7 @@ _GPS.getLocation = function() {
- +
diff --git a/templates/locations/checkin.html b/templates/locations/checkin.html index f1c532e..7e2fd5d 100644 --- a/templates/locations/checkin.html +++ b/templates/locations/checkin.html @@ -26,7 +26,7 @@

- +
{% endblock %} diff --git a/templates/locations/get_coords_nearby.html b/templates/locations/get_coords_nearby.html index aa74fc6..cf3fe26 100644 --- a/templates/locations/get_coords_nearby.html +++ b/templates/locations/get_coords_nearby.html @@ -89,7 +89,7 @@ _GPS.getLocation = function() {
- +
diff --git a/templates/locations/home.html b/templates/locations/home.html index 1b0470f..5ac6577 100644 --- a/templates/locations/home.html +++ b/templates/locations/home.html @@ -8,7 +8,7 @@

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. + at the top of the screen.

@@ -21,13 +21,14 @@

diff --git a/templates/locations/search.html b/templates/locations/search.html index 8e692f0..738ca12 100644 --- a/templates/locations/search.html +++ b/templates/locations/search.html @@ -21,7 +21,7 @@

- +
@@ -32,16 +32,15 @@ diff --git a/templates/locations/view.html b/templates/locations/view.html index dfcffa6..7a70ed7 100644 --- a/templates/locations/view.html +++ b/templates/locations/view.html @@ -43,29 +43,29 @@ #checkin_here_link, #checked_in_already { position: absolute; - top: 10px; - right: 10px; + top: 5px; + right: 8px; -moz-border-radius: 4px; -webkit-border-radius: 4px; text-decoration: none; color: #fff; font-weight: bold; + padding: 3px 7px !important; + font-size: 12px; } - + + #checked_in_already { padding: 3px !important; font-size: 11px !important; top: 5px; /* I'm tired, hush */ } + #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; } @@ -160,10 +160,13 @@ {% 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)."}} +
  • +

    {{ checkin.user.username }} {{ checkin.timestamp|timesince }} ago

    + View User +
      +
    • Identify by: {{ checkin.identify_by }}
    • +
    • Should be there for: {{ checkin.estimated_time_here }}
    • +
  • {% endfor %}
@@ -171,13 +174,15 @@

No checkins yet. You could be the first!

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

Leave a Tip or Todo

{% csrf_token %} - +
{% endif %} diff --git a/templates/redditors/view_profile.html b/templates/redditors/view_profile.html new file mode 100644 index 0000000..347b896 --- /dev/null +++ b/templates/redditors/view_profile.html @@ -0,0 +1,22 @@ +{% extends "base.html" %} + +{% block extra_head %} +{% endblock %} + + +{% block content %} +

Where has {{ redditor.username }} been?

+ +
    + {% for checkin in redditor.checkin_set.all %} +
  • +

    {{ checkin.location.name }} {{ checkin.timestamp|timesince }} ago

    + View Location +
      +
    • Was identifiable by: {{ checkin.identify_by }}
    • +
    • Was there for: {{ checkin.estimated_time_here }}
    • +
    +
  • + {% endfor %} +
+{% endblock %} diff --git a/urls.py b/urls.py index 947a397..d57054b 100644 --- a/urls.py +++ b/urls.py @@ -5,7 +5,7 @@ from django.contrib import admin admin.autodiscover() urlpatterns = patterns('', - (r'^admin/', include(admin.site.urls)), + (r'^knux/', include(admin.site.urls)), # Viewing and adding tips/locations (r'^locations/add/$', 'drinkkit.redditors.views.add_location'), @@ -16,6 +16,9 @@ urlpatterns = patterns('', (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'), + # Query and see who's getting into what + (r'^redditor/(?P[a-zA-Z0-9_.-]+)/$', 'drinkkit.redditors.views.view_redditor'), + # Registration (r'^register/$', 'drinkkit.redditors.views.register'),