Ejemplo n.º 1
0
class AccessGroup(Model):
    """
    An access group identifies a set of members with a defined set
    of permissions (and project access) for a Team.

    Groups may be automated through extensions (such as LDAP) so that
    membership is automatically maintained. If this is the case the
    ``managed`` attribute will be ``True``.
    """
    team = models.ForeignKey(Team)
    name = models.CharField(max_length=64)
    type = BoundedIntegerField(choices=MEMBER_TYPES, default=MEMBER_USER)
    managed = models.BooleanField(default=False)
    data = GzippedDictField(blank=True, null=True)
    date_added = models.DateTimeField(default=timezone.now)

    projects = models.ManyToManyField('sentry.Project')
    members = models.ManyToManyField(settings.AUTH_USER_MODEL)

    objects = BaseManager()

    class Meta:
        unique_together = (('team', 'name'), )

    __repr__ = sane_repr('team_id', 'name', 'type', 'managed')
Ejemplo n.º 2
0
class ProjectKey(Model):
    project = models.ForeignKey(Project, related_name='key_set')
    public_key = models.CharField(max_length=32, unique=True, null=True)
    secret_key = models.CharField(max_length=32, unique=True, null=True)
    user = models.ForeignKey(User, null=True)

    objects = BaseManager(cache_fields=(
        'public_key',
        'secret_key',
    ))

    def __unicode__(self):
        return u'project=%s, user=%s' % (self.project_id, self.user_id)

    @classmethod
    def generate_api_key(cls):
        return uuid.uuid4().hex

    def save(self, *args, **kwargs):
        if not self.public_key:
            self.public_key = ProjectKey.generate_api_key()
        if not self.secret_key:
            self.secret_key = ProjectKey.generate_api_key()
        super(ProjectKey, self).save(*args, **kwargs)

    def get_dsn(self, domain=None, secure=True):
        urlparts = urlparse.urlparse(settings.URL_PREFIX)
        return '%s://%s:%s@%s/%s' % (
            urlparts.scheme,
            self.public_key,
            self.secret_key,
            urlparts.netloc + urlparts.path,
            self.project_id,
        )
Ejemplo n.º 3
0
class MessageFilterValue(Model):
    """
    Stores the total number of messages seen by a group matching
    the given filter.
    """
    project = models.ForeignKey(Project, null=True)
    group = models.ForeignKey(Group)
    times_seen = models.PositiveIntegerField(default=0)
    key = models.CharField(max_length=32)
    value = models.CharField(max_length=200)
    last_seen = models.DateTimeField(default=timezone.now,
                                     db_index=True,
                                     null=True)
    first_seen = models.DateTimeField(default=timezone.now,
                                      db_index=True,
                                      null=True)

    objects = BaseManager()

    class Meta:
        unique_together = (('project', 'key', 'value', 'group'), )

    def __unicode__(self):
        return u'group_id=%s, times_seen=%s, key=%s, value=%s' % (
            self.group_id, self.times_seen, self.key, self.value)

    def save(self, *args, **kwargs):
        if not self.first_seen:
            self.first_seen = self.last_seen
        super(MessageFilterValue, self).save(*args, **kwargs)
Ejemplo n.º 4
0
class GroupTag(Model):
    """
    Stores the total number of messages seen by a group matching
    the given filter.
    """
    project = models.ForeignKey(Project, null=True)
    group = models.ForeignKey(Group)
    times_seen = BoundedPositiveIntegerField(default=0)
    key = models.CharField(max_length=MAX_TAG_KEY_LENGTH)
    value = models.CharField(max_length=MAX_TAG_VALUE_LENGTH)
    last_seen = models.DateTimeField(default=timezone.now,
                                     db_index=True,
                                     null=True)
    first_seen = models.DateTimeField(default=timezone.now,
                                      db_index=True,
                                      null=True)

    objects = BaseManager()

    class Meta:
        db_table = 'sentry_messagefiltervalue'
        unique_together = (('project', 'key', 'value', 'group'), )

    __repr__ = sane_repr('project_id', 'group_id', 'key', 'value')

    def save(self, *args, **kwargs):
        if not self.first_seen:
            self.first_seen = self.last_seen
        super(GroupTag, self).save(*args, **kwargs)
