Merge branch 'master' of github.com:Gluejar/regluit

pull/1/head
Raymond Yee 2011-09-16 10:12:39 -07:00
commit f774d9d552
20 changed files with 437 additions and 24 deletions

View File

103
api/resources.py Executable file
View File

@ -0,0 +1,103 @@
import logging
from django.contrib import auth
from django.contrib.auth.models import User, AnonymousUser
from django.conf.urls.defaults import url
from django.db.models import Q
from tastypie import fields
from tastypie.constants import ALL, ALL_WITH_RELATIONS
from tastypie.resources import ModelResource, Resource, Bundle
from tastypie.utils import trailing_slash
from tastypie.authentication import ApiKeyAuthentication
from regluit.core import models
logger = logging.getLogger(__name__)
class UserResource(ModelResource):
class Meta:
authentication = ApiKeyAuthentication()
queryset = User.objects.all()
resource_name = 'user'
fields = ['username', 'first_name', 'last_name']
class EditionResource(ModelResource):
class Meta:
authentication = ApiKeyAuthentication()
queryset = models.Edition.objects.all()
resource_name = 'edition'
filtering = {
"isbn_10": ALL,
}
class WorkResource(ModelResource):
editions = fields.ToManyField(EditionResource, 'editions')
class Meta:
authentication = ApiKeyAuthentication()
queryset = models.Work.objects.all()
resource_name = 'work'
filtering = {'editions': ALL_WITH_RELATIONS, 'id': ALL}
class CampaignResource(ModelResource):
work = fields.ToOneField(WorkResource, 'work')
def alter_list_data_to_serialize(self, request, data):
"""
annotate the list of campaigns with information from the logged in
user. note: this isn't the user identified by the api username/api_key
it's the the user that client might be logged into unglue.it as.
"""
u = auth.get_user(request)
if isinstance(u, User):
data['meta']['logged_in_username'] = u.username
wishlist_work_ids = [w.id for w in u.wishlist.works.all()]
else:
data['meta']['logged_in_username'] = None
wishlist_work_ids = []
for o in data['objects']:
o.data['in_wishlist'] = o.obj.id in wishlist_work_ids
# TODO: add pledging information
return data
class Meta:
authentication = ApiKeyAuthentication()
queryset = models.Campaign.objects.all()
resource_name = 'campaign'
excludes = ['amazon_receiver', 'paypal_receiver']
filtering = {
"work": ALL_WITH_RELATIONS,
}
class AuthorResource(ModelResource):
works = fields.ToManyField(WorkResource, 'works')
class Meta:
authentication = ApiKeyAuthentication()
queryset = models.Author.objects.all()
resource_name = 'author'
class SubjectResource(ModelResource):
works = fields.ToManyField(WorkResource, 'works')
class Meta:
authentication = ApiKeyAuthentication()
queryset = models.Subject.objects.all()
resource_name = 'subject'
class EditionCoverResource(ModelResource):
edition = fields.ToManyField(EditionResource, 'editions')
class Meta:
authentication = ApiKeyAuthentication()
queryset = models.EditionCover.objects.all()
resource_name = 'editioncover'
class WishlistResource(ModelResource):
user = fields.ToOneField(UserResource, 'user')
works = fields.ToManyField(WorkResource, 'works')
class Meta:
authentication = ApiKeyAuthentication()
queryset = models.Wishlist.objects.all()
resource_name = 'wishlist'

View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<title>editions</title>
</head>
<body>
<!--- editions --->
{% if editions %}
<ul>
{% for edition in editions %}
<li> <img src="http://covers.openlibrary.org/b/isbn/{{edition.isbn_10}}-S.jpg" /> {{edition.id}} | {{edition.title}} |
<a href="{% url isbn isbn=edition.isbn_10 %}">{{edition.isbn_10}}</a> |
<a href="{% url isbn isbn=edition.isbn_13 %}">{{edition.isbn_13}}</a>
{% endfor %}
</ul>
{% else %}
<p>No editions are available.</p>
{% endif %}
</body>
</html>

