├── model_update.py ├── nullable_foreignkey.py └── staff_debug_wsgi_handler.py /model_update.py: -------------------------------------------------------------------------------- 1 | import operator 2 | 3 | from django.db.models.expressions import F, ExpressionNode 4 | 5 | EXPRESSION_NODE_CALLBACKS = { 6 | ExpressionNode.ADD: operator.add, 7 | ExpressionNode.SUB: operator.sub, 8 | ExpressionNode.MUL: operator.mul, 9 | ExpressionNode.DIV: operator.div, 10 | ExpressionNode.MOD: operator.mod, 11 | ExpressionNode.AND: operator.and_, 12 | ExpressionNode.OR: operator.or_, 13 | } 14 | 15 | class CannotResolve(Exception): 16 | pass 17 | 18 | def _resolve(instance, node): 19 | if isinstance(node, F): 20 | return getattr(instance, node.name) 21 | elif isinstance(node, ExpressionNode): 22 | return _resolve(instance, node) 23 | return node 24 | 25 | def resolve_expression_node(instance, node): 26 | op = EXPRESSION_NODE_CALLBACKS.get(node.connector, None) 27 | if not op: 28 | raise CannotResolve 29 | runner = _resolve(instance, node.children[0]) 30 | for n in node.children[1:]: 31 | runner = op(runner, _resolve(instance, n)) 32 | return runner 33 | 34 | def update(instance, **kwargs): 35 | "Atomically update instance, setting field/value pairs from kwargs" 36 | # fields that use auto_now=True should be updated corrected, too! 37 | for field in instance._meta.fields: 38 | if hasattr(field, 'auto_now') and field.auto_now and field.name not in kwargs: 39 | kwargs[field.name] = field.pre_save(instance, False) 40 | 41 | rows_affected = instance.__class__._default_manager.filter(pk=instance.pk).update(**kwargs) 42 | 43 | # apply the updated args to the instance to mimic the change 44 | # note that these might slightly differ from the true database values 45 | # as the DB could have been updated by another thread. callers should 46 | # retrieve a new copy of the object if up-to-date values are required 47 | for k,v in kwargs.iteritems(): 48 | if isinstance(v, ExpressionNode): 49 | v = resolve_expression_node(instance, v) 50 | setattr(instance, k, v) 51 | 52 | # If you use an ORM cache, make sure to invalidate the instance! 53 | #cache.set(djangocache.get_cache_key(instance=instance), None, 5) 54 | return rows_affected 55 | -------------------------------------------------------------------------------- /nullable_foreignkey.py: -------------------------------------------------------------------------------- 1 | from django.db import connection, models 2 | 3 | class NullableForeignKey(models.ForeignKey): 4 | """ 5 | Prevent the default CASCADE DELETE behavior of normal ForeignKey. 6 | When an instance pointed to by a NullableForeignKey is deleted, 7 | the NullableForeignKey field is NULLed rather than the row being deleted. 8 | """ 9 | def __init__(self, *args, **kwargs): 10 | kwargs['null'] = kwargs['blank'] = True 11 | super(NullableForeignKey, self).__init__(*args, **kwargs) 12 | 13 | # Monkeypatch the related class's "collect_sub_objects" 14 | # to not delete this object 15 | def contribute_to_related_class(self, cls, related): 16 | super(NullableForeignKey, self).contribute_to_related_class(cls, related) 17 | _original_csb_attr_name = '_original_collect_sub_objects' 18 | # define a new "collect_sub_objects" method 19 | this_field = self 20 | def _new_collect_sub_objects(self, *args, **kwargs): 21 | qn = connection.ops.quote_name 22 | # find all fields related to the model who's instance is 23 | # being deleted 24 | for related in self._meta.get_all_related_objects(): 25 | if isinstance(related.field, this_field.__class__): 26 | table = qn(related.model._meta.db_table) 27 | column = qn(related.field.column) 28 | sql = "UPDATE %s SET %s = NULL WHERE %s = %%s;" % (table, column, column) 29 | connection.cursor().execute(sql, [self.pk]) 30 | 31 | # Now proceed with collecting sub objects that are still tied via FK 32 | getattr(self, _original_csb_attr_name)(*args, **kwargs) 33 | 34 | # monkey patch the related classes _collect_sub_objects method. 35 | # store the original method in an attr named `_original_csb_attr_name` 36 | if not hasattr(cls, _original_csb_attr_name): 37 | setattr(cls, _original_csb_attr_name, cls._collect_sub_objects) 38 | setattr(cls, '_collect_sub_objects', _new_collect_sub_objects) 39 | -------------------------------------------------------------------------------- /staff_debug_wsgi_handler.py: -------------------------------------------------------------------------------- 1 | from django.core.handlers import wsgi 2 | 3 | class StaffDebugWSGIHandler(wsgi.WSGIHandler): 4 | "WSGI Handler that shows the debug error page if the logged in user is staff" 5 | 6 | def handle_uncaught_exception(self, request, resolver, exc_info): 7 | "Return a debug page response if the logged in user is staff" 8 | from django.conf import settings 9 | 10 | if not settings.DEBUG and hasattr(request, 'user') and request.user.is_staff: 11 | from django.views import debug 12 | return debug.technical_500_response(request, *exc_info) 13 | 14 | # not logged in or not a staff user, display normal public 500 15 | return super(StaffDebugWSGIHandler, self).handle_uncaught_exception( 16 | request, resolver, exc_info 17 | ) 18 | --------------------------------------------------------------------------------