Ejemplo n.º 5
0
class ProjectKey(Model):
    project = models.ForeignKey(Project, related_name='key_set')
    public_key = models.CharField(max_length=32, unique=True, null=True)
    secret_key = models.CharField(max_length=32, unique=True, null=True)
    user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True)

    # For audits
    user_added = models.ForeignKey(settings.AUTH_USER_MODEL,
                                   null=True,
                                   related_name='keys_added_set')
    date_added = models.DateTimeField(default=timezone.now, null=True)

    objects = BaseManager(cache_fields=(
        'public_key',
        'secret_key',
    ))

    __repr__ = sane_repr('project_id', 'user_id', 'public_key')

    def __unicode__(self):
        return unicode(self.public_key)

    @classmethod
    def generate_api_key(cls):
        return uuid.uuid4().hex

    def save(self, *args, **kwargs):
        if not self.public_key:
            self.public_key = ProjectKey.generate_api_key()
        if not self.secret_key:
            self.secret_key = ProjectKey.generate_api_key()
        super(ProjectKey, self).save(*args, **kwargs)

    def get_dsn(self, domain=None, secure=True, public=False):
        # TODO: change the DSN to use project slug once clients are compatible
        if not public:
            key = '%s:%s' % (self.public_key, self.secret_key)
            url = settings.SENTRY_ENDPOINT
        else:
            key = self.public_key
            url = settings.SENTRY_PUBLIC_ENDPOINT

        urlparts = urlparse.urlparse(url or settings.SENTRY_URL_PREFIX)

        return '%s://%s@%s/%s' % (
            urlparts.scheme,
            key,
            urlparts.netloc + urlparts.path,
            self.project_id,
        )

    @property
    def dsn_private(self):
        return self.get_dsn(public=False)

    @property
    def dsn_public(self):
        return self.get_dsn(public=True)
Ejemplo n.º 6
0
class SearchToken(Model):
    document = models.ForeignKey(SearchDocument, related_name="token_set")
    field = models.CharField(max_length=64, default='text')
    token = models.CharField(max_length=128)
    times_seen = models.PositiveIntegerField(default=1)

    objects = BaseManager()

    class Meta:
        unique_together = (('document', 'field', 'token'), )
Ejemplo n.º 7
0
class PendingTeamMember(Model):
    """
    Identifies relationships between teams and pending invites.
    """
    team = models.ForeignKey(Team, related_name="pending_member_set")
    email = models.EmailField()
    type = models.IntegerField(choices=MEMBER_TYPES,
                               default=globals().get(
                                   settings.DEFAULT_PROJECT_ACCESS))
    date_added = models.DateTimeField(default=timezone.now)

    objects = BaseManager()

    class Meta:
        unique_together = (('team', 'email'), )

    def __unicode__(self):
        return u'team=%s, email=%s, type=%s' % (self.team_id, self.email,
                                                self.get_type_display())

    @property
    def token(self):
        checksum = md5()
        for x in (str(self.team_id), self.email, settings.KEY):
            checksum.update(x)
        return checksum.hexdigest()

    def send_invite_email(self):
        from django.core.mail import send_mail
        from sentry.web.helpers import render_to_string

        context = {
            'email':
            self.email,
            'team':
            self.team,
            'url':
            '%s%s' % (settings.URL_PREFIX,
                      reverse('sentry-accept-invite',
                              kwargs={
                                  'member_id': self.id,
                                  'token': self.token,
                              })),
        }
        body = render_to_string('sentry/emails/member_invite.txt', context)

        try:
            send_mail('%s Invite to join team: %s' %
                      (settings.EMAIL_SUBJECT_PREFIX, self.team.name),
                      body,
                      settings.SERVER_EMAIL, [self.email],
                      fail_silently=False)
        except Exception, e:
            logger = logging.getLogger('sentry.mail.errors')
            logger.exception(e)