58
api/templates/isbn.html Normal file
View File

@ -0,0 +1,58 @@
<!DOCTYPE html>
<html>
<head>
<title>isbn</title>
</head>
<body>
<p>You entered isbn: {{isbn}}</p>
<!--- editions --->
{% if editions %}
<ul>
{% for edition in editions %}
<li>{{edition.id}} | {{edition.title}}
<a href="http://openlibrary.org/isbn/{{edition.isbn_10}}"><img src="http://covers.openlibrary.org/b/isbn/{{edition.isbn_10}}-M.jpg"/ width="120" height="182" ></a>
<!-- editioncovers for given edition -->
{% if edition.covers.all %}
<ul id="id">
{% for editioncover in edition.covers.all %}
<li>cover OL id: {{editioncover.openlibrary_id}}</li>
{% endfor %}
</ul>
{% else %}
<p class="classname">No editioncover for this edition</p>
{% endif %}
<!-- related work -->
{% if edition.work %}
<p class="classname">Related work: {{edition.work.id}} | {{edition.work.title}} | first author: {{edition.work.authors.all.0.name}} <br/>
All authors: {{edition.work.authors.all}} </p>
{% endif %}
<!-- related campaigns -->
{% if edition.work.campaigns.all %}
<ul id="id">
{% for campaign in edition.work.campaigns.all %}
<li>Campaign: id:{{campaign.id}} | name:{{campaign.name}} | desc:{{campaign.description}} <br/>
target:{{campaign.target}} created:{{campaign.created}} deadline:{{campaign.deadline}}</li>
{% endfor %}
</ul>
{% else %}
<p class="classname">No associated campaigns</p>
{% endif %}
</li>
{% endfor %}
</ul>
{% else %}
<p>No editions are available.</p>
{% endif %}
</body>
</html>

View File

