Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

initial commit

  • Loading branch information...
commit 7a56b7987a1d9e18b4b92a050862b019fa57a92c 0 parents
@paltman paltman authored
0  invitations/__init__.py
No changes.
13 invitations/admin.py
@@ -0,0 +1,13 @@
+from django.contrib import admin
+
+from invitations.models import JoinInvitation, InvitationStat
+
+
+class InvitationStatAdmin(admin.ModelAdmin):
+ readonly_fields = ["user", "invites_sent", "invites_accepted"]
+ list_display = ["user", "invites_sent", "invites_accepted", "invites_allocated", "invites_remaining", "can_send"]
+ list_filter = ["invites_sent", "invites_accepted"]
+
+
+admin.site.register(JoinInvitation)
+admin.site.register(InvitationStat, InvitationStatAdmin)
6 invitations/forms.py
@@ -0,0 +1,6 @@
+from django import forms
+
+
+class InviteForm(forms.Form):
+
+ email_address = forms.EmailField()
102 invitations/models.py
@@ -0,0 +1,102 @@
+import datetime
+
+from django.db import models
+from django.db.models.signals import post_save
+from django.conf import settings
+
+from django.contrib.auth.models import User
+
+from pinax.apps.signup_codes.models import SignupCode, SignupCodeResult
+from pinax.apps.signup_codes.signals import signup_code_used
+
+from invitations.signals import invite_sent, invite_accepted
+
+
+DEFAULT_INVITE_ALLOCATION = getattr(settings, "INVITATIONS_DEFAULT_ALLOCATION", 0)
+DEFAULT_INVITE_EXPIRATION = getattr(settings, "INVITATIONS_DEFAULT_EXPIRATION", 168) # 168 Hours = 7 Days
+
+
+class NotEnoughInvitationsError(Exception):
+ pass
+
+
+class JoinInvitation(models.Model):
+
+ STATUS_SENT = 1
+ STATUS_ACCEPTED = 2
+ STATUS_JOINED_INDEPENDENTLY = 3
+
+ INVITE_STATUS_CHOICES = [
+ (STATUS_SENT, "Sent"),
+ (STATUS_ACCEPTED, "Accepted"),
+ (STATUS_JOINED_INDEPENDENTLY, "Joined Independently")
+ ]
+
+ from_user = models.ForeignKey(User, related_name="invites_sent")
+ to_user = models.ForeignKey(User, null=True, related_name="invites_received")
+ message = models.TextField(null=True)
+ sent = models.DateTimeField(default=datetime.datetime.now)
+ status = models.IntegerField(choices=INVITE_STATUS_CHOICES)
+ signup_code = models.OneToOneField(SignupCode)
+
+ @classmethod
+ def invite(cls, from_user, to_email, message=None):
+ if not from_user.invitationstat.can_send():
+ raise NotEnoughInvitationsError()
+
+ signup_code = SignupCode.create(to_email, DEFAULT_INVITE_EXPIRATION)
+ signup_code.save()
+ join = cls.objects.create(
+ from_user=from_user,
+ message=message,
+ status=JoinInvitation.STATUS_SENT,
+ signup_code=signup_code
+ )
+ signup_code.send() # @@@ might want to implement our own method and just set the .send field on signup_code
+ stat = from_user.invitationstat
+ stat.invites_sent += 1
+ stat.save()
+ invite_sent.send(sender=cls, invitation=join)
+ return join
+
+
+class InvitationStat(models.Model):
+
+ user = models.OneToOneField(User)
+ invites_sent = models.IntegerField(default=0)
+ invites_allocated = models.IntegerField(default=DEFAULT_INVITE_ALLOCATION)
+ invites_accepted = models.IntegerField(default=0)
+
+ def invites_remaining(self):
+ return self.invites_allocated - self.invites_sent
+
+ def can_send(self):
+ return self.invites_allocated > self.invites_sent
+ can_send.boolean = True
+
+
+def process_used_signup_code(sender, **kwargs):
+ result = kwargs.get("signup_code_result")
+ try:
+ invite = result.signup_code.joininvitation
+ invite.to_user = result.user
+ invite.status = JoinInvitation.STATUS_ACCEPTED
+ invite.save()
+ stat = invite.from_user.invitationstat
+ stat.invites_accepted += 1
+ stat.save()
+ invite_accepted.send(sender=JoinInvitation, invitation=invite)
+ except JoinInvitation.DoesNotExist:
+ pass
+
+
+signup_code_used.connect(process_used_signup_code, sender=SignupCodeResult)
+
+
+def create_stat(sender, instance=None, **kwargs):
+ if instance is None:
+ return
+ InvitationStat.objects.get_or_create(user=instance)
+
+
+post_save.connect(create_stat, sender=User)
5 invitations/signals.py
@@ -0,0 +1,5 @@
+import django.dispatch
+
+
+invite_sent = django.dispatch.Signal(providing_args=["invitation",])
+invite_accepted = django.dispatch.Signal(providing_args=["invitation"])
12 invitations/templates/invitations/_invite_form.html
@@ -0,0 +1,12 @@
+{% load uni_form_tags %}
+<div class="invitation_form">
+ {% if user.invitationstat.can_send %}
+ <form action="{% url invitations_invite %}" method="POST" accept-charset="utf-8">
+ {% csrf_token %}
+ {{ form|as_uni_form }}
+ <p><input type="submit" value="Invite"></p>
+ </form>
+ {% else %}
+ <p>You do not have any invitiations currrently.</p>
+ {% endif %}
+</div>
19 invitations/templates/invitations/_invited.html
@@ -0,0 +1,19 @@
+{% load invitations_tags %}
+
+{% if invited_list %}
+ <ul>
+ {% for invited in invited_list %}
+ <li class="{{ invited|status_class }}">
+ {% if invited.to_user %}
+ <a href="{{ invited.to_user.get_profile.get_absolute_url }}">
+ {{ invited.signup_code.email }}
+ </a>
+ {% else %}
+ {{ invited.signup_code.email }}
+ {% endif %}
+ </li>
+ {% endfor %}
+ </ul>
+{% else %}
+ <p>You have no sent any invites.</p>
+{% endif %}
0  invitations/templatetags/__init__.py
No changes.
58 invitations/templatetags/invitations_tags.py
@@ -0,0 +1,58 @@
+from django import template
+
+from invitations.forms import InviteForm
+
+
+register = template.Library()
+
+
+class RemainingInvitesNode(template.Node):
+
+ @classmethod
+ def handle_token(cls, parser, token):
+ bits = token.split_contents()
+ if len(bits) != 2:
+ raise template.TemplateSyntaxError
+
+ return cls(
+ user = parser.compile_filter(bits[1])
+ )
+
+ def __init__(self, user):
+ self.user = user
+
+ def render(self, context):
+ user = self.user.resolve(context)
+ return user.invitationstat.invites_remaining()
+
+
+@register.tag
+def remaining_invites(parser, token):
+ """
+ Usage::
+ {% remaining_invites user %}
+
+ Returns an integer that is the # of remaining invites the user has.
+ """
+ return RemainingInvitesNode.handle_token(parser, token)
+
+
+@register.inclusion_tag("invitations/_invite_form.html")
+def invite_form(user):
+ return {"form": InviteForm(), "user": user}
+
+
+@register.inclusion_tag("invitations/_invited.html")
+def invites_sent(user):
+ return {"invited_list": user.invites_sent.all()}
+
+
+@register.filter
+def status_class(invite):
+ if invite.status == invite.STATUS_SENT:
+ return "sent"
+ elif invite.status == invite.STATUS_ACCEPTED:
+ return "accepted"
+ elif invite.status == invite.STATUS_JOINED:
+ return "joined"
+ return ""
8 invitations/urls.py
@@ -0,0 +1,8 @@
+from django.conf.urls.defaults import url, patterns
+
+from invitations.views import invite
+
+
+urlpatterns = patterns("",
+ url(r"^invite/$", invite, name="invitations_invite"),
+)
30 invitations/views.py
@@ -0,0 +1,30 @@
+from django import http
+from django.utils import simplejson as json
+from django.views.decorators.http import require_http_methods
+
+from django.contrib.auth.decorators import login_required
+
+from emailconfirmation.models import EmailAddress
+
+from invitations.forms import InviteForm
+from invitations.models import JoinInvitation
+
+
+@login_required
+@require_http_methods(["POST"])
+def invite(request):
+ form = InviteForm(request.POST)
+ if form.is_valid():
+ email = form.cleaned_data["email_address"]
+ if EmailAddress.objects.filter(email=email, verified=True).exists():
+ data = {"status": "ERROR", "errors": "A user with %s as their email address already exists." % email}
+ else:
+ JoinInvitation.invite(request.user, email)
+ data = {
+ "status": "OK",
+ "email": email,
+ "invitations_remaining": request.user.invitationstat.invites_remaining()
+ }
+ else:
+ data = {"status": "ERROR", "errors": form.errors}
+ return http.HttpResponse(json.dumps(data), content_type="application/json")
13 setup.py
@@ -0,0 +1,13 @@
+from setuptools import setup, find_packages
+
+
+setup(
+ name = "invitations",
+ version = "0.1.dev1",
+ description = "an invitations app",
+ url = "https://github.com/eldarion/django-invitations",
+ author = "Eldarion",
+ author_email = "opensource@eldarion.com",
+ packages = find_packages(),
+ zip_safe = False
+)
Please sign in to comment.
Something went wrong with that request. Please try again.