Ejemplo n.º 8
0
class PendingTeamMember(Model):
    """
    Identifies relationships between teams and pending invites.
    """
    team = models.ForeignKey(Team, related_name="pending_member_set")
    email = models.EmailField()
    type = BoundedIntegerField(choices=MEMBER_TYPES, default=MEMBER_USER)
    date_added = models.DateTimeField(default=timezone.now)

    objects = BaseManager()

    class Meta:
        unique_together = (('team', 'email'), )

    __repr__ = sane_repr('team_id', 'email', 'type')

    @property
    def token(self):
        checksum = md5()
        for x in (str(self.team_id), self.email, settings.SECRET_KEY):
            checksum.update(x)
        return checksum.hexdigest()

    def send_invite_email(self):
        from sentry.utils.email import MessageBuilder

        context = {
            'email':
            self.email,
            'team':
            self.team,
            'url':
            absolute_uri(
                reverse('sentry-accept-invite',
                        kwargs={
                            'member_id': self.id,
                            'token': self.token,
                        })),
        }

        msg = MessageBuilder(
            subject='Invite to join team: %s' % (self.team.name, ),
            template='sentry/emails/member_invite.txt',
            context=context,
        )

        try:
            msg.send([self.email])
        except Exception, e:
            logger = logging.getLogger('sentry.mail.errors')
            logger.exception(e)
Ejemplo n.º 9
0
class FilterValue(Model):
    """
    Stores references to available filters.
    """
    project = models.ForeignKey(Project, null=True)
    key = models.CharField(max_length=32)
    value = models.CharField(max_length=200)

    objects = BaseManager()

    class Meta:
        unique_together = (('project', 'key', 'value'), )

    __repr__ = sane_repr('project_id', 'key', 'value')
Ejemplo n.º 10
0
class View(Model):
    """
    A view ties directly to a view extension and simply
    identifies it at the db level.
    """
    path = models.CharField(max_length=100, unique=True)
    verbose_name = models.CharField(max_length=200, null=True)
    verbose_name_plural = models.CharField(max_length=200, null=True)

    objects = BaseManager(cache_fields=[
        'path',
    ])

    def __unicode__(self):
        return self.path
Ejemplo n.º 11
0
class GroupBookmark(Model):
    """
    Identifies a bookmark relationship between a user and an
    aggregated event (Group).
    """
    project = models.ForeignKey(Project, related_name="bookmark_set")  # denormalized
    group = models.ForeignKey(Group, related_name="bookmark_set")
    # namespace related_name on User since we dont own the model
    user = models.ForeignKey(User, related_name="sentry_bookmark_set")

    objects = BaseManager()

    class Meta:
        # composite index includes project for efficient queries
        unique_together = (('project', 'user', 'group'),)
Ejemplo n.º 12
0
class FilterValue(Model):
    """
    Stores references to available filters.
    """
    project = models.ForeignKey(Project, null=True)
    key = models.CharField(max_length=32)
    value = models.CharField(max_length=200)

    objects = BaseManager()

    class Meta:
        unique_together = (('project', 'key', 'value'), )

    def __unicode__(self):
        return u'key=%s, value=%s' % (self.key, self.value)
Ejemplo n.º 13
0
class TeamMember(Model):
    """
    Identifies relationships between teams and users.
    """
    team = models.ForeignKey(Team, related_name="member_set")
    user = models.ForeignKey(User, related_name="sentry_teammember_set")
    is_active = models.BooleanField(default=True)
    type = models.IntegerField(choices=MEMBER_TYPES, default=MEMBER_USER)
    date_added = models.DateTimeField(default=timezone.now)

    objects = BaseManager()

    class Meta:
        unique_together = (('team', 'user'), )

    __repr__ = sane_repr('team_id', 'user_id', 'type')
