Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Sumit Chachra committed Mar 15, 2011
0 parents commit a1af1b0
Show file tree
Hide file tree
Showing 18 changed files with 704 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.pyc
*.DS_Store
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
http://github.com/Tivix/django-cron/contributors
20 changes: 20 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
Copyright (c) 2011 Tivix, Inc.

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
58 changes: 58 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
===========
django-cron
===========

Overview
--------

Django-common consists of the following things:

- A middleware that makes sure your web-app runs either on or without 'www' in the domain.

- A ``SessionManagerBase`` base class, that helps in keeping your session related code object-oriented and clean! See session.py for usage details.

- Some custom db fields that you can use in your models including a ``UniqueHashField`` and ``RandomHashField``.

- Bunch of helpful functions in helper.py

- A ``render_form_field`` template tag that makes rendering form fields easy and DRY.

- A couple of dry response classes: ``JsonResponse`` and ``XMLResponse`` in the django_common.http that can be used in views that give json/xml responses.


Installation
------------

- Install django_common (ideally in your virtualenv!) using pip or simply getting a copy of the code and putting it in a
directory in your codebase.

- Add ``django_common`` to your Django settings ``INSTALLED_APPS``::
INSTALLED_APPS = [
# ...
"django_common",
]

- Add the following to your settings.py with appropriate values:

- IS_DEV
- IS_PROD
- DOMAIN_NAME
- WWW_ROOT

- Add ``common_settings`` to your Django settings ``TEMPLATE_CONTEXT_PROCESSORS``::
TEMPLATE_CONTEXT_PROCESSORS = [
# ...
"common_settings",
]

- Add ``WWWRedirectMiddleware`` if required to the list of middlewares::
MIDDLEWARE_CLASSES = [
# ...
"WWWRedirectMiddleware",
]


