Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions bin/load-mocks
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ def main(num_events=1, extra_events=False):

for team_name, project_names in mocks:
print('> Mocking team {}'.format(team_name))
team, _ = Team.objects.get_or_create(
team, _ = Team.objects.unrestricted_unsafe().get_or_create(
name=team_name,
defaults={
'organization': org,
Expand All @@ -347,7 +347,7 @@ def main(num_events=1, extra_events=False):

for project_name in project_names:
print(' > Mocking project {}'.format(project_name))
project, _ = Project.objects.get_or_create(
project, _ = Project.objects.unrestricted_unsafe().get_or_create(
name=project_name,
defaults={
'organization': org,
Expand Down
6 changes: 3 additions & 3 deletions examples/oauth2_consumer_webserver/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def index():
return ('<h1>Who are you?</h1>'
'<p><a href="{}">Login with Sentry</a></p>').format(
url_for('login'),
)
)

from urllib2 import Request, urlopen, URLError
headers = {'Authorization': 'Bearer {}'.format(access_token)}
Expand All @@ -64,7 +64,7 @@ def index():
'<pre>{}</pre>').format(
json.loads(session['user'])['email'],
json.dumps(json.loads(res.read()), indent=2),
)
)


@app.route('/login')
Expand All @@ -82,7 +82,7 @@ def authorized(resp):
'<p><a href="{}">Try again</a></p>').format(
request.args['error'],
url_for('login'),
)
)
access_token = resp['access_token']
session['access_token'] = access_token
session['user'] = json.dumps(resp['user'])
Expand Down
18 changes: 11 additions & 7 deletions src/sentry/api/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@

from sentry import tsdb
from sentry.app import raven
from sentry.models import Environment
from sentry.models import ApiKey, AuditLogEntry, Environment
from sentry.utils import tenants
from sentry.utils.cursors import Cursor
from sentry.utils.dates import to_datetime
from sentry.utils.http import absolute_uri, is_valid_origin
Expand Down Expand Up @@ -142,12 +143,15 @@ def dispatch(self, request, *args, **kwargs):

self.initial(request, *args, **kwargs)

if getattr(request, 'user', None) and request.user.is_authenticated():
raven.user_context({
'id': request.user.id,
'username': request.user.username,
'email': request.user.email,
})
if getattr(request, 'user', None):
if request.user.is_authenticated():
raven.user_context({
'id': request.user.id,
'email': request.user.email,
'username': request.user.username,
})

tenants.set_current_tenant(tenants.Tenant.from_user(request.user))

# Get the appropriate handler method
if request.method.lower() in self.http_method_names:
Expand Down
3 changes: 2 additions & 1 deletion src/sentry/api/endpoints/group_similar_issues.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ def get(self, request, group):
filter(
lambda group_id__scores: group_id__scores[0] is not None,
map(
lambda group_id__scores: (serialized_groups.get(group_id__scores[0]), group_id__scores[1], ),
lambda group_id__scores: (serialized_groups.get(
group_id__scores[0]), group_id__scores[1], ),
results,
),
),
Expand Down
5 changes: 4 additions & 1 deletion src/sentry/api/endpoints/organization_slugs.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ def put(self, request, organization):
if len(slugs) != len(set(slugs.values())):
return Response({'detail': 'Duplicate slugs'}, status=400)

project_q = organization.project_set.filter(pk__in=[int(x) for x in slugs])
project_q = Project.objects.filter(
organization=organization,
pk__in=[int(x) for x in slugs]
)

rv = {}

Expand Down
1 change: 1 addition & 0 deletions src/sentry/db/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@
from .base import * # NOQA
from .fields import * # NOQA
from .manager import * # NOQA
from .mixins import * # NOQA
from .query import * # NOQA
81 changes: 69 additions & 12 deletions src/sentry/db/models/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,21 @@

from django.conf import settings
from django.db import router
from django.db.models import Manager, Model
from django.db.models.signals import (post_save, post_delete, post_init, class_prepared)
from django.db.models import Manager, Model, Q
from django.db.models.signals import (
post_save, post_delete, post_init, class_prepared)
from django.utils.encoding import smart_text

from sentry import nodestore
from sentry.exceptions import MissingTenant
from sentry.utils.cache import cache
from sentry.utils.hashlib import md5_text

from .query import create_or_update
from .queryset import BoundQuerySet

__all__ = ('BaseManager', )
__all__ = ('BaseManager', 'BoundManager',
'OrganizationBoundManager', 'ProjectBoundManager')

logger = logging.getLogger('sentry')

Expand Down Expand Up @@ -215,14 +219,21 @@ def contribute_to_class(self, model, name):
super(BaseManager, self).contribute_to_class(model, name)
class_prepared.connect(self.__class_prepared, sender=model)

def get_from_cache(self, **kwargs):
# TODO(dcramer): we're hijacking this method and adding
# unrestricted_unsafe to avoid refactoring right now
def get_from_cache(self, unrestricted_unsafe=False, **kwargs):
"""
Wrapper around QuerySet.get which supports caching of the
intermediate value. Callee is responsible for making sure
the cache key is cleared on save.
"""
if unrestricted_unsafe:
queryset = self.unrestricted_unsafe()
else:
queryset = self

if not self.cache_fields or len(kwargs) > 1:
return self.get(**kwargs)
return queryset.get(**kwargs)

key, value = next(six.iteritems(kwargs))
pk_name = self.model._meta.pk.name
Expand All @@ -242,7 +253,7 @@ def get_from_cache(self, **kwargs):

retval = cache.get(cache_key, version=self.cache_version)
if retval is None:
result = self.get(**kwargs)
result = queryset.get(**kwargs)
# Ensure we're pushing it into the cache
self.__post_save(instance=result)
return result
Expand All @@ -254,21 +265,24 @@ def get_from_cache(self, **kwargs):

if type(retval) != self.model:
if settings.DEBUG:
raise ValueError('Unexpected value type returned from cache')
logger.error('Cache response returned invalid value %r', retval)
return self.get(**kwargs)
raise ValueError(
'Unexpected value type returned from cache')
logger.error(
'Cache response returned invalid value %r', retval)
return queryset.get(**kwargs)

if key == pk_name and int(value) != retval.pk:
if settings.DEBUG:
raise ValueError('Unexpected value returned from cache')
logger.error('Cache response returned invalid value %r', retval)
return self.get(**kwargs)
logger.error(
'Cache response returned invalid value %r', retval)
return queryset.get(**kwargs)

retval._state.db = router.db_for_read(self.model, **kwargs)

return retval
else:
return self.get(**kwargs)
return queryset.get(**kwargs)

def create_or_update(self, **kwargs):
return create_or_update(self.model, **kwargs)
Expand Down Expand Up @@ -304,3 +318,46 @@ def post_delete(self, instance, **kwargs):
"""
Triggered when a model bound to this manager is deleted.
"""


class BoundManager(BaseManager):
# TODO(dcramer): its not clear to me that we actually need to
# use BaseManager ever on related_fields (other this changes
# that behavior)
use_for_related_fields = False

def get_queryset(self, *args, **kwargs):
return BoundQuerySet(
model=self.model,
using=self._db,
binding_criteria_fn=self.get_binding_criteria,
)

def unrestricted_unsafe(self):
return self.get_queryset().unrestricted_unsafe()


class OrganizationBoundManager(BoundManager):
def get_binding_criteria(self):
from sentry.utils import tenants
tenant = tenants.get_current_tenant()
if not tenant:
raise MissingTenant
elif not tenant.organization_ids:
return
elif tenant.organization_ids == tenants.ALL:
return Q()
return Q(organization_id__in=tenant.organization_ids)


class ProjectBoundManager(BoundManager):
def get_binding_criteria(self):
from sentry.utils import tenants
tenant = tenants.get_current_tenant()
if not tenant:
raise MissingTenant
elif not tenant.organization_ids:
return
elif tenant.project_ids == tenants.ALL:
return Q()
return Q(project_id__in=tenant.project_ids)
19 changes: 19 additions & 0 deletions src/sentry/db/models/mixins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from __future__ import absolute_import

__all__ = ('OrganizationBoundMixin', 'ProjectBoundMixin')

from .manager import OrganizationBoundManager, ProjectBoundManager


class OrganizationBoundMixin(object):
# XXX(dcramer): cant seem to define fields via mixin
# organization = FlexibleForeignKey('sentry.Organization', related_name=None)

objects = OrganizationBoundManager()


class ProjectBoundMixin(object):
# XXX(dcramer): cant seem to define fields via mixin
# project = FlexibleForeignKey('sentry.Project', related_name=None)

objects = ProjectBoundManager()
11 changes: 8 additions & 3 deletions src/sentry/db/models/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
__all__ = ('update', 'create_or_update')


def update(self, using=None, **kwargs):
def update(self, using=None, unconstrained_unsafe=False, **kwargs):
"""
Updates specified attributes on the current instance.
"""
Expand All @@ -33,7 +33,10 @@ def update(self, using=None, **kwargs):
if getattr(field, 'auto_now', False) and field.name not in kwargs:
kwargs[field.name] = field.pre_save(self, False)

affected = self.__class__._base_manager.using(using).filter(pk=self.pk).update(**kwargs)
objects = self.__class__._base_manager.using(using).filter(pk=self.pk)
if unconstrained_unsafe:
objects = objects.unconstrained_unsafe()
affected = objects.update(**kwargs)
for k, v in six.iteritems(kwargs):
if isinstance(v, ExpressionNode):
v = resolve_expression_node(self, v)
Expand All @@ -54,7 +57,7 @@ def update(self, using=None, **kwargs):
update.alters_data = True


def create_or_update(model, using=None, **kwargs):
def create_or_update(model, using=None, unconstrained_unsafe=False, **kwargs):
"""
Similar to get_or_create, either updates a row or creates it.
only values args are used for update
Expand All @@ -74,6 +77,8 @@ def create_or_update(model, using=None, **kwargs):
using = router.db_for_write(model)

objects = model.objects.using(using)
if unconstrained_unsafe:
objects = objects.unconstrained_unsafe()

affected = objects.filter(**kwargs).update(**values)
if affected:
Expand Down
Loading