Ejemplo n.º 14
0
class TrackedUser(Model):
    project = models.ForeignKey(Project)
    ident = models.CharField(max_length=200)
    email = models.EmailField(null=True)
    data = GzippedDictField(blank=True, null=True)
    last_seen = models.DateTimeField(default=timezone.now, db_index=True)
    first_seen = models.DateTimeField(default=timezone.now, db_index=True)
    num_events = models.PositiveIntegerField(default=0)
    groups = models.ManyToManyField(Group, through='sentry.AffectedUserByGroup')

    objects = BaseManager()

    class Meta:
        unique_together = (('project', 'ident'),)

    __repr__ = sane_repr('project_id', 'ident', 'email')
Ejemplo n.º 15
0
class FilterValue(Model):
    """
    Stores references to available filters.
    """
    project = models.ForeignKey(Project, null=True)
    key = models.CharField(max_length=32)
    value = models.CharField(max_length=200)
    times_seen = models.PositiveIntegerField(default=0)
    last_seen = models.DateTimeField(default=timezone.now, db_index=True, null=True)
    first_seen = models.DateTimeField(default=timezone.now, db_index=True, null=True)

    objects = BaseManager()

    class Meta:
        unique_together = (('project', 'key', 'value'),)

    __repr__ = sane_repr('project_id', 'key', 'value')
Ejemplo n.º 16
0
class TeamMember(Model):
    """
    Identifies relationships between teams and users.
    """
    team = models.ForeignKey(Team, related_name="member_set")
    user = models.ForeignKey(User, related_name="sentry_teammember_set")
    is_active = models.BooleanField(default=True)
    type = models.IntegerField(choices=MEMBER_TYPES, default=globals().get(settings.DEFAULT_PROJECT_ACCESS))
    date_added = models.DateTimeField(default=timezone.now)

    objects = BaseManager()

    class Meta:
        unique_together = (('team', 'user'),)

    def __unicode__(self):
        return u'team=%s, user=%s, type=%s' % (self.team_id, self.user_id, self.get_type_display())
Ejemplo n.º 17
0
class GroupTagKey(Model):
    """
    Stores a unique tag key name for a group.

    An example key might be "url" or "server_name".
    """
    project = models.ForeignKey(Project, null=True)
    group = models.ForeignKey(Group)
    key = models.CharField(max_length=MAX_TAG_KEY_LENGTH)
    values_seen = BoundedPositiveIntegerField(default=0)

    objects = BaseManager()

    class Meta:
        unique_together = (('project', 'group', 'key'), )

    __repr__ = sane_repr('project_id', 'group_id', 'key')
Ejemplo n.º 18
0
class ProjectCountByMinute(Model):
    """
    Stores the total number of messages seen by a project at N minute intervals.

    e.g. if it happened at 08:34:55 the time would be normalized to 08:30:00
    """

    project = models.ForeignKey(Project, null=True)
    date = models.DateTimeField()  # normalized to HH:MM:00
    times_seen = models.PositiveIntegerField(default=0)
    time_spent_total = models.FloatField(default=0)
    time_spent_count = models.IntegerField(default=0)

    objects = BaseManager()

    class Meta:
        unique_together = (('project', 'date'), )
Ejemplo n.º 19
0
class TeamMember(Model):
    """
    Identifies relationships between teams and users.

    Users listed as team members are considered to have access to all projects
    and could be thought of as team owners (though their access level may not)
    be set to ownership.
    """
    team = models.ForeignKey(Team, related_name="member_set")
    user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name="sentry_teammember_set")
    type = BoundedIntegerField(choices=MEMBER_TYPES, default=MEMBER_USER)
    date_added = models.DateTimeField(default=timezone.now)

    objects = BaseManager()

    class Meta:
        unique_together = (('team', 'user'),)

    __repr__ = sane_repr('team_id', 'user_id', 'type')
