Skip to content

Commit a1af1b0

Browse files
author
Sumit Chachra
committed
first commit
0 parents  commit a1af1b0

18 files changed

+704
-0
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
*.pyc
2+
*.DS_Store

AUTHORS

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
http://github.com/Tivix/django-cron/contributors

LICENSE

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
Copyright (c) 2011 Tivix, Inc.
2+
3+
Permission is hereby granted, free of charge, to any person obtaining
4+
a copy of this software and associated documentation files (the
5+
"Software"), to deal in the Software without restriction, including
6+
without limitation the rights to use, copy, modify, merge, publish,
7+
distribute, sublicense, and/or sell copies of the Software, and to
8+
permit persons to whom the Software is furnished to do so, subject to
9+
the following conditions:
10+
11+
The above copyright notice and this permission notice shall be
12+
included in all copies or substantial portions of the Software.
13+
14+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

README.rst

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
===========
2+
django-cron
3+
===========
4+
5+
Overview
6+
--------
7+
8+
Django-common consists of the following things:
9+
10+
- A middleware that makes sure your web-app runs either on or without 'www' in the domain.
11+
12+
- A ``SessionManagerBase`` base class, that helps in keeping your session related code object-oriented and clean! See session.py for usage details.
13+
14+
- Some custom db fields that you can use in your models including a ``UniqueHashField`` and ``RandomHashField``.
15+
16+
- Bunch of helpful functions in helper.py
17+
18+
- A ``render_form_field`` template tag that makes rendering form fields easy and DRY.
19+
20+
- 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.
21+
22+
23+
Installation
24+
------------
25+
26+
- Install django_common (ideally in your virtualenv!) using pip or simply getting a copy of the code and putting it in a
27+
directory in your codebase.
28+
29+
- Add ``django_common`` to your Django settings ``INSTALLED_APPS``::
30+
31+
INSTALLED_APPS = [
32+
# ...
33+
"django_common",
34+
]
35+
36+
- Add the following to your settings.py with appropriate values:
37+
38+
- IS_DEV
39+
- IS_PROD
40+
- DOMAIN_NAME
41+
- WWW_ROOT
42+
43+
- Add ``common_settings`` to your Django settings ``TEMPLATE_CONTEXT_PROCESSORS``::
44+
45+
TEMPLATE_CONTEXT_PROCESSORS = [
46+
# ...
47+
"common_settings",
48+
]
49+
50+
- Add ``WWWRedirectMiddleware`` if required to the list of middlewares::
51+
52+
MIDDLEWARE_CLASSES = [
53+
# ...
54+
"WWWRedirectMiddleware",
55+
]
56+
57+
58+
This open-source app is brought to you by Tivix, Inc. ( http://tivix.com/ )

django_common/__init__.py

Whitespace-only changes.

django_common/auth_backends.py

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import logging
2+
3+
from django.contrib.auth.backends import ModelBackend
4+
from django.contrib.auth.models import User
5+
6+
7+
class EmailBackend(ModelBackend):
8+
def authenticate(self, username=None, password=None):
9+
"""
10+
"username" being passed is really email address and being compared to as such.
11+
"""
12+
try:
13+
user = User.objects.get(email=username)
14+
if user.check_password(password):
15+
return user
16+
except (User.DoesNotExist, User.MultipleObjectsReturned):
17+
logging.warn('Unsuccessful login attempt using username/email: %s' % username)
18+
19+
return None

django_common/context_processors.py

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from django.conf import settings as django_settings
2+
3+
4+
def common_settings(request):
5+
return {
6+
'domain_name': django_settings.DOMAIN_NAME,
7+
'www_root': django_settings.WWW_ROOT,
8+
9+
'is_dev': django_settings.IS_DEV,
10+
'is_prod': django_settings.IS_PROD,
11+
}

django_common/db_fields.py

+140
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
from django.core import exceptions, validators
2+
from django.db.models import fields
3+
from django.template.defaultfilters import slugify
4+
from django.db import models
5+
from django.core.serializers.json import DjangoJSONEncoder
6+
from django.utils import simplejson as json
7+
8+
from south.modelsinspector import add_introspection_rules
9+
from django_common.helper import md5_hash
10+
11+
12+
class JSONField(models.TextField):
13+
"""
14+
JSONField is a generic textfield that neatly serializes/unserializes JSON objects seamlessly
15+
"""
16+
17+
# Used so to_python() is called
18+
__metaclass__ = models.SubfieldBase
19+
20+
def to_python(self, value):
21+
"""Convert our string value to JSON after we load it from the DB"""
22+
23+
if value == "":
24+
return None
25+
26+
try:
27+
if isinstance(value, basestring):
28+
return json.loads(value)
29+
except ValueError:
30+
pass
31+
32+
return value
33+
34+
def get_db_prep_save(self, value):
35+
"""Convert our JSON object to a string before we save"""
36+
37+
if value == "":
38+
return None
39+
40+
if isinstance(value, dict):
41+
value = json.dumps(value, cls=DjangoJSONEncoder)
42+
43+
return super(JSONField, self).get_db_prep_save(value)
44+
45+
class UniqueSlugField(fields.SlugField):
46+
"""
47+
Represents a self-managing sluf field, that makes sure that the slug value is unique on the db table. Slugs by
48+
default get a db_index on them. The "Unique" in the class name is a misnomer since it does support unique=False
49+
50+
@requires "prepopulate_from" in the constructor. This could be a field or a function in the model class which is using
51+
this field
52+
53+
Defaults update_on_save to False
54+
55+
Taken and edited from: http://www.djangosnippets.org/snippets/728/
56+
"""
57+
def __init__(self, prepopulate_from='id', *args, **kwargs):
58+
if kwargs.get('update_on_save'):
59+
self.__update_on_save = kwargs.pop('update_on_save')
60+
else:
61+
self.__update_on_save = False
62+
self.prepopulate_from = prepopulate_from
63+
super(UniqueSlugField, self).__init__(*args, **kwargs)
64+
65+
def pre_save(self, model_instance, add):
66+
prepopulate_field = getattr(model_instance, self.prepopulate_from)
67+
if callable(prepopulate_field):
68+
prepopulate_value = prepopulate_field()
69+
else:
70+
prepopulate_value = prepopulate_field
71+
72+
# if object has an id, and not to update on save, then return existig model instance's slug value
73+
if getattr(model_instance, 'id') and not self.__update_on_save:
74+
return getattr(model_instance, self.name)
75+
76+
# if this is a previously saved object, and current instance's slug is same as one being proposed
77+
if getattr(model_instance, 'id') and getattr(model_instance, self.name) == prepopulate_value:
78+
return self.__set_and_return(model_instance, self.name, slugify(prepopulate_value))
79+
80+
# if a unique slug is not required (not the default of course)
81+
if not self.unique:
82+
return self.__set_and_return(model_instance, self.name, slugify(prepopulate_value))
83+
84+
return self.__unique_slug(model_instance.__class__, model_instance, self.name,
85+
prepopulate_value)
86+
87+
def __unique_slug(self, model, model_instance, slug_field, slug_value):
88+
orig_slug = slug = slugify(slug_value)
89+
index = 1
90+
while True:
91+
try:
92+
model.objects.get(**{slug_field: slug})
93+
index += 1
94+
slug = orig_slug + '-' + str(index)
95+
except model.DoesNotExist:
96+
return self.__set_and_return(model_instance, slug_field, slug)
97+
98+
def __set_and_return(self, model_instance, slug_field, slug):
99+
setattr(model_instance, slug_field, slug)
100+
return slug
101+
102+
add_introspection_rules([
103+
(
104+
[UniqueSlugField], # Class(es) these apply to
105+
[], # Positional arguments (not used)
106+
{ # Keyword argument
107+
"prepopulate_from": ["prepopulate_from", {"default": 'id'}],
108+
},
109+
),
110+
], ["^django_common\.db_fields\.UniqueSlugField"])
111+
112+
113+
class RandomHashField(fields.CharField):
114+
"""
115+
Store a random hash for a certain model field.
116+
117+
@param update_on_save optional field whether to update this hash or not, everytime the model instance is saved
118+
"""
119+
def __init__(self, update_on_save=False, *args, **kwargs):
120+
#TODO: args & kwargs serve no purpose but to make django evolution to work
121+
self.update_on_save = update_on_save
122+
super(fields.CharField, self).__init__(max_length=128, unique=True, blank=False, null=False, db_index=True, default=md5_hash())
123+
124+
def pre_save(self, model_instance, add):
125+
if not add and not self.update_on_save:
126+
return getattr(model_instance, self.name)
127+
128+
random_hash = md5_hash()
129+
setattr(model_instance, self.name, random_hash)
130+
return random_hash
131+
132+
add_introspection_rules([
133+
(
134+
[RandomHashField], # Class(es) these apply to
135+
[], # Positional arguments (not used)
136+
{ # Keyword argument
137+
"update_on_save": ["update_on_save", {"default": False}],
138+
},
139+
),
140+
], ["^django_common\.db_fields\.RandomHashField"])

django_common/helper.py

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
"Some common routines that can be used throughout the code."
2+
import hashlib, os, logging, re, datetime, threading
3+
4+
from django.http import Http404
5+
from django.utils import simplejson
6+
from django.utils.encoding import force_unicode
7+
from django.template import Context, RequestContext
8+
from django.template.loader import get_template
9+
from django.core import exceptions
10+
11+
from apps.common.tzinfo import utc, Pacific
12+
13+
14+
class AppException(exceptions.ValidationError):
15+
"""Base class for exceptions used in our system.
16+
17+
A common base class permits application code to distinguish between exceptions raised in our code from ones raised
18+
in libraries.
19+
"""
20+
pass
21+
22+
class InvalidContentType(AppException):
23+
def __init__(self, file_types, msg=None):
24+
msg = msg or 'Only the following file content types are permitted: %s' % str(file_types)
25+
super(self.__class__, self).__init__(msg)
26+
self.file_types = file_types
27+
28+
class FileTooLarge(AppException):
29+
def __init__(self, file_size_kb, msg=None):
30+
msg = msg or 'Files may not be larger than %s KB' % file_size_kb
31+
super(self.__class__, self).__init__(msg)
32+
self.file_size = file_size_kb
33+
34+
def is_among(value, *possibilities):
35+
"Ensure that the method that has been used for the request is one of the expected ones (e.g., GET or POST)."
36+
for possibility in possibilities:
37+
if value == possibility:
38+
return True
39+
raise Exception, 'A different request value was encountered than expected: %s' % value
40+
41+
def form_errors_serialize(form):
42+
errors = {}
43+
for field in form.fields.keys():
44+
if form.errors.has_key(field):
45+
if form.prefix:
46+
errors['%s-%s' % (form.prefix, field)] = force_unicode(form.errors[field])
47+
else:
48+
errors[field] = force_unicode(form.errors[field])
49+
50+
if form.non_field_errors():
51+
errors['non_field_errors'] = force_unicode(form.non_field_errors())
52+
return {'errors': errors}
53+
54+
def json_response(data={ }, errors=[ ], success=True):
55+
data.update({
56+
'errors': errors,
57+
'success': len(errors) == 0 and success,
58+
})
59+
return simplejson.dumps(data)
60+
61+
def sha224_hash():
62+
return hashlib.sha224(os.urandom(224)).hexdigest()
63+
64+
def sha1_hash():
65+
return hashlib.sha1(os.urandom(224)).hexdigest()
66+
67+
def md5_hash(image=None, max_length=None):
68+
# TODO: Figure out how much entropy is actually needed, and reduce the current number of bytes if possible if doing
69+
# so will result in a performance improvement.
70+
if max_length:
71+
assert max_length > 0
72+
73+
ret = hashlib.md5(image or os.urandom(224)).hexdigest()
74+
return ret if not max_length else ret[:max_length]
75+
76+
def start_thread(target, *args):
77+
t = threading.Thread(target=target, args=args)
78+
t.setDaemon(True)
79+
t.start()
80+
81+
def send_mail(subject, message, from_email, recipient_emails):
82+
import django.core.mail
83+
try:
84+
logging.debug('Sending mail to: %s' % recipient_emails)
85+
logging.debug('Message: %s' % message)
86+
django.core.mail.send_mail(subject, message, from_email, recipient_emails)
87+
except Exception, e:
88+
# TODO: Raise error again so that more information is included in the logs?
89+
logging.error('Error sending message [%s] from %s to %s %s' % (subject, from_email, recipient_emails, e))
90+
91+
def send_mail_in_thread(subject, message, from_email, recipient_emails):
92+
start_thread(send_mail, subject, message, from_email, recipient_emails)
93+
94+
def send_mail_using_template(subject, template_name, from_email, recipient_emails, context_map, in_thread=False):
95+
t = get_template(template_name)
96+
message = t.render(Context(context_map))
97+
if in_thread:
98+
return send_mail_in_thread(subject, message, from_email, recipient_emails)
99+
else:
100+
return send_mail(subject, message, from_email, recipient_emails)
101+
102+
def utc_to_pacific(timestamp):
103+
return timestamp.replace(tzinfo=utc).astimezone(Pacific)
104+
105+
def pacific_to_utc(timestamp):
106+
return timestamp.replace(tzinfo=Pacific).astimezone(utc)

django_common/http.py

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from StringIO import StringIO
2+
3+
from django.core.urlresolvers import reverse
4+
from django.http import HttpResponse, HttpResponsePermanentRedirect, HttpResponseRedirect, Http404
5+
from django.utils import simplejson
6+
7+
8+
class JsonResponse(HttpResponse):
9+
def __init__(self, data={ }, errors=[ ], success=True):
10+
"""
11+
data is a map, errors a list
12+
"""
13+
json = json_response(data=data, errors=errors, success=success)
14+
super(JsonResponse, self).__init__(json, mimetype='application/json')
15+
16+
def json_response(data={ }, errors=[ ], success=True):
17+
data.update({
18+
'errors': errors,
19+
'success': len(errors) == 0 and success,
20+
})
21+
return simplejson.dumps(data)
22+
23+
24+
class XMLResponse(HttpResponse):
25+
def __init__(self, data):
26+
"""
27+
data is the entire xml body/document
28+
"""
29+
super(XMLResponse, self).__init__(data, mimetype='text/xml')

0 commit comments

Comments
 (0)