This open-source app is brought to you by Tivix, Inc. ( http://tivix.com/ )
Empty file added django_common/__init__.py
Empty file.
19 changes: 19 additions & 0 deletions django_common/auth_backends.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import logging

from django.contrib.auth.backends import ModelBackend
from django.contrib.auth.models import User


class EmailBackend(ModelBackend):
def authenticate(self, username=None, password=None):
"""
"username" being passed is really email address and being compared to as such.
"""
try:
user = User.objects.get(email=username)
if user.check_password(password):
return user
except (User.DoesNotExist, User.MultipleObjectsReturned):
logging.warn('Unsuccessful login attempt using username/email: %s' % username)

return None
11 changes: 11 additions & 0 deletions django_common/context_processors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from django.conf import settings as django_settings


def common_settings(request):
return {
'domain_name': django_settings.DOMAIN_NAME,
'www_root': django_settings.WWW_ROOT,

'is_dev': django_settings.IS_DEV,
'is_prod': django_settings.IS_PROD,
}
140 changes: 140 additions & 0 deletions django_common/db_fields.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
from django.core import exceptions, validators
from django.db.models import fields
from django.template.defaultfilters import slugify
from django.db import models
from django.core.serializers.json import DjangoJSONEncoder
from django.utils import simplejson as json

from south.modelsinspector import add_introspection_rules
from django_common.helper import md5_hash


class JSONField(models.TextField):
"""
JSONField is a generic textfield that neatly serializes/unserializes JSON objects seamlessly
"""

# Used so to_python() is called
__metaclass__ = models.SubfieldBase

def to_python(self, value):
"""Convert our string value to JSON after we load it from the DB"""

if value == "":
return None

try:
if isinstance(value, basestring):
return json.loads(value)
except ValueError:
pass

return value

def get_db_prep_save(self, value):
"""Convert our JSON object to a string before we save"""

if value == "":
return None

if isinstance(value, dict):
value = json.dumps(value, cls=DjangoJSONEncoder)

return super(JSONField, self).get_db_prep_save(value)

class UniqueSlugField(fields.SlugField):
"""
Represents a self-managing sluf field, that makes sure that the slug value is unique on the db table. Slugs by
default get a db_index on them. The "Unique" in the class name is a misnomer since it does support unique=False
@requires "prepopulate_from" in the constructor. This could be a field or a function in the model class which is using
this field
Defaults update_on_save to False
Taken and edited from: http://www.djangosnippets.org/snippets/728/
"""
def __init__(self, prepopulate_from='id', *args, **kwargs):
if kwargs.get('update_on_save'):
self.__update_on_save = kwargs.pop('update_on_save')
else:
self.__update_on_save = False
self.prepopulate_from = prepopulate_from
super(UniqueSlugField, self).__init__(*args, **kwargs)

def pre_save(self, model_instance, add):
prepopulate_field = getattr(model_instance, self.prepopulate_from)
if callable(prepopulate_field):
prepopulate_value = prepopulate_field()
else:
prepopulate_value = prepopulate_field

# if object has an id, and not to update on save, then return existig model instance's slug value
if getattr(model_instance, 'id') and not self.__update_on_save:
return getattr(model_instance, self.name)

# if this is a previously saved object, and current instance's slug is same as one being proposed
if getattr(model_instance, 'id') and getattr(model_instance, self.name) == prepopulate_value:
return self.__set_and_return(model_instance, self.name, slugify(prepopulate_value))

# if a unique slug is not required (not the default of course)
if not self.unique:
return self.__set_and_return(model_instance, self.name, slugify(prepopulate_value))

return self.__unique_slug(model_instance.__class__, model_instance, self.name,
prepopulate_value)

def __unique_slug(self, model, model_instance, slug_field, slug_value):
orig_slug = slug = slugify(slug_value)
index = 1
while True:
try:
model.objects.get(**{slug_field: slug})
index += 1
slug = orig_slug + '-' + str(index)
except model.DoesNotExist:
return self.__set_and_return(model_instance, slug_field, slug)

def __set_and_return(self, model_instance, slug_field, slug):
setattr(model_instance, slug_field, slug)
return slug

add_introspection_rules([
(
[UniqueSlugField], # Class(es) these apply to
[], # Positional arguments (not used)
{ # Keyword argument
"prepopulate_from": ["prepopulate_from", {"default": 'id'}],
},
),
], ["^django_common\.db_fields\.UniqueSlugField"])


class RandomHashField(fields.CharField):
"""
Store a random hash for a certain model field.
@param update_on_save optional field whether to update this hash or not, everytime the model instance is saved
"""
def __init__(self, update_on_save=False, *args, **kwargs):
#TODO: args & kwargs serve no purpose but to make django evolution to work
self.update_on_save = update_on_save
super(fields.CharField, self).__init__(max_length=128, unique=True, blank=False, null=False, db_index=True, default=md5_hash())

def pre_save(self, model_instance, add):
if not add and not self.update_on_save:
return getattr(model_instance, self.name)

random_hash = md5_hash()
setattr(model_instance, self.name, random_hash)
return random_hash

add_introspection_rules([
(
[RandomHashField], # Class(es) these apply to
[], # Positional arguments (not used)
{ # Keyword argument
"update_on_save": ["update_on_save", {"default": False}],
},
),
], ["^django_common\.db_fields\.RandomHashField"])
106 changes: 106 additions & 0 deletions django_common/helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
"Some common routines that can be used throughout the code."
import hashlib, os, logging, re, datetime, threading

from django.http import Http404
from django.utils import simplejson
from django.utils.encoding import force_unicode
from django.template import Context, RequestContext
from django.template.loader import get_template
from django.core import exceptions

from apps.common.tzinfo import utc, Pacific


class AppException(exceptions.ValidationError):
"""Base class for exceptions used in our system.
A common base class permits application code to distinguish between exceptions raised in our code from ones raised
in libraries.
"""
pass

class InvalidContentType(AppException):
def __init__(self, file_types, msg=None):
msg = msg or 'Only the following file content types are permitted: %s' % str(file_types)
super(self.__class__, self).__init__(msg)
self.file_types = file_types

class FileTooLarge(AppException):
def __init__(self, file_size_kb, msg=None):
msg = msg or 'Files may not be larger than %s KB' % file_size_kb
super(self.__class__, self).__init__(msg)
self.file_size = file_size_kb

def is_among(value, *possibilities):
"Ensure that the method that has been used for the request is one of the expected ones (e.g., GET or POST)."
for possibility in possibilities:
if value == possibility:
return True
raise Exception, 'A different request value was encountered than expected: %s' % value

def form_errors_serialize(form):
errors = {}
for field in form.fields.keys():
if form.errors.has_key(field):
if form.prefix:
errors['%s-%s' % (form.prefix, field)] = force_unicode(form.errors[field])
else:
errors[field] = force_unicode(form.errors[field])

if form.non_field_errors():
errors['non_field_errors'] = force_unicode(form.non_field_errors())
return {'errors': errors}

def json_response(data={ }, errors=[ ], success=True):
data.update({
'errors': errors,
'success': len(errors) == 0 and success,
})
return simplejson.dumps(data)

def sha224_hash():
return hashlib.sha224(os.urandom(224)).hexdigest()

def sha1_hash():
return hashlib.sha1(os.urandom(224)).hexdigest()

def md5_hash(image=None, max_length=None):
# TODO: Figure out how much entropy is actually needed, and reduce the current number of bytes if possible if doing
# so will result in a performance improvement.
if max_length:
assert max_length > 0

ret = hashlib.md5(image or os.urandom(224)).hexdigest()
return ret if not max_length else ret[:max_length]

def start_thread(target, *args):
t = threading.Thread(target=target, args=args)
t.setDaemon(True)
t.start()

def send_mail(subject, message, from_email, recipient_emails):
import django.core.mail
try:
logging.debug('Sending mail to: %s' % recipient_emails)
logging.debug('Message: %s' % message)
django.core.mail.send_mail(subject, message, from_email, recipient_emails)
except Exception, e:
# TODO: Raise error again so that more information is included in the logs?
logging.error('Error sending message [%s] from %s to %s %s' % (subject, from_email, recipient_emails, e))

def send_mail_in_thread(subject, message, from_email, recipient_emails):
start_thread(send_mail, subject, message, from_email, recipient_emails)

def send_mail_using_template(subject, template_name, from_email, recipient_emails, context_map, in_thread=False):
t = get_template(template_name)
message = t.render(Context(context_map))
if in_thread:
return send_mail_in_thread(subject, message, from_email, recipient_emails)
else:
return send_mail(subject, message, from_email, recipient_emails)

def utc_to_pacific(timestamp):
return timestamp.replace(tzinfo=utc).astimezone(Pacific)

def pacific_to_utc(timestamp):
return timestamp.replace(tzinfo=Pacific).astimezone(utc)
29 changes: 29 additions & 0 deletions django_common/http.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from StringIO import StringIO

from django.core.urlresolvers import reverse
from django.http import HttpResponse, HttpResponsePermanentRedirect, HttpResponseRedirect, Http404
from django.utils import simplejson


class JsonResponse(HttpResponse):
def __init__(self, data={ }, errors=[ ], success=True):
"""
data is a map, errors a list
"""
json = json_response(data=data, errors=errors, success=success)
super(JsonResponse, self).__init__(json, mimetype='application/json')

def json_response(data={ }, errors=[ ], success=True):
data.update({
'errors': errors,
'success': len(errors) == 0 and success,
})
return simplejson.dumps(data)


class XMLResponse(HttpResponse):
def __init__(self, data):
"""
data is the entire xml body/document
"""
super(XMLResponse, self).__init__(data, mimetype='text/xml')
Loading

0 comments on commit a1af1b0

Please sign in to comment.