Ejemplo n.º 20
0
class AffectedUserByGroup(Model):
    """
    Stores a count of how many times a ``Group`` has affected
    a user.
    """
    project = models.ForeignKey(Project)
    tuser = models.ForeignKey(TrackedUser, null=True)
    group = models.ForeignKey(Group)
    ident = models.CharField(max_length=200, null=True)
    times_seen = models.PositiveIntegerField(default=0)
    last_seen = models.DateTimeField(default=timezone.now, db_index=True)
    first_seen = models.DateTimeField(default=timezone.now, db_index=True)

    objects = BaseManager()

    class Meta:
        unique_together = (('project', 'tuser', 'group'), )

    __repr__ = sane_repr('project_id', 'group_id', 'tuser_id')
Ejemplo n.º 21
0
class Team(Model):
    """
    A team represents a group of individuals which maintain ownership of projects.
    """
    slug = models.SlugField(unique=True)
    name = models.CharField(max_length=64)
    owner = models.ForeignKey(User)

    objects = BaseManager(cache_fields=(
        'pk',
        'slug',
    ))

    def __unicode__(self):
        return self.slug

    def save(self, *args, **kwargs):
        if not self.slug:
            self.slug = slugify(self.name)
        super(Team, self).save(*args, **kwargs)
Ejemplo n.º 22
0
class GroupCountByMinute(Model):
    """
    Stores the total number of messages seen by a group at N minute intervals.

    e.g. if it happened at 08:34:55 the time would be normalized to 08:30:00
    """

    project = models.ForeignKey(Project, null=True)
    group = models.ForeignKey(Group)
    date = models.DateTimeField(db_index=True)  # normalized to HH:MM:00
    times_seen = BoundedPositiveIntegerField(default=0)
    time_spent_total = models.FloatField(default=0)
    time_spent_count = BoundedIntegerField(default=0)

    objects = BaseManager()

    class Meta:
        db_table = 'sentry_messagecountbyminute'
        unique_together = (('project', 'group', 'date'), )

    __repr__ = sane_repr('project_id', 'group_id', 'date')
Ejemplo n.º 23
0
class TagValue(Model):
    """
    Stores references to available filters.
    """
    project = models.ForeignKey(Project, null=True)
    key = models.CharField(max_length=MAX_TAG_KEY_LENGTH)
    value = models.CharField(max_length=MAX_TAG_VALUE_LENGTH)
    data = GzippedDictField(blank=True, null=True)
    times_seen = BoundedPositiveIntegerField(default=0)
    last_seen = models.DateTimeField(
        default=timezone.now, db_index=True, null=True)
    first_seen = models.DateTimeField(
        default=timezone.now, db_index=True, null=True)

    objects = BaseManager()

    class Meta:
        db_table = 'sentry_filtervalue'
        unique_together = (('project', 'key', 'value'),)

    __repr__ = sane_repr('project_id', 'key', 'value')
Ejemplo n.º 24
0
class MessageCountByMinute(Model):
    """
    Stores the total number of messages seen by a group at N minute intervals.

    e.g. if it happened at 08:34:55 the time would be normalized to 08:30:00
    """

    project = models.ForeignKey(Project, null=True)
    group = models.ForeignKey(Group)
    date = models.DateTimeField(db_index=True)  # normalized to HH:MM:00
    times_seen = models.PositiveIntegerField(default=0)
    time_spent_total = models.FloatField(default=0)
    time_spent_count = models.IntegerField(default=0)

    objects = BaseManager()

    class Meta:
        unique_together = (('project', 'group', 'date'),)

    def __unicode__(self):
        return u'group_id=%s, times_seen=%s, date=%s' % (self.group_id, self.times_seen, self.date)
Ejemplo n.º 25
0
class ProjectDomain(Model):
    """
    Currently unused. Planned for 'trusted domains' for JS apis.
    """
    project = models.ForeignKey(Project, related_name="domain_set")
    domain = models.CharField(max_length=128)

    objects = BaseManager()

    class Meta:
        unique_together = (('project', 'domain'),)

    def __unicode__(self):
        return u'project=%s, domain=%s' % (self.project_id, self.domain)

    @classmethod
    def test(cls, project, url, strict=False):
        """
        Tests whether the ``url`` is a trusted domain for the given project.
        """
        if not url:
            return False
        url = urlparse.urlsplit(url).hostname
        if not url:
            # If we fail to parse the referral url
            return False
        if url in ('127.0.0.1', 'localhost'):
            return True
        if url.endswith('.local'):
            return True
        url = url.split('.')
        domains = ProjectDomain.objects.filter(project=project).values_list('domain', flat=True)
        for d in domains:
            d = d.split('.')
            if url[-len(d):] == d:
                return True
        return False
Ejemplo n.º 26
0
class Event(EventBase):
    """
    An individual event.
    """
    group = models.ForeignKey(Group,
                              blank=True,
                              null=True,
                              related_name="event_set")
    event_id = models.CharField(max_length=32,
                                null=True,
                                db_column="message_id")
    datetime = models.DateTimeField(default=timezone.now, db_index=True)
    time_spent = models.FloatField(null=True)
    server_name = models.CharField(max_length=128, db_index=True, null=True)
    site = models.CharField(max_length=128, db_index=True, null=True)

    objects = BaseManager()

    class Meta:
        verbose_name = _('message')
        verbose_name_plural = _('messages')
        db_table = 'sentry_message'
        unique_together = ('project', 'event_id')

    __repr__ = sane_repr('project_id', 'group_id', 'checksum')

    @memoize
    def interfaces(self):
        result = []
        for key, data in self.data.iteritems():
            if '.' not in key:
                continue

            try:
                cls = import_string(key)
            except ImportError:
                continue  # suppress invalid interfaces

            value = safe_execute(cls, **data)
            if not value:
                continue

            result.append((key, value))

        return SortedDict((k, v) for k, v in sorted(
            result, key=lambda x: x[1].get_score(), reverse=True))

    def get_version(self):
        if not self.data:
            return
        if '__sentry__' not in self.data:
            return
        if 'version' not in self.data['__sentry__']:
            return
        module = self.data['__sentry__'].get('module', 'ver')
        return module, self.data['__sentry__']['version']

    def get_tags(self):
        try:
            return [(t, v) for t, v in self.data.get('tags') or ()
                    if not t.startswith('sentry:')]
        except ValueError:
            # at one point Sentry allowed invalid tag sets such as (foo, bar)
            # vs ((tag, foo), (tag, bar))
            return []

    def as_dict(self):
        # We use a SortedDict to keep elements ordered for a potential JSON serializer
        data = SortedDict()
        data['id'] = self.event_id
        data['checksum'] = self.checksum
        data['project'] = self.project.slug
        data['logger'] = self.logger
        data['level'] = self.get_level_display()
        data['culprit'] = self.culprit
        for k, v in sorted(self.data.iteritems()):
            data[k] = v
        return data

    @property
    def size(self):
        return len(unicode(vars(self)))
Ejemplo n.º 27
0
class Event(MessageBase):
    """
    An individual event.
    """
    group = models.ForeignKey(Group, blank=True, null=True, related_name="event_set")
    event_id = models.CharField(max_length=32, null=True, db_column="message_id")
    datetime = models.DateTimeField(default=datetime.now, db_index=True)
    time_spent = models.FloatField(null=True)
    server_name = models.CharField(max_length=128, db_index=True, null=True)
    site = models.CharField(max_length=128, db_index=True, null=True)

    objects = BaseManager()

    class Meta:
        verbose_name = _('message')
        verbose_name_plural = _('messages')
        db_table = 'sentry_message'
        unique_together = ('project', 'event_id')

    def __unicode__(self):
        return self.error()

    def get_absolute_url(self):
        if self.project_id:
            return reverse('sentry-group-event', kwargs={'group_id': self.group_id, 'event_id': self.pk, 'project_id': self.project_id})
        return '#'

    @cached_property
    def request(self):
        data = self.data
        if 'META' in data:
            kwargs = {
                'META': data.get('META'),
                'GET': data.get('GET'),
                'POST': data.get('POST'),
                'FILES': data.get('FILES'),
                'COOKIES': data.get('COOKIES'),
                'url': data.get('url'),
            }
        elif 'sentry.interfaces.Http' in data:
            http = data['sentry.interfaces.Http']
            kwargs = {
                'META': http
            }
        else:
            return MockDjangoRequest()

        fake_request = MockDjangoRequest(**kwargs)
        if kwargs['url']:
            fake_request.path_info = '/' + kwargs['url'].split('/', 3)[-1]
        else:
            fake_request.path_info = ''
        fake_request.path = fake_request.path_info
        return fake_request

    @cached_property
    def interfaces(self):
        result = []
        for k, v in self.data.iteritems():
            if '.' not in k:
                continue
            m, c = k.rsplit('.', 1)
            cls = getattr(__import__(m, {}, {}, [c]), c)
            v = cls(**v)
            result.append((v.score, k, v))
        return SortedDict((k, v) for _, k, v in sorted(result, key=lambda x: x[0], reverse=True))

    def get_version(self):
        if not self.data:
            return
        if '__sentry__' not in self.data:
            return
        if 'version' not in self.data['__sentry__']:
            return
        module = self.data['__sentry__'].get('module', 'ver')
        return module, self.data['__sentry__']['version']
Ejemplo n.º 28
0
class Event(MessageBase):
    """
    An individual event.
    """
    group = models.ForeignKey(Group,
                              blank=True,
                              null=True,
                              related_name="event_set")
    event_id = models.CharField(max_length=32,
                                null=True,
                                db_column="message_id")
    datetime = models.DateTimeField(default=timezone.now, db_index=True)
    time_spent = models.FloatField(null=True)
    server_name = models.CharField(max_length=128, db_index=True, null=True)
    site = models.CharField(max_length=128, db_index=True, null=True)

    objects = BaseManager()

    class Meta:
        verbose_name = _('message')
        verbose_name_plural = _('messages')
        db_table = 'sentry_message'
        unique_together = ('project', 'event_id')

    def __unicode__(self):
        return self.error()

    def get_absolute_url(self):
        if self.project_id:
            return reverse('sentry-group-event',
                           kwargs={
                               'group_id': self.group_id,
                               'event_id': self.pk,
                               'project_id': self.project.slug
                           })
        return '#'

    @cached_property
    def request(self):
        data = self.data
        if 'META' in data:
            kwargs = {
                'META': data.get('META'),
                'GET': data.get('GET'),
                'POST': data.get('POST'),
                'FILES': data.get('FILES'),
                'COOKIES': data.get('COOKIES'),
                'url': data.get('url'),
            }
        elif 'sentry.interfaces.Http' in data:
            http = data['sentry.interfaces.Http']
            kwargs = {'META': http}
        else:
            return MockDjangoRequest()

        fake_request = MockDjangoRequest(**kwargs)
        if kwargs['url']:
            fake_request.path_info = '/' + kwargs['url'].split('/', 3)[-1]
        else:
            fake_request.path_info = ''
        fake_request.path = fake_request.path_info
        return fake_request

    @cached_property
    def interfaces(self):
        result = []
        for key, data in self.data.iteritems():
            if '.' not in key:
                continue

            try:
                cls = import_string(key)
            except ImportError:
                pass  # suppress invalid interfaces
            value = cls(**data)
            result.append((value.score, key, value))
        return SortedDict(
            (k, v)
            for _, k, v in sorted(result, key=lambda x: x[0], reverse=True))

    def get_version(self):
        if not self.data:
            return
        if '__sentry__' not in self.data:
            return
        if 'version' not in self.data['__sentry__']:
            return
        module = self.data['__sentry__'].get('module', 'ver')
        return module, self.data['__sentry__']['version']

    def as_dict(self):
        # We use a SortedDict to keep elements ordered for a potential JSON serializer
        data = SortedDict()
        data['id'] = self.event_id
        data['checksum'] = self.checksum
        data['project'] = self.project.slug
        data['logger'] = self.logger
        data['level'] = self.get_level_display()
        data['culprit'] = self.culprit
        for k, v in sorted(self.data.iteritems()):
            data[k] = v
        return data