Permalink
Browse files

added initial app extracted from internal Eldarion code

  • Loading branch information...
1 parent 15fe949 commit 3d0b2669a486cc2003ab0eb610eae3d998beec64 @brosner brosner committed Dec 16, 2009
View
10 contacts_import/admin.py
@@ -0,0 +1,10 @@
+from django.contrib import admin
+
+from contacts_import.models import Contact
+
+
+class ContactAdmin(admin.ModelAdmin):
+ pass
+
+
+admin.site.register(Contact, ContactAdmin)
View
0 contacts_import/backends/__init__.py
No changes.
View
84 contacts_import/backends/importers.py
@@ -0,0 +1,84 @@
+import httplib2
+import vobject
+import ybrowserauth
+
+from django.conf import settings
+from django.utils import simplejson as json
+
+from celery.task import Task
+
+
+class BaseImporter(Task):
+ def run(self, credentials, persistance):
+ status = None
+ for contact in self.get_contacts(credentials):
+ status = persistance.persist(contact, status, credentials)
+ return status
+
+
+class VcardImporter(BaseImporter):
+ def get_contacts(self, credentials):
+ for card in vobject.readComponents(credentials["stream"]):
+ try:
+ yield {
+ "email": card.email.value,
+ "name": card.fn.value
+ }
+ except AttributeError:
+ # if a person doesn"t have an email or a name ignore them
+ continue
+
+
+class YahooImporter(BaseImporter):
+ def get_contacts(self, credentials):
+ ybbauth = ybrowserauth.YBrowserAuth(settings.BBAUTH_APP_ID,
+ settings.BBAUTH_SHARED_SECRET)
+ ybbauth.token = credentials["bbauth_token"]
+ address_book_json = ybbauth.makeAuthWSgetCall("http://address.yahooapis.com/v1/searchContacts?format=json&email.present=1&fields=name,email")
+ address_book = json.loads(address_book_json)
+
+ for contact in address_book["contacts"]:
+ email = contact["fields"][0]["data"]
+ try:
+ first_name = contact["fields"][1]["first"]
+ except (KeyError, IndexError):
+ first_name = None
+
+ try:
+ last_name = contact["fields"][1]["last"]
+ except (KeyError, IndexError):
+ last_name = None
+
+ if first_name and last_name:
+ name = "%s %s" % (first_name, last_name)
+ elif first_name:
+ name = first_name
+ elif last_name:
+ name = last_name
+ else:
+ name = ""
+
+ yield {
+ "email": email,
+ "name": name,
+ }
+
+
+GOOGLE_CONTACTS_URI = "http://www.google.com/m8/feeds/contacts/default/full?alt=json&max-results=1000"
+
+
+class GoogleImporter(BaseImporter):
+ def get_contacts(self, credentials):
+ h = httplib2.Http()
+ response, content = h.request(GOOGLE_CONTACTS_URI, headers={
+ "Authorization": "AuthSub token="%s"" % credentials["authsub_token"]
+ })
+ if response.status != 200:
+ return
+ results = json.loads(content)
+ for person in results["feed"]["entry"]:
+ for email in person.get("gd$email", []):
+ yield {
+ "name": person["title"]["$t"],
+ "email": email["address"],
+ }
View
39 contacts_import/backends/persistance.py
@@ -0,0 +1,39 @@
+from contacts_import.models import Contact
+
+
+class BasePersistance(object):
+ def persist(self, contact, status, credentials):
+ if status is None:
+ status = self.default_status()
+ return self.persist_contact(contact, status, credentials)
+
+ def default_status(self):
+ return None
+
+ def persist_contact(self, contact, status, credentials):
+ return None
+
+
+class ModelPersistance(BasePersistance):
+ def default_status(self):
+ return {
+ "imported": 0,
+ "total": 0,
+ }
+
+ def persist_contact(self, contact, status, credentials):
+ obj, created = Contact.objects.get_or_create(user=credentials["user"],
+ email=contact["email"], defaults={"name": contact["name"]})
+ status["total"] += 1
+ if created:
+ status["imported"] += 1
+ return status
+
+
+class InMemoryPersistance(BasePersistance):
+ def default_status(self):
+ return []
+
+ def persist_contact(self, contact, status, credentials):
+ status.append(contact)
+ return status
View
31 contacts_import/backends/runners.py
@@ -0,0 +1,31 @@
+import sys
+
+from django.conf import settings
+
+
+DEFAULT_CONTACT_PERSISTANCE = getattr(settings, "DEFAULT_CONTACT_PERSISTANCE",
+ "contacts_import.backends.persistance.ModelPersistance")
+
+
+class BaseRunner(object):
+ def __init__(self, importer, persistance=None, **credentials):
+ if persistance is None:
+ module, klass = DEFAULT_CONTACT_PERSISTANCE.rsplit(".", 1)
+ __import__(module)
+ persistance = getattr(sys.modules[module], klass)
+ self.importer = importer
+ self.persistance = persistance
+ self.credentials = credentials
+
+ def import_contacts(self):
+ raise NotImplementedError("Implement this in a subclass")
+
+
+class SynchronousRunner(BaseRunner):
+ def import_contacts(self):
+ return self.importer.apply(args=[self.credentials, self.persistance()])
+
+
+class AsyncRunner(BaseRunner):
+ def import_contacts(self):
+ return self.importer.delay(self.credentials, self.persistance())
View
13 contacts_import/forms.py
@@ -0,0 +1,13 @@
+from django import forms
+
+from contacts_import.backends.importers import VcardImporter
+from contacts_import.backends.runners import SynchronousRunner
+
+
+class VcardImportForm(forms.Form):
+ vcard_file = forms.FileField(label="vCard File")
+
+ def save(self, user, runner_class=SynchronousRunner):
+ importer = runner_class(VcardImporter, user=user,
+ stream=self.cleaned_data["vcard_file"].content)
+ return importer.import_contacts()
View
20 contacts_import/models.py
@@ -0,0 +1,20 @@
+from datetime import date
+
+from django.db import models
+
+from django.contrib.auth.models import User
+
+
+class Contact(models.Model):
+ # The user who created this contact
+ user = models.ForeignKey(User, related_name="contacts")
+
+ name = models.CharField(max_length=100, blank=True)
+ email = models.EmailField()
+ added = models.DateField(default=date.today)
+
+ # the user(s) this contact ultimately corrosponds to
+ users = models.ManyToManyField(User)
+
+ def __unicode__(self):
+ return "%s (%s's contact)" % (self.email, self.user)
View
8 contacts_import/tasks.py
@@ -0,0 +1,8 @@
+from celery.task import tasks
+
+from contacts_import.backends import importers
+
+
+tasks.register(importers.VcardImporter)
+tasks.register(importers.GoogleImporter)
+tasks.register(importers.YahooImporter)
View
40 contacts_import/tests.py
@@ -0,0 +1,40 @@
+from django.core.urlresolvers import reverse
+from django.test import TestCase
+
+from django.contrib.auth.models import User
+
+from contacts_import.models import Contact
+
+
+class BasicTest(TestCase):
+ fixtures = ["sample_data.json"]
+
+ def setUp(self):
+ self.client.login(username="bob", password="abc123")
+ self.bob = User.objects.get(username="bob")
+
+ def tearDown(self):
+ self.client.logout()
+
+ def tests_views(self):
+ response = self.client.get(reverse("import_contacts"))
+ self.assertEqual(response.status_code, 200)
+
+ response = self.client.post(reverse("import_contacts"), {
+ "action": "upload_vcard",
+ })
+ self.assertEqual(response.status_code, 200)
+
+ response = self.client.get(reverse("authsub_login"))
+ self.assertEqual(response.status_code, 302)
+
+ def test_contact_display(self):
+ Contact.objects.create(user=self.bob, name="Bjarne Stroustrup",
+ email="bjarne@making.life.hard.com")
+ Contact.objects.create(user=self.bob, name="Guido van Rossum",
+ email="guido@python.org")
+
+ response = self.client.get(reverse("import_contacts"))
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, "Bjarne Stroustrup")
+ self.assertContains(response, "Guido van Rossum")
View
16 contacts_import/urls.py
@@ -0,0 +1,16 @@
+from django.conf.urls.defaults import *
+
+import contacts_import.tasks
+
+
+urlpatterns = patterns("contacts_import.views",
+ url(r"^import_contacts/$", "import_contacts", name="import_contacts"),
+ url(r"^authsub/login/$", "authsub_login", name="authsub_login"),
+)
+
+
+urlpatterns += patterns("bbauth.views",
+ url(r"^bbauth/login/$", "login", {
+ "redirect_to": "/contacts/import_contacts/"
+ }, name="bbauth_login"),
+)
View
85 contacts_import/views.py
@@ -0,0 +1,85 @@
+from django.core.paginator import Paginator
+from django.core.urlresolvers import reverse
+from django.http import HttpResponseRedirect
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.utils.translation import ugettext as _
+
+from django.contrib.auth.decorators import login_required
+
+from gdata.contacts.service import ContactsService
+
+from contacts_import.forms import VcardImportForm
+from contacts_import.backends.importers import GoogleImporter, YahooImporter
+from contacts_import.backends.runners import SynchronousRunner, AsyncRunner
+
+
+GOOGLE_CONTACTS_URI = "http://www.google.com/m8/feeds/"
+
+
+def _import_success(request, results):
+ if results.ready():
+ if results.status == "DONE":
+ request.user.message_set.create(message=_(
+ "%(total)s people with email found, %(imported)s contacts imported."
+ ) % results.result)
+ elif results.status == "FAILURE":
+ request.user.message_set.create(message=_("There was an error "
+ "importing your contacts."))
+ else:
+ request.user.message_set.create(message=_("We're still importing your "
+ "contacts. We'll let you know when they're ready, it shouldn't "
+ "take too long."))
+ request.session["import_contacts_task_id"] = results.task_id
+ return HttpResponseRedirect(request.path)
+
+
+@login_required
+def import_contacts(request, runner_class=AsyncRunner):
+ if request.method == "POST":
+ if request.POST["action"] == "upload_vcard":
+ form = VcardImportForm(request.POST)
+ if form.is_valid():
+ results = form.save(request.user, runner_class=runner_class)
+ return _import_success(request, results)
+ else:
+ form = VcardImportForm()
+ if request.POST["action"] == "import_yahoo":
+ bbauth_token = request.session.pop("bbauth_token", None)
+ if bbauth_token:
+ results = runner_class(YahooImporter, user=request.user,
+ bbauth_token=bbauth_token).import_contacts()
+ return _import_success(request, results)
+ elif request.POST["action"] == "import_google":
+ authsub_token = request.session.pop("authsub_token", None)
+ if authsub_token:
+ results = runner_class(GoogleImporter, user=request.user,
+ authsub_token=authsub_token).import_contacts()
+ return _import_success(request, results)
+ else:
+ form = VcardImportForm()
+
+ contacts = request.user.contacts.all()
+ page = Paginator(contacts, 50).page(request.GET.get("page", 1))
+
+ return render_to_response("contacts_import/import_contacts.html", {
+ "form": form,
+ "bbauth_token": request.session.get("bbauth_token"),
+ "authsub_token": request.session.get("authsub_token"),
+ "page": page,
+ "task_id": request.session.pop("import_contacts_task_id", None),
+ }, context_instance=RequestContext(request))
+
+
+def _authsub_url(next):
+ contacts_service = ContactsService()
+ return contacts_service.GenerateAuthSubURL(next, GOOGLE_CONTACTS_URI, False, True)
+
+
+def authsub_login(request, redirect_to=None):
+ if redirect_to is None:
+ redirect_to = reverse("import_contacts")
+ if "token" in request.GET:
+ request.session["authsub_token"] = request.GET["token"]
+ return HttpResponseRedirect(redirect_to)
+ return HttpResponseRedirect(_authsub_url(request.build_absolute_uri()))
View
1 setup.py
@@ -12,6 +12,7 @@
url = "http://github.com/eldarion/django-contacts-import",
packages = [
"contacts_import",
+ "contacts_import.backends",
],
classifiers = [
"Development Status :: 3 - Alpha",

0 comments on commit 3d0b266

Please sign in to comment.