@ -1,23 +1,95 @@
""" import json
This file demonstrates two different styles of tests (one doctest and one import datetime
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 from django.test import TestCase
from django.test.client import Client
from django.contrib.auth.models import User
class SimpleTest(TestCase): from regluit.core import bookloader, models
def test_basic_addition(self):
"""
Tests that 1 + 1 always equals 2.
"""
self.failUnlessEqual(1 + 1, 2)
__test__ = {"doctest": """ class ApiTests(TestCase):
Another way to test that 1 + 1 is equal to 2.
>>> 1 + 1 == 2 def setUp(self):
True edition = bookloader.add_book(isbn='0441012035')
"""} campaign = models.Campaign.objects.create(
name=edition.work.title,
work=edition.work,
description='Test Campaign',
deadline=datetime.datetime.now(),
target=1000.0,
)
self.user = User.objects.create_user('test', 'test@example.com', 'testpass')
self.client = Client()
def test_user(self):
self.assertEqual(User.objects.all().count(), 1)
self.assertTrue(User.objects.all()[0].api_key.key)
def test_no_auth(self):
r = self.client.get('/api/v1/campaign/', data={'format': 'json'})
self.assertEqual(r.status_code, 401)
def test_campaigns(self):
r = self.client.get('/api/v1/campaign/', data={
'format': 'json',
'username': self.user.username,
'api_key': self.user.api_key.key
})
self.assertEqual(r.status_code, 200)
j = json.loads(r.content)
self.assertEqual(len(j['objects']), 1)
self.assertEqual(j['objects'][0]['name'], 'Neuromancer')
self.assertEqual(j['objects'][0]['work'], '/api/v1/work/1/')
def test_campaign(self):
r = self.client.get('/api/v1/campaign/1/', data={
'format': 'json',
'username': self.user.username,
'api_key': self.user.api_key.key
})
self.assertEqual(r.status_code, 200)
j = json.loads(r.content)
self.assertEqual(j['name'], 'Neuromancer')
self.assertEqual(j['work'], '/api/v1/work/1/')
def test_campaign_lookup_by_isbn(self):
r = self.client.get('/api/v1/campaign/', data={
'format': 'json',
'work__editions__isbn_10': '0441012035',
'username': self.user.username,
'api_key': self.user.api_key.key
})
self.assertEqual(r.status_code, 200)
j = json.loads(r.content)
self.assertEqual(len(j['objects']), 1)
self.assertEqual(j['objects'][0]['name'], 'Neuromancer')
self.assertEqual(j['meta']['logged_in_username'], None)
self.assertEqual(j['objects'][0]['in_wishlist'], False)
def test_logged_in_user_info(self):
# login and see if adding a work to the users wishlist causes
# it to show up as in_wishlist in the campaign info
self.client.login(username='test', password='testpass')
r = self.client.get('/api/v1/campaign/', data={
'format': 'json',
'work__editions__isbn_10': '0441012035',
'username': self.user.username,
'api_key': self.user.api_key.key
})
j = json.loads(r.content)
self.assertEqual(j['meta']['logged_in_username'], 'test')
self.assertEqual(j['objects'][0]['in_wishlist'], False)
w = models.Work.objects.get(editions__isbn_10='0441012035')
self.user.wishlist.works.add(w)
r = self.client.get('/api/v1/campaign/', data={
'format': 'json',
'work__editions__isbn_10': '0441012035',
'username': self.user.username,
'api_key': self.user.api_key.key
})
j = json.loads(r.content)
self.assertEqual(j['meta']['logged_in_username'], 'test')
self.assertEqual(j['objects'][0]['in_wishlist'], True)

18
api/urls.py Normal file
View File

@ -0,0 +1,18 @@
from django.conf.urls.defaults import *
from tastypie.api import Api
from regluit.api import resources
v1_api = Api(api_name='v1')
v1_api.register(resources.UserResource())
v1_api.register(resources.WorkResource())
v1_api.register(resources.EditionResource())
v1_api.register(resources.EditionCoverResource())
v1_api.register(resources.CampaignResource())
v1_api.register(resources.AuthorResource())
v1_api.register(resources.SubjectResource())
v1_api.register(resources.WishlistResource())
urlpatterns = patterns('',
(r'^', include(v1_api.urls)),
)

View File

@ -0,0 +1,22 @@
from django.template import RequestContext
from django.shortcuts import render_to_response, get_object_or_404
from django.db.models import Q
from regluit.core import models
def isbn(request,isbn):
editions = models.Edition.objects.filter(Q(isbn_10 = isbn) | Q(isbn_13 = isbn))
# models.Campaign.objects.filter(work__editions__isbn_13='9780811216999')
return render_to_response('isbn.html',
{'isbn':isbn, 'editions':editions},
context_instance=RequestContext(request)
)
def editions(request):
editions = models.Edition.objects.all()
return render_to_response('editions.html',
{'editions':editions},
context_instance=RequestContext(request)
)

View File

@ -0,0 +1,73 @@
# code modified from http://stackoverflow.com/questions/4734645/is-there-a-tool-to-check-database-integrity-in-django/4736176#4736176
from django.core.management.base import BaseCommand, CommandError
from django.core.management.base import NoArgsCommand
from django.core.exceptions import ObjectDoesNotExist
from django.db import models
from optparse import make_option
def model_name(model):
return '%s.%s' % (model._meta.app_label, model._meta.object_name)
class Command(BaseCommand):
args = '[-e|--exclude app_name.ModelName]'
help = 'Checks constraints in the database and reports violations on stdout'
option_list = NoArgsCommand.option_list + (
make_option('-e', '--exclude', action='append', type='string', dest='exclude'),
)
def handle(self, *args, **options):
# TODO once we're on Django 1.2, write to self.stdout and self.stderr instead of plain print
exclude = options.get('exclude', None) or []
failed_instance_count = 0
failed_model_count = 0
for app in models.get_apps():
for model in models.get_models(app):
if model_name(model) in exclude:
print 'Skipping model %s' % model_name(model)
continue
fail_count = self.check_model(app, model)
if fail_count > 0:
failed_model_count += 1
failed_instance_count += fail_count
print 'Detected %d errors in %d models' % (failed_instance_count, failed_model_count)
def check_model(self, app, model):
meta = model._meta
if meta.proxy:
print 'WARNING: proxy models not currently supported; ignored'
return
# Define all the checks we can do; they return True if they are ok,
# False if not (and print a message to stdout)
def check_foreign_key(model, field):
foreign_model = field.related.parent_model
def check_instance(instance):
try:
# name: name of the attribute containing the model instance (e.g. 'user')
# attname: name of the attribute containing the id (e.g. 'user_id')
getattr(instance, field.name)
return True
except ObjectDoesNotExist:
print '%s with pk %s refers via field %s to nonexistent %s with pk %s' % \
(model_name(model), str(instance.pk), field.name, model_name(foreign_model), getattr(instance, field.attname))
return check_instance
# Make a list of checks to run on each model instance
checks = []
for field in meta.local_fields + meta.local_many_to_many + meta.virtual_fields:
if isinstance(field, models.ForeignKey):
checks.append(check_foreign_key(model, field))
# Run all checks
fail_count = 0
if checks:
for instance in model.objects.all():
print 'Checking model %s ...' % model_name(model)
for check in checks:
if not check(instance):
fail_count += 1
return fail_count

View File

@ -0,0 +1,11 @@
from django.core.management.base import BaseCommand
from regluit.core import models
class Command(BaseCommand):
help = "list all editions in the database"
def handle(self, *args, **options):
editions = models.Edition.objects.all()
for edition in editions:
print edition.id, edition.title, edition.isbn_10, edition.isbn_13

View File

@ -6,8 +6,8 @@ class Campaign(models.Model):
created = models.DateTimeField(auto_now_add=True) created = models.DateTimeField(auto_now_add=True)
name = models.CharField(max_length=500, null=False) name = models.CharField(max_length=500, null=False)
description = models.CharField(max_length=10000, null=False) description = models.CharField(max_length=10000, null=False)
target = models.FloatField() target = models.FloatField(null=False)
deadline = models.DateTimeField() deadline = models.DateTimeField(null=False)
paypal_receiver = models.CharField(max_length=100, null=True) paypal_receiver = models.CharField(max_length=100, null=True)
amazon_receiver = models.CharField(max_length=100, null=True) amazon_receiver = models.CharField(max_length=100, null=True)
work = models.ForeignKey("Work", related_name="campaigns") work = models.ForeignKey("Work", related_name="campaigns")

View File

@ -5,6 +5,7 @@ from django.contrib.auth.models import User
from social_auth.signals import pre_update from social_auth.signals import pre_update
from social_auth.backends.facebook import FacebookBackend from social_auth.backends.facebook import FacebookBackend
from tastypie.models import create_api_key
def facebook_extra_values(sender, user, response, details, **kwargs): def facebook_extra_values(sender, user, response, details, **kwargs):
@ -17,14 +18,15 @@ pre_update.connect(facebook_extra_values, sender=FacebookBackend)
def create_wishlist(sender, created, instance, **kwargs): def create_wishlist(sender, created, instance, **kwargs):
# use get_model to avoid circular import problem with models # use get_model to avoid circular import problem with models
# this fails when a superuser is being created as part of a syncdb
# since the database table for wishlist doesn't exist yet
try: try:
Wishlist = get_model('core', 'Wishlist') Wishlist = get_model('core', 'Wishlist')
if created: if created:
Wishlist.objects.create(user=instance) Wishlist.objects.create(user=instance)
except DatabaseError: except DatabaseError:
# this can happen when creating superuser during syncdb since the
# core_wishlist table doesn't exist yet
return return
post_save.connect(create_wishlist, sender=User) post_save.connect(create_wishlist, sender=User)
post_save.connect(create_api_key, sender=User)

View File

@ -40,6 +40,12 @@
</div> </div>
<div id="footer">
<ul class="menu">
<li><a href="{% url privacy %}"><span>Privacy</span></a></li>
</ul>
</div>
</body> </body>
</html> </html>

View File

@ -1,3 +1,7 @@
{% extends "base.html" %}
{% block content %}
<h1>Gluejar Privacy Policy</h1> <h1>Gluejar Privacy Policy</h1>
Date of last revision: September 13, 2011 Date of last revision: September 13, 2011
@ -46,3 +50,6 @@ Date of last revision: September 13, 2011
<h2>Contact Us</h2> <h2>Contact Us</h2>
<p>If you have any questions regarding this privacy policy, you can contact us at <a href="mailto:contact@gluejar.com?Subject=Questions%about%20privacy%20policy">contact@gluejar.com</a>, or using our contact page.</p> <p>If you have any questions regarding this privacy policy, you can contact us at <a href="mailto:contact@gluejar.com?Subject=Questions%about%20privacy%20policy">contact@gluejar.com</a>, or using our contact page.</p>
{% endblock %}

View File

@ -4,4 +4,5 @@ urlpatterns = patterns(
"regluit.frontend.views", "regluit.frontend.views",
url(r"^$", "home", name="home"), url(r"^$", "home", name="home"),
url(r"^supporter/(?P<supporter_username>.+)/$", "supporter", name="supporter"), url(r"^supporter/(?P<supporter_username>.+)/$", "supporter", name="supporter"),
url(r"^privacy$", "textpage", {'page': 'privacy'}, name="privacy"),
) )

View File

@ -16,3 +16,9 @@ def supporter(request, supporter_username):
{"supporter": supporter}, {"supporter": supporter},
context_instance=RequestContext(request) context_instance=RequestContext(request)
) )
def textpage(request, page):
return render_to_response(page + '.html',
{},
context_instance=RequestContext(request)
)

View File

@ -1,7 +1,7 @@
django django
south south
django-extensions django-extensions
django-tastypie https://github.com/toastdriven/django-tastypie/tarball/master
requests requests
https://bitbucket.org/ubernostrum/django-registration/get/tip.tar.gz https://bitbucket.org/ubernostrum/django-registration/get/tip.tar.gz
django-social-auth django-social-auth

View File

@ -105,6 +105,7 @@ INSTALLED_APPS = (
'regluit.core', 'regluit.core',
'registration', 'registration',
'social_auth', 'social_auth',
'tastypie',
) )
# A sample logging configuration. The only tangible logging # A sample logging configuration. The only tangible logging

View File

@ -20,6 +20,7 @@ body{
#header{ height:90px;} #header{ height:90px;}
.logo{ float:left; padding-top:20px;} .logo{ float:left; padding-top:20px;}
.logo a img{border: none;}
.topmenu{ float:right; padding-top:25px;} .topmenu{ float:right; padding-top:25px;}
.topmenu ul li{ float:left; padding:0 10px;} .topmenu ul li{ float:left; padding:0 10px;}
.topmenu ul li a{ height:36px; line-height:36px; display:block; color:#fff; text-decoration:none; font-weight:bold; font-size:13px; letter-spacing:1px; text-transform:capitalize;} .topmenu ul li a{ height:36px; line-height:36px; display:block; color:#fff; text-decoration:none; font-weight:bold; font-size:13px; letter-spacing:1px; text-transform:capitalize;}
@ -132,3 +133,9 @@ a{ font-weight:bold; font-size:13px; text-decoration:none; cursor:pointer;}
padding-top: 5px; padding-top: 5px;
padding-bottom: 5px; padding-bottom: 5px;
} }
#footer{
border-top: 7px solid #edf3f4;
clear: both;
height:90px;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -3,6 +3,6 @@ from django.conf.urls.defaults import *
urlpatterns = patterns('', urlpatterns = patterns('',
(r'^accounts/', include('registration.backends.default.urls')), (r'^accounts/', include('registration.backends.default.urls')),
(r'^socialauth/', include('social_auth.urls')), (r'^socialauth/', include('social_auth.urls')),
(r'^api/', include('regluit.api.urls')),
(r'', include('regluit.frontend.urls')), (r'', include('regluit.frontend.urls')),
) )