Last active
January 2, 2016 23:59
-
-
Save jacobg/8380205 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from django.conf import settings | |
from django.core.exceptions import ImproperlyConfigured | |
from django.db import connection, models | |
from django.db.models import permalink, get_model | |
from django.utils.translation import ugettext_lazy as _ | |
from django_extensions.db.fields import AutoSlugField | |
from django_extensions.db.models import TimeStampedModel | |
from organizations.managers import OrgManager, ActiveOrgManager | |
USER_MODEL = getattr(settings, 'AUTH_USER_MODEL', 'auth.User') | |
ORGANIZATION_MODEL = getattr(settings, 'ORGANIZATION_MODEL', 'organizations.Organization') | |
ORGANIZATION_USER_MODEL = getattr(settings, 'ORGANIZATION_USER_MODEL', 'organizations.OrganizationUser') | |
def get_user_model(): | |
""" | |
Returns the chosen user model as a class. This functionality won't be | |
built-in until Django 1.5. | |
""" | |
try: | |
klass = get_model(USER_MODEL.split('.')[0], USER_MODEL.split('.')[1]) | |
except: | |
raise ImproperlyConfigured("Your user class, {0}, is improperly defined".format(klass_string)) | |
return klass | |
def get_organization_model(): | |
""" | |
Returns the chosen model as a class. | |
""" | |
try: | |
klass = get_model(ORGANIZATION_MODEL.split('.')[0], ORGANIZATION_MODEL.split('.')[1]) | |
except: | |
raise ImproperlyConfigured("Your organization class, {0}, is improperly defined".format(klass_string)) | |
return klass | |
def get_organization_user_model(): | |
""" | |
Returns the chosen model as a class. | |
""" | |
try: | |
klass = get_model(ORGANIZATION_USER_MODEL.split('.')[0], ORGANIZATION_USER_MODEL.split('.')[1]) | |
except: | |
raise ImproperlyConfigured("Your organization user class, {0}, is improperly defined".format(klass_string)) | |
return klass | |
if connection.features.supports_joins: | |
# this is the case for typical relational sql db | |
class AbstractOrganizationBase(TimeStampedModel): | |
users = models.ManyToManyField(USER_MODEL, through="OrganizationUser") | |
class Meta: | |
abstract = True | |
else: | |
# this is typically the NoSQL Django-nonrel case (e.g., mongodb or gae) | |
AbstractOrganizationBase = TimeStampedModel | |
class AbstractOrganization(AbstractOrganizationBase): | |
""" | |
The umbrella object with which users can be associated. | |
An organization can have multiple users but only one who can be designated | |
the owner user. | |
""" | |
name = models.CharField(max_length=200, | |
help_text=_("The name of the organization")) | |
slug = AutoSlugField(max_length=200, blank=False, editable=True, | |
populate_from='name', unique=True, | |
help_text=_("The name in all lowercase, suitable for URL identification")) | |
is_active = models.BooleanField(default=True) | |
objects = OrgManager() | |
active = ActiveOrgManager() | |
class Meta: | |
abstract = True | |
ordering = ['name'] | |
verbose_name = _("organization") | |
verbose_name_plural = _("organizations") | |
def __unicode__(self): | |
return self.name | |
@permalink | |
def get_absolute_url(self): | |
return ('organization_detail', (), {'organization_pk': self.pk}) | |
def add_user(self, user, is_admin=False): | |
""" | |
Adds a new user and if the first user makes the user an admin and | |
the owner. | |
""" | |
users_count = self.organization_users.all().count() | |
if users_count == 0: | |
is_admin = True | |
org_user = ORGANIZATION_USER_MODEL.objects.create(user=user, | |
organization=self, is_admin=is_admin) | |
if users_count == 0: | |
OrganizationOwner.objects.create(organization=self, | |
organization_user=org_user) | |
return org_user | |
def get_or_add_user(self, user, is_admin=False): | |
""" | |
Adds a new user to the organization, and if it's the first user makes | |
the user an admin and the owner. Uses the `get_or_create` method to | |
create or return the existing user. | |
`user` should be a user instance, e.g. `auth.User`. | |
Returns the same tuple as the `get_or_create` method, the | |
`OrganizationUser` and a boolean value indicating whether the | |
OrganizationUser was created or not. | |
""" | |
users_count = self.organization_users.all().count() | |
if users_count == 0: | |
is_admin = True | |
org_user, created = ORGANIZATION_USER_MODEL.objects.get_or_create( | |
organization=self, user=user, defaults={'is_admin': is_admin}) | |
if users_count == 0: | |
OrganizationOwner.objects.create(organization=self, | |
organization_user=org_user) | |
return org_user, created | |
def is_member(self, user): | |
return next((ou for ou in self.organization_users.all() if ou.user_id == user.id), False) and True | |
def is_admin(self, user): | |
return True if self.organization_users.filter(user=user, is_admin=True) else False | |
class Organization(AbstractOrganization): | |
pass | |
class AbstractOrganizationUser(TimeStampedModel): | |
""" | |
ManyToMany through field relating Users to Organizations. | |
It is possible for a User to be a member of multiple organizations, so this | |
class relates the OrganizationUser to the User model using a ForeignKey | |
relationship, rather than a OneToOne relationship. | |
Authentication and general user information is handled by the User class | |
and the contrib.auth application. | |
""" | |
user = models.ForeignKey(USER_MODEL, related_name="organization_users") | |
organization = models.ForeignKey(ORGANIZATION_MODEL, | |
related_name="organization_users") | |
is_admin = models.BooleanField(default=False) | |
class Meta: | |
abstract = True | |
ordering = ['organization', 'user'] | |
unique_together = ('user', 'organization') | |
verbose_name = _("organization user") | |
verbose_name_plural = _("organization users") | |
def __unicode__(self): | |
return u"{0} ({1})".format(self.name if self.user.is_active else | |
self.user.email, self.organization.name) | |
def delete(self, using=None): | |
""" | |
If the organization user is also the owner, this should not be deleted | |
unless it's part of a cascade from the Organization. | |
If there is no owner then the deletion should proceed. | |
""" | |
from organizations.exceptions import OwnershipRequired | |
try: | |
if self.organization.owner.organization_user.id == self.id: | |
raise OwnershipRequired(_("Cannot delete organization owner before organization or transferring ownership.")) | |
except OrganizationOwner.DoesNotExist: | |
pass | |
super(OrganizationUser, self).delete(using=using) | |
@permalink | |
def get_absolute_url(self): | |
return ('organization_user_detail', (), | |
{'organization_pk': self.organization.pk, 'user_pk': self.user.pk}) | |
@property | |
def name(self): | |
if hasattr(self.user, 'get_full_name'): | |
return self.user.get_full_name() | |
return "{0}".format(self.user) | |
class OrganizationUser(AbstractOrganizationUser): | |
# swappable is necessary to prevent model validation errors | |
# coming from the ForeignKey fields' reverse relations, which | |
# would be duplicate by both this class as well as the custom class. | |
class Meta: | |
swappable = 'ORGANIZATION_USER_MODEL' | |
class OrganizationOwner(TimeStampedModel): | |
"""Each organization must have one and only one organization owner.""" | |
organization = models.OneToOneField(ORGANIZATION_MODEL, related_name="owner") | |
organization_user = models.OneToOneField(ORGANIZATION_USER_MODEL, | |
related_name="owned_organization") | |
class Meta: | |
verbose_name = _("organization owner") | |
verbose_name_plural = _("organization owners") | |
def __unicode__(self): | |
return u"{0}: {1}".format(self.organization, self.organization_user) | |
def save(self, *args, **kwargs): | |
""" | |
Extends the default save method by verifying that the chosen | |
organization user is associated with the organization. | |
""" | |
from organizations.exceptions import OrganizationMismatch | |
if self.organization_user.organization != self.organization: | |
raise OrganizationMismatch | |
else: | |
super(OrganizationOwner, self).save(*args, **kwargs) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment