コード例 #1
0
class Card(models.Model):
    customer = BigForeignKey(Customer,
                             related_name='cards',
                             on_delete=models.CASCADE,
                             null=True)
    number = EncryptedCharField(max_length=255)
    last4_number = models.IntegerField(blank=True)
    expiration_year = EncryptedIntegerField(max_length=255)
    expiration_month = EncryptedIntegerField(max_length=255)

    cvv = EncryptedCharField(max_length=255)

    created = models.DateTimeField("Created at", auto_now_add=True)
    updated = models.DateTimeField(verbose_name="Updated at", auto_now=True)

    class Meta:
        ordering = ('number', )

    def __unicode__(self):
        return self.masked_number

    @property
    def masked_number(self):
        return u'XXXX%s' % self.last4_number

    def set_last4_number(self):
        self.last4_number = self.number[-4:]
        return self.last4_number
コード例 #2
0
class SMTPAccount(Account):
    """ An account for sending emails via SMTP. """
    smtp_host = EncryptedCharField(max_length=100, null=True)
    smtp_port = models.IntegerField(null=True)
    smtp_username = EncryptedCharField(max_length=100, null=True)
    smtp_password = EncryptedCharField(max_length=100, null=True)

    def send_email(self, user, contact, subject, html, text):
        msg = MIMEMultipart("alternative")
        msg['Subject'] = subject
        msg['From'] = self.email_address
        msg['To'] = contact.email

        part1 = MIMEText(text, "plain")
        part2 = MIMEText(html, "html")

        msg.attach(part1)
        msg.attach(part2)

        s = smtplib.SMTP("%s:%s" % (self.smtp_host, self.smtp_port))
        if self.smtp_username:
            s.login(self.smtp_username, self.smtp_password)

        s.sendmail(self.email_address, contact.email, msg.as_string())
        s.quit()
コード例 #3
0
class TextCaptchaToken(CaptchaToken):
    """docstring for TextCaptcha."""

    result = EncryptedCharField(max_length=256)

    def create(self, file_name, file_data, resolved, result='', unsolvable=False):
        super(TextCaptchaToken, self).create(file_name, file_data, resolved)
        self.result = result
        self.captcha_type = "text"
        return self

    # solves token if 3 matching proposals were made
    # marks token as unsolveable if more than 6 resolutions didn't result in
    # solved token
    def try_solve(self):
        # checks if there is a solution for a token based on saved proposals
        proposals = self.proposals
        most_common = proposals.most_common()
        num_proposoals = sum(proposals.values())

        if len(proposals.values()) >= 6:  # more than six different proposals
            self.unsolvable = True
            self.resolved = True
            self.save()
        elif num_proposoals >= 3:
            if most_common[0][1] >= 3:
                self.resolved = True
                self.result = most_common[0][0]
                self.save()
コード例 #4
0
ファイル: models.py プロジェクト: zoeylee/empl
class Contract(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    #user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    wage = EncryptedCharField(max_length=200)
    start = models.DateField(auto_now=False, auto_now_add=False)
    end = models.DateField(auto_now=False, auto_now_add=False)
    work_permit_no = EncryptedCharField(max_length=200)
    visa_no = EncryptedCharField(max_length=200)
    visa_expire = EncryptedDateTimeField()
    created_at = models.DateTimeField(default=timezone.now)
    updated_at = models.DateTimeField(blank=True, null=True)

    def update(self):
        self.updated_at = timezone.now()
        self.save()

    def __unicode__(self):
        return self.user.username + " / " + self.wage + " / " + self.work_permit_no + " / " + self.visa_no + " / " + self.visa_expire.strftime(
            '%Y-%m-%d')
コード例 #5
0
class DataConnection(AuditableTable):
    """How to connect to a datasource.  This uses sqlalchemy behind the scenes.
    See http://docs.sqlalchemy.org/en/rel_0_9/core/engines.html for details"""
    class Meta:
        verbose_name_plural = "Data Connections"

    drivername = models.CharField(
        max_length=100,
        help_text="The name of the database backend. This name will correspond "
        "to a module in sqlalchemy/databases or a third party plug-in. Examples: mysql, sqlite"
    )
    dialect = models.CharField(
        max_length=100,
        blank=True,
        help_text="Optionally used for certain drivers.  For example, use "
        "'oursql' for OurSQL")
    username = models.CharField(max_length=300, blank=True)
    password = EncryptedCharField(
        passphrase_setting='SECRET_KEY',
        max_length=300,
        help_text=
        "WARNING: Assume this is not safe! It is encrypted using unvetted code I "
        "found on the internet.  It will be encrypted when stored in "
        "the database BUT the key is stored on your sever. "
        " It will also be available to any users using the admin interface and "
        "is sent to the DB server and to admin users in clear text.",
        blank=True)
    host = models.CharField(max_length=300,
                            help_text="The name of the host",
                            blank=True)
    port = models.IntegerField(help_text="The port number",
                               null=True,
                               blank=True)
    database = models.CharField(max_length=300, help_text="The database name")

    def get_db_connection(self):
        url = sqlalchemy.engine.url.URL(drivername=self.drivername,
                                        username=self.username or None,
                                        password=self.password or None,
                                        host=self.host or None,
                                        port=self.port or None,
                                        database=self.database)
        #Sqlalchemy doesn't seem to let us specify dialect in URL, I guess we have to hack it in??
        s_url = str(url)
        if self.dialect:
            drivername, the_rest = str(url).split('://')
            s_url = drivername + '+' + self.dialect + '://' + the_rest
        engine = sqlalchemy.create_engine(s_url)
        return engine.connect()

    def __unicode__(self):
        return "%s@%s/%s (%s)" % (self.username, self.host, self.database,
                                  self.drivername)
コード例 #6
0
class RippleWalletCredentials(SingletonModel):
    address = models.CharField(
        max_length=35,
        validators=[ripple_address_validator],
        verbose_name='Address',
    )
    secret = EncryptedCharField(max_length=29, verbose_name='Secret key')

    def __str__(self):
        return 'Ripple Wallet Credentials'

    class Meta:
        verbose_name = 'Ripple Wallet Credentials'
コード例 #7
0
class UserCredential(PlCoreBase):
    user = models.ForeignKey(
        User,
        related_name='usercredentials',
        help_text="The User this credential is associated with")

    name = models.SlugField(help_text="The credential type, e.g. ec2",
                            max_length=128)
    key_id = models.CharField(help_text="The backend id of this credential",
                              max_length=1024)
    enc_value = EncryptedCharField(
        help_text="The key value of this credential", max_length=1024)

    def __unicode__(self):
        return self.name
コード例 #8
0
ファイル: credential.py プロジェクト: shamoya/xos
class SliceCredential(PlCoreBase):
    slice = models.ForeignKey(
        Slice,
        related_name='slicecredentials',
        help_text="The User this credential is associated with")

    name = models.SlugField(help_text="The credential type, e.g. ec2",
                            max_length=128)
    key_id = StrippedCharField(help_text="The backend id of this credential",
                               max_length=1024)
    enc_value = EncryptedCharField(
        help_text="The key value of this credential", max_length=1024)

    xos_links = [ModelLink(Slice, via='slice')]

    def __unicode__(self):
        return self.name
コード例 #9
0
class ControllerCredential(PlCoreBase):
    objects = ControllerLinkManager()
    deleted_objects = ControllerLinkDeletionManager()
    controller = models.ForeignKey(
        Controller,
        related_name='controllercredentials',
        help_text="The User this credential is associated with")

    name = models.SlugField(help_text="The credential type, e.g. ec2",
                            max_length=128)
    key_id = models.CharField(help_text="The backend id of this credential",
                              max_length=1024)
    enc_value = EncryptedCharField(
        help_text="The key value of this credential", max_length=1024)

    def __unicode__(self):
        return self.name
コード例 #10
0
class Dot(models.Model):
    note = models.ForeignKey(Note, related_name='dots')
    diagram = models.ForeignKey(Diagram, related_name='diagrams')
    marker = models.ForeignKey(Marker, related_name='markers')
    position_x = models.PositiveSmallIntegerField()
    position_y = models.PositiveSmallIntegerField()
    text = EncryptedCharField(default='', max_length=1024)

    def __unicode__(self):
        return ('Note - %s / Diagram - %s / Marker - %s - %s' %
                (str(self.note), self.diagram, self.marker, self.text))

    def save(self, *args, **kwargs):
        if self.note.is_finalized:
            raise NoteIsAlreadyFinalizedError()
        super(Dot, self).save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        if self.note.is_finalized:
            raise NoteIsAlreadyFinalizedError()
        super(Dot, self).delete(*args, **kwargs)
コード例 #11
0
class Db(models.Model):
    name_short = models.CharField(unique=True, max_length=10)
    name_long = models.CharField(unique=True, max_length=128)
    type = models.CharField(max_length=10,
                            choices=(('MySQL', 'MySQL'),
                                     ('Postgres', 'Postgres'), ('Hive2',
                                                                'Hive2')),
                            default='None')
    host = models.CharField(max_length=1024)
    db = models.CharField(max_length=1024)
    port = models.IntegerField()
    username = models.CharField(max_length=128)
    password_encrypted = EncryptedCharField(max_length=1024,
                                            )  # TODO FIX SPELLING MISTAKE
    create_time = models.DateTimeField(auto_now_add=True, editable=False)
    modified_time = models.DateTimeField(auto_now=True, editable=False)
    tags = TaggableManager(blank=True)

    def __str__(self):
        return self.name_short

    def clean(self):
        return True  # TODO Validate database connection
コード例 #12
0
class Note(models.Model):
    CREATED_DATE_FORMAT = '%m/%d/%Y'

    healer = models.ForeignKey('healers.Healer', related_name='notes')
    client = models.ForeignKey('clients.Client')
    created_date = models.DateTimeField(auto_now_add=True)
    finalized_date = models.DateTimeField(null=True, blank=True)
    subjective = EncryptedCharField(default='', max_length=1024)
    objective = EncryptedCharField(default='', max_length=1024)
    assessment = EncryptedCharField(default='', max_length=1024)
    plan = EncryptedCharField(default='', max_length=1024)
    condition = models.PositiveSmallIntegerField(null=True,
                                                 blank=True)  # 0 - 10

    class Meta:
        ordering = ['-pk']

    def __unicode__(self):
        return 'Healer: %s / Client: %s / Created: %s' % (
            self.healer, self.client, self.created_date)

    @property
    def is_finalized(self):
        return self.finalized_date is not None

    @property
    def created_date_formated(self):
        def get_format():
            format = '%m/%d'
            if datetime.now() - self.created_date >= timedelta(days=365):
                format += '/%y'
            return format

        return self.created_date.strftime(get_format())

    def as_dict(self):
        return {
            'client_name':
            str(self.client),
            'created_date':
            self.created_date_formated,
            'created_date_full':
            self.created_date.strftime(Note.CREATED_DATE_FORMAT),
            'subjective':
            self.subjective,
            'objective':
            self.objective,
            'assessment':
            self.assessment,
            'condition':
            self.condition,
            'plan':
            self.plan,
            'is_finalized':
            self.is_finalized,
        }

    def get_note_prev_next_ids(self):
        def get_note_id(type):
            if type == 'prev':
                notes_filtered = notes.filter(pk__lt=self.pk)
            elif type == 'next':
                notes_filtered = notes.filter(pk__gt=self.pk).order_by('pk')
            if notes_filtered.exists():
                return notes_filtered[0].pk

        notes = Note.objects.filter(healer=self.healer, client=self.client)
        notes_prev = get_note_id('prev')
        notes_next = get_note_id('next')
        return notes_prev, notes_next

    def get_dots(self):
        return {
            dot.pk: {
                'diagram_id': dot.diagram.pk,
                'marker_id': dot.marker.pk,
                'position': [dot.position_x, dot.position_y],
                'text': dot.text,
            }
            for dot in self.dots.all()
        }

    def duplicate(self):
        def duplicate_note():
            note = self
            note.pk = None
            note.created_date = None
            note.finalized_date = None
            note.save()
            return note

        def duplicate_dots():
            for dot in original_dots:
                dot.pk = None
                dot.note = note
                dot.save()

        original_dots = self.dots.all()
        note = duplicate_note()
        duplicate_dots()
        return note.pk

    def finalize(self):
        if self.is_finalized:
            raise NoteIsAlreadyFinalizedError()
        else:
            self.finalized_date = datetime.now()
            self.save(True)

    def save(self, force=False, *args, **kwargs):
        if (self.pk is None and not self.healer.user.has_perm(
                healers.models.Healer.NOTES_PERMISSION)
                and (Note.objects.filter(healer=self.healer).count() >=
                     settings.NUMBER_OF_FREE_NOTES)):
            raise NoNotesPermissionError()

        if not force and self.is_finalized:
            raise NoteIsAlreadyFinalizedError()
        super(Note, self).save(*args, **kwargs)

    def delete(self, *args, **kwargs):
        if self.is_finalized:
            raise NoteIsAlreadyFinalizedError()
        super(Note, self).delete(*args, **kwargs)
コード例 #13
0
class Integration(Entity):
    DEFAULT_INTEGRATION_NAME = 'Default'

    ACCESS_KEY_LENGTH = 32
    SECRET_KEY_LENGTH = 48

    tenant = models.ForeignKey(Tenant,
                               related_name='integrations',
                               on_delete=models.CASCADE)
    policy = models.OneToOneField(Policy,
                                  related_name='integration',
                                  on_delete=models.CASCADE)
    access_key = models.CharField(max_length=128, unique=True)
    secret_key = EncryptedCharField(max_length=128, unique=True)
    endpoint = models.URLField(blank=True)
    notes = models.TextField(blank=True, null=True)
    uid = models.CharField(max_length=16, blank=True, null=True)

    def save(self, *args, **kwargs):
        if not self.uid:
            self.uid = uuid.uuid4().get_hex()[:8]
        super(Integration, self).save(*args, **kwargs)

    @staticmethod
    def create(tenant, name, notes):
        return Integration.objects.create(
            tenant=tenant,
            name=name,
            notes=notes,
            policy=Policy.objects.create(name=name),
            access_key=get_random_string(Integration.ACCESS_KEY_LENGTH),
            secret_key=get_random_string(Integration.SECRET_KEY_LENGTH),
        )

    def generate_auth_session_token(self, username, expires_at):
        return ''

    def generate_auth_session_portal_url(self, kind, username, expires_at):
        return self.endpoint + '/' + kind + '/?token=' + self.generate_auth_session_token(
            username, expires_at)

    def enroll(self, username):
        client = Client.objects.filter(integration=self,
                                       username=username).first()
        if client:
            return None, errors.MFAError('username `{0}` already exists',
                                         username)

        Enrollment = apps.get_model('enrollment', 'Enrollment')

        expiration_mins = self.policy.get_configuration(
            Configuration.KIND_ENROLLMENT_EXPIRATION_IN_MINUTES
        ) or Enrollment.DEFAULT_EXPIRATION_IN_MINUTES
        expires_at = timezone.now() + timedelta(minutes=expiration_mins)

        entity = Enrollment.objects.create(
            integration=self,
            policy=self.policy,
            username=username,
            binding_context=None,
            expires_at=expires_at,
            portal_url=self.generate_auth_session_portal_url(
                'enrollment', username, expires_at))

        return entity, None

    def challenge(self, client, data):
        assert client.integration == self

        Challenge = apps.get_model('challenge', 'Challenge')

        device = None
        if data['device_pk']:
            device = client.devices.filter(pk=data['device_pk']).first()
            if not device:
                return None, errors.MFAInconsistentStateError(
                    'device `{0}` does not exist for client `{1}`'.format(
                        data['device_pk'], client.username))

        # obtain the challenge expiration in minute
        expiration_mins = self.policy.get_configuration(
            Configuration.KIND_CHALLENGE_EXPIRATION_IN_MINUTES
        ) or Challenge.DEFAULT_EXPIRATION_IN_MINUTES

        expires_at = timezone.now() + timedelta(minutes=expiration_mins)

        # create the challenge entity
        entity = Challenge.objects.create(
            client=client,
            device=device,
            policy=self.policy,
            reference=data['reference'] if 'reference' in data else '',
            expires_at=expires_at,
            portal_url=self.generate_auth_session_portal_url(
                'challenge', client.username, expires_at))

        return entity, None
コード例 #14
0
class Remote(TimeStampedModel):
    name = models.CharField(max_length=100)
    key = models.SlugField(max_length=120, unique=True)
    developer = models.ForeignKey('users.User', related_name="developer")
    endpoint = models.URLField(null=True, blank=True)
    configuration = models.TextField(default=default_config_json())
    secret = EncryptedCharField(max_length=36)
    users = models.ManyToManyField('users.User')

    allow_all_origins = models.BooleanField(
        default=False, verbose_name="Development mode enabled")

    def delete(self):
        self.delete_cache()
        return super(Remote, self).delete()

    def save(self, *args, **kwargs):
        if not self.pk:
            self.secret = uuid.uuid4()

        # Sync allow all origins with redis.
        conn = get_redis_connection('default')
        conn.set(self.get_allow_all_key(), int(self.allow_all_origins))

        self.sync_values()

        # Save the object - we're done.
        return super(Remote, self).save(*args, **kwargs)

    def sync_values(self):
        # Makes sure the values are properly synced up on cache.
        configuration_json = json.loads(self.configuration)
        fields = configuration_json["fields"]

        # Get cached values and types
        values = self.get_values()
        types = self.get_types()

        for key, field in fields.iteritems():
            # If the field is not an action
            if field["type"] != "action":
                # Get the default value and the type as per the JSON conf.
                default_value = field["default"]
                field_type = field["type"]

                # Get the type we have cached, in case the types differ.
                cached_type = types.get(key)

                if cached_type != field_type:
                    # If they do differ, set the value to the default value and update the type
                    values[key] = default_value
                    types[key] = field_type
                else:
                    # If they don't, just add the value if the key was not populated.
                    cached_value = values.get(key, default_value)
                    values[key] = cached_value

        removed_keys = set(values.keys()) - set(fields.keys())

        for key in removed_keys:
            values.pop(key)

        self.save_values(values)
        self.save_types(types)

    def emit_socket_event(self, event_type, action_name=None):
        socket_emit_url = 'http://localhost:9000/private/io/emit/'
        emit_password = os.environ['SUPREMOTE_SOCKET_KEY']

        request_body = {
            'event_type': event_type,
            'room': self.key,
            'message': self.get_values(),
            'password': emit_password
        }

        if action_name:
            request_body.update({'action_name': action_name})

        try:
            grequests.post(socket_emit_url,
                           data=json.dumps(request_body),
                           headers={
                               'Content-Type': 'application/json'
                           }).send()
        except Exception:
            pass

    def update_endpoint(self, user_email):
        self.emit_socket_event('update')

        if self.endpoint:
            values = self.get_values()

            request_body = {
                'user': user_email,
                'remote_key': self.key,
                'data': values,
                'updated': str(timezone.now()),
            }

            transaction_id = uuid.uuid4()
            signature = get_body_signature(request_body, self.secret,
                                           transaction_id)

            r = grequests.post(self.endpoint,
                               data=json.dumps(request_body),
                               headers=get_headers(signature,
                                                   transaction_id)).send()

    def trigger_action(self, action_name, user_email):
        configuration = self.get_configuration()
        action_dictionary = configuration['fields'].get(action_name)

        if action_dictionary:
            endpoint = action_dictionary.get(
                'endpoint') or self.endpoint or None
            throttle = action_dictionary['throttle']
            throttle_threshold = timezone.now() - datetime.timedelta(
                seconds=throttle)

            # Look for latest throttle entry for this action.
            throttle_entry_count = ActionThrottle.objects.filter(
                remote=self,
                key=action_name,
                created__gt=throttle_threshold,
                status_code=200,
            ).count()

            if throttle_entry_count == 0:
                self.emit_socket_event('action', action_name=action_name)
                if endpoint:
                    # Action must be triggered.
                    request_body = {
                        'user': user_email,
                        'remote_key': self.key,
                        'data': {
                            'action_id': action_name,
                            'triggered': str(timezone.now()),
                        }
                    }

                    transaction_id = uuid.uuid4()
                    signature = get_body_signature(request_body, self.secret,
                                                   transaction_id)
                    headers = get_headers(signature, transaction_id)

                    grequests.post(endpoint,
                                   data=json.dumps(request_body),
                                   headers=headers).send()

                # FIXME: Put actual response status code.
                ActionThrottle(remote=self, key=action_name,
                               status_code=200).save()

                return True
            else:
                # Action is throttled and must not be carried on.
                return False
        else:
            raise KeyError("%s is not an action defined by this remote." %
                           action_name)

    def get_configuration(self):
        return json.loads(self.configuration)

    def delete_cache(self):
        conn = get_redis_connection('default')
        cache = get_cache('default')
        cache.delete_pattern(self.get_cache_key())
        cache.delete_pattern(self.get_type_cache_key())
        cache.delete_pattern(self.get_domains_key())
        conn.delete(self.get_allow_all_key())

    def get_types(self):
        cache = get_cache('default')
        key = self.get_type_cache_key()
        return cache.get(key, {})

    def save_types(self, types):
        cache = get_cache('default')
        key = self.get_type_cache_key()
        cache.set(key, types, None)

    def get_values(self):
        conn = get_redis_connection('default')
        key = self.get_cache_key()
        return json.loads(conn.get(key) or '{}')

    def save_values(self, values):
        conn = get_redis_connection('default')
        key = self.get_cache_key()
        conn.set(key, json.dumps(values))

    def save_default(self, key, field):
        # Saves the default value for this key if the key does not have an associated value.
        values = self.get_values()
        default_value = field["default"]
        cached_value = values.get(key, default_value)
        values[key] = cached_value
        self.save_values(values)

    def get_cache_key(self):
        return '{}.values'.format(self.key)

    def get_type_cache_key(self):
        return '{}.types'.format(self.key)

    def get_domains_key(self):
        return '{}.domains'.format(self.key)

    def get_allow_all_key(self):
        return '{}.allow_all_origins'.format(self.key)

    def get_absolute_url(self):
        return reverse('core:remote_detail',
                       kwargs={
                           'remote_id': self.pk,
                           'remote_key': self.key,
                       })

    def clean(self):
        #Load the JSON schema
        schema_file = open(path_to_schema())
        schema = self.validate_schema(schema_file)
        configuration_json = self.validate_configuration(
            schema, self.configuration)
        schema_file.close()
        return super(Remote, self).clean()

    def validate_schema(self, schema_file):
        try:
            schema = json.load(schema_file)
        except:
            raise ValidationError("Schema appears to be invalid.")
        return schema

    def validate_configuration(self, schema, configuration):

        validator = Draft4Validator(schema)
        try:
            configuration_json = json.loads(self.configuration)
        except:
            raise ValidationError(
                "The given configuration file is not valid JSON.")

        error = best_match(validator.iter_errors(configuration_json))

        if error:
            raise ValidationError(error.message)

        for key, field in configuration_json["fields"].iteritems():
            self.validate_field(key, field)

        index = 0
        fieldset_entries = []

        for fieldset in configuration_json["fieldsets"]:
            for field in fieldset["fields"]:
                if field not in fieldset_entries:
                    fieldset_entries.append(field)
                else:
                    raise ValidationError(
                        "Fieldset at index {} repeats field key '{}'.".format(
                            index, field))

                if field not in configuration_json["fields"]:
                    raise ValidationError(
                        "The key '{}'' is not defined as a field in fieldset at index {}"
                        .format(field, index))

            index += 1

        return configuration_json

    def validate_field(self, key, field):

        acceptable_action_classes = [
            'danger', 'primary', 'default', 'warning', 'success', 'info'
        ]

        if field["type"] == "text":
            if field["maxLength"] <= 0:
                raise ValidationError(
                    "{}: maxLength must be greater than 0.".format(key))
            if len(field["default"]) > field["maxLength"]:
                raise ValidationError(
                    "{}: default value is longer than maxLength".format(key))

        elif field["type"] == "number":
            f_range = field["range"]
            if f_range[0] >= f_range[1]:
                raise ValidationError(
                    "{}: range must be [lower, upper]".format(key))

            if field["default"] not in range(f_range[0], f_range[1] + 1):
                raise ValidationError(
                    "{}: default value is not in range {}".format(
                        key, f_range))

        elif field["type"] == "multiple":
            if field["default"] not in field["choices"]:
                raise ValidationError("{}: default value is not in {}".format(
                    key, field["choices"]))

        elif field["type"] == "action":
            endpoint = field.get("endpoint")

            if endpoint:
                validator = URLValidator()
                validator.schemes = ["http", "https"]
                validator(field["endpoint"])

            if not field["class"] in acceptable_action_classes:
                raise ValidationError("{}: class must be one of {}".format(
                    key, acceptable_action_classes))

    def __unicode__(self):
        return self.name

    def get_pending_invitations(self):
        return self.invitation_set.filter(remote=self,
                                          accepted=False,
                                          declined=False)