Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

initial commit

  • Loading branch information...
commit 7a56b7987a1d9e18b4b92a050862b019fa57a92c 0 parents
Patrick Altman paltman authored
0  invitations/__init__.py
No changes.
13 invitations/admin.py
... ... @@ -0,0 +1,13 @@
  1 +from django.contrib import admin
  2 +
  3 +from invitations.models import JoinInvitation, InvitationStat
  4 +
  5 +
  6 +class InvitationStatAdmin(admin.ModelAdmin):
  7 + readonly_fields = ["user", "invites_sent", "invites_accepted"]
  8 + list_display = ["user", "invites_sent", "invites_accepted", "invites_allocated", "invites_remaining", "can_send"]
  9 + list_filter = ["invites_sent", "invites_accepted"]
  10 +
  11 +
  12 +admin.site.register(JoinInvitation)
  13 +admin.site.register(InvitationStat, InvitationStatAdmin)
6 invitations/forms.py
... ... @@ -0,0 +1,6 @@
  1 +from django import forms
  2 +
  3 +
  4 +class InviteForm(forms.Form):
  5 +
  6 + email_address = forms.EmailField()
102 invitations/models.py
... ... @@ -0,0 +1,102 @@
  1 +import datetime
  2 +
  3 +from django.db import models
  4 +from django.db.models.signals import post_save
  5 +from django.conf import settings
  6 +
  7 +from django.contrib.auth.models import User
  8 +
  9 +from pinax.apps.signup_codes.models import SignupCode, SignupCodeResult
  10 +from pinax.apps.signup_codes.signals import signup_code_used
  11 +
  12 +from invitations.signals import invite_sent, invite_accepted
  13 +
  14 +
  15 +DEFAULT_INVITE_ALLOCATION = getattr(settings, "INVITATIONS_DEFAULT_ALLOCATION", 0)
  16 +DEFAULT_INVITE_EXPIRATION = getattr(settings, "INVITATIONS_DEFAULT_EXPIRATION", 168) # 168 Hours = 7 Days
  17 +
  18 +
  19 +class NotEnoughInvitationsError(Exception):
  20 + pass
  21 +
  22 +
  23 +class JoinInvitation(models.Model):
  24 +
  25 + STATUS_SENT = 1
  26 + STATUS_ACCEPTED = 2
  27 + STATUS_JOINED_INDEPENDENTLY = 3
  28 +
  29 + INVITE_STATUS_CHOICES = [
  30 + (STATUS_SENT, "Sent"),
  31 + (STATUS_ACCEPTED, "Accepted"),
  32 + (STATUS_JOINED_INDEPENDENTLY, "Joined Independently")
  33 + ]
  34 +
  35 + from_user = models.ForeignKey(User, related_name="invites_sent")
  36 + to_user = models.ForeignKey(User, null=True, related_name="invites_received")
  37 + message = models.TextField(null=True)
  38 + sent = models.DateTimeField(default=datetime.datetime.now)
  39 + status = models.IntegerField(choices=INVITE_STATUS_CHOICES)
  40 + signup_code = models.OneToOneField(SignupCode)
  41 +
  42 + @classmethod
  43 + def invite(cls, from_user, to_email, message=None):
  44 + if not from_user.invitationstat.can_send():
  45 + raise NotEnoughInvitationsError()
  46 +
  47 + signup_code = SignupCode.create(to_email, DEFAULT_INVITE_EXPIRATION)
  48 + signup_code.save()
  49 + join = cls.objects.create(
  50 + from_user=from_user,
  51 + message=message,
  52 + status=JoinInvitation.STATUS_SENT,
  53 + signup_code=signup_code
  54 + )
  55 + signup_code.send() # @@@ might want to implement our own method and just set the .send field on signup_code
  56 + stat = from_user.invitationstat
  57 + stat.invites_sent += 1
  58 + stat.save()
  59 + invite_sent.send(sender=cls, invitation=join)
  60 + return join
  61 +
  62 +
  63 +class InvitationStat(models.Model):
  64 +
  65 + user = models.OneToOneField(User)
  66 + invites_sent = models.IntegerField(default=0)
  67 + invites_allocated = models.IntegerField(default=DEFAULT_INVITE_ALLOCATION)
  68 + invites_accepted = models.IntegerField(default=0)
  69 +
  70 + def invites_remaining(self):
  71 + return self.invites_allocated - self.invites_sent
  72 +
  73 + def can_send(self):
  74 + return self.invites_allocated > self.invites_sent
  75 + can_send.boolean = True
  76 +
  77 +
  78 +def process_used_signup_code(sender, **kwargs):
  79 + result = kwargs.get("signup_code_result")
  80 + try:
  81 + invite = result.signup_code.joininvitation
  82 + invite.to_user = result.user
  83 + invite.status = JoinInvitation.STATUS_ACCEPTED
  84 + invite.save()
  85 + stat = invite.from_user.invitationstat
  86 + stat.invites_accepted += 1
  87 + stat.save()
  88 + invite_accepted.send(sender=JoinInvitation, invitation=invite)
  89 + except JoinInvitation.DoesNotExist:
  90 + pass
  91 +
  92 +
  93 +signup_code_used.connect(process_used_signup_code, sender=SignupCodeResult)
  94 +
  95 +
  96 +def create_stat(sender, instance=None, **kwargs):
  97 + if instance is None:
  98 + return
  99 + InvitationStat.objects.get_or_create(user=instance)
  100 +
  101 +
  102 +post_save.connect(create_stat, sender=User)
5 invitations/signals.py
... ... @@ -0,0 +1,5 @@
  1 +import django.dispatch
  2 +
  3 +
  4 +invite_sent = django.dispatch.Signal(providing_args=["invitation",])
  5 +invite_accepted = django.dispatch.Signal(providing_args=["invitation"])
12 invitations/templates/invitations/_invite_form.html
... ... @@ -0,0 +1,12 @@
  1 +{% load uni_form_tags %}
  2 +<div class="invitation_form">
  3 + {% if user.invitationstat.can_send %}
  4 + <form action="{% url invitations_invite %}" method="POST" accept-charset="utf-8">
  5 + {% csrf_token %}
  6 + {{ form|as_uni_form }}
  7 + <p><input type="submit" value="Invite"></p>
  8 + </form>
  9 + {% else %}
  10 + <p>You do not have any invitiations currrently.</p>
  11 + {% endif %}
  12 +</div>
19 invitations/templates/invitations/_invited.html
... ... @@ -0,0 +1,19 @@
  1 +{% load invitations_tags %}
  2 +
  3 +{% if invited_list %}
  4 + <ul>
  5 + {% for invited in invited_list %}
  6 + <li class="{{ invited|status_class }}">
  7 + {% if invited.to_user %}
  8 + <a href="{{ invited.to_user.get_profile.get_absolute_url }}">
  9 + {{ invited.signup_code.email }}
  10 + </a>
  11 + {% else %}
  12 + {{ invited.signup_code.email }}
  13 + {% endif %}
  14 + </li>
  15 + {% endfor %}
  16 + </ul>
  17 +{% else %}
  18 + <p>You have no sent any invites.</p>
  19 +{% endif %}
0  invitations/templatetags/__init__.py
No changes.
58 invitations/templatetags/invitations_tags.py
... ... @@ -0,0 +1,58 @@
  1 +from django import template
  2 +
  3 +from invitations.forms import InviteForm
  4 +
  5 +
  6 +register = template.Library()
  7 +
  8 +
  9 +class RemainingInvitesNode(template.Node):
  10 +
  11 + @classmethod
  12 + def handle_token(cls, parser, token):
  13 + bits = token.split_contents()
  14 + if len(bits) != 2:
  15 + raise template.TemplateSyntaxError
  16 +
  17 + return cls(
  18 + user = parser.compile_filter(bits[1])
  19 + )
  20 +
  21 + def __init__(self, user):
  22 + self.user = user
  23 +
  24 + def render(self, context):
  25 + user = self.user.resolve(context)
  26 + return user.invitationstat.invites_remaining()
  27 +
  28 +
  29 +@register.tag
  30 +def remaining_invites(parser, token):
  31 + """
  32 + Usage::
  33 + {% remaining_invites user %}
  34 +
  35 + Returns an integer that is the # of remaining invites the user has.
  36 + """
  37 + return RemainingInvitesNode.handle_token(parser, token)
  38 +
  39 +
  40 +@register.inclusion_tag("invitations/_invite_form.html")
  41 +def invite_form(user):
  42 + return {"form": InviteForm(), "user": user}
  43 +
  44 +
  45 +@register.inclusion_tag("invitations/_invited.html")
  46 +def invites_sent(user):
  47 + return {"invited_list": user.invites_sent.all()}
  48 +
  49 +
  50 +@register.filter
  51 +def status_class(invite):
  52 + if invite.status == invite.STATUS_SENT:
  53 + return "sent"
  54 + elif invite.status == invite.STATUS_ACCEPTED:
  55 + return "accepted"
  56 + elif invite.status == invite.STATUS_JOINED:
  57 + return "joined"
  58 + return ""
8 invitations/urls.py
... ... @@ -0,0 +1,8 @@
  1 +from django.conf.urls.defaults import url, patterns
  2 +
  3 +from invitations.views import invite
  4 +
  5 +
  6 +urlpatterns = patterns("",
  7 + url(r"^invite/$", invite, name="invitations_invite"),
  8 +)
30 invitations/views.py
... ... @@ -0,0 +1,30 @@
  1 +from django import http
  2 +from django.utils import simplejson as json
  3 +from django.views.decorators.http import require_http_methods
  4 +
  5 +from django.contrib.auth.decorators import login_required
  6 +
  7 +from emailconfirmation.models import EmailAddress
  8 +
  9 +from invitations.forms import InviteForm
  10 +from invitations.models import JoinInvitation
  11 +
  12 +
  13 +@login_required
  14 +@require_http_methods(["POST"])
  15 +def invite(request):
  16 + form = InviteForm(request.POST)
  17 + if form.is_valid():
  18 + email = form.cleaned_data["email_address"]
  19 + if EmailAddress.objects.filter(email=email, verified=True).exists():
  20 + data = {"status": "ERROR", "errors": "A user with %s as their email address already exists." % email}
  21 + else:
  22 + JoinInvitation.invite(request.user, email)
  23 + data = {
  24 + "status": "OK",
  25 + "email": email,
  26 + "invitations_remaining": request.user.invitationstat.invites_remaining()
  27 + }
  28 + else:
  29 + data = {"status": "ERROR", "errors": form.errors}
  30 + return http.HttpResponse(json.dumps(data), content_type="application/json")
13 setup.py
... ... @@ -0,0 +1,13 @@
  1 +from setuptools import setup, find_packages
  2 +
  3 +
  4 +setup(
  5 + name = "invitations",
  6 + version = "0.1.dev1",
  7 + description = "an invitations app",
  8 + url = "https://github.com/eldarion/django-invitations",
  9 + author = "Eldarion",
  10 + author_email = "opensource@eldarion.com",
  11 + packages = find_packages(),
  12 + zip_safe = False
  13 +)

0 comments on commit 7a56b79

Please sign in to comment.
Something went wrong with that request. Please try again.