Merge branch 'master' of github.com:Gluejar/regluit
commit
2aa91d88de
|
@ -0,0 +1,3 @@
|
|||
recursive-include django_extensions/conf *.tmpl
|
||||
recursive-include django_extensions/templates *.html
|
||||
recursive-include django_extensions/media *
|
|
@ -0,0 +1,21 @@
|
|||
Metadata-Version: 1.0
|
||||
Name: django-extensions
|
||||
Version: 0.7.1
|
||||
Summary: Extensions for Django
|
||||
Home-page: http://github.com/django-extensions/django-extensions
|
||||
Author: Bas van Oostveen
|
||||
Author-email: v.oostveen@gmail.com
|
||||
License: New BSD License
|
||||
Description: django-extensions bundles several useful
|
||||
additions for Django projects. See the project page for more information:
|
||||
http://github.com/django-extensions/django-extensions
|
||||
Platform: any
|
||||
Classifier: Development Status :: 4 - Beta
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Environment :: Web Environment
|
||||
Classifier: Framework :: Django
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: BSD License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Topic :: Utilities
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
VERSION = (0, 7, 1)
|
||||
|
||||
# Dynamically calculate the version based on VERSION tuple
|
||||
if len(VERSION) > 2 and VERSION[2] is not None:
|
||||
if isinstance(VERSION[2], int):
|
||||
str_version = "%s.%s.%s" % VERSION[:3]
|
||||
else:
|
||||
str_version = "%s.%s_%s" % VERSION[:3]
|
||||
else:
|
||||
str_version = "%s.%s" % VERSION[:2]
|
||||
|
||||
__version__ = str_version
|
|
@ -0,0 +1,147 @@
|
|||
#
|
||||
# Autocomplete feature for admin panel
|
||||
#
|
||||
# Most of the code has been written by Jannis Leidel and was updated a bit
|
||||
# for django_extensions.
|
||||
# http://jannisleidel.com/2008/11/autocomplete-form-widget-foreignkey-model-fields/
|
||||
#
|
||||
# to_string_function, Satchmo adaptation and some comments added by emes
|
||||
# (Michal Salaban)
|
||||
#
|
||||
|
||||
import operator
|
||||
from django.http import HttpResponse, HttpResponseNotFound
|
||||
from django.db import models
|
||||
from django.db.models.query import QuerySet
|
||||
from django.utils.encoding import smart_str
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.text import get_text_list
|
||||
try:
|
||||
from functools import update_wrapper
|
||||
except ImportError:
|
||||
from django.utils.functional import update_wrapper
|
||||
|
||||
from django_extensions.admin.widgets import ForeignKeySearchInput
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
if 'reversion' in settings.INSTALLED_APPS:
|
||||
from reversion.admin import VersionAdmin as ModelAdmin
|
||||
else:
|
||||
from django.contrib.admin import ModelAdmin
|
||||
|
||||
|
||||
class ForeignKeyAutocompleteAdmin(ModelAdmin):
|
||||
"""Admin class for models using the autocomplete feature.
|
||||
|
||||
There are two additional fields:
|
||||
- related_search_fields: defines fields of managed model that
|
||||
have to be represented by autocomplete input, together with
|
||||
a list of target model fields that are searched for
|
||||
input string, e.g.:
|
||||
|
||||
related_search_fields = {
|
||||
'author': ('first_name', 'email'),
|
||||
}
|
||||
|
||||
- related_string_functions: contains optional functions which
|
||||
take target model instance as only argument and return string
|
||||
representation. By default __unicode__() method of target
|
||||
object is used.
|
||||
"""
|
||||
|
||||
related_search_fields = {}
|
||||
related_string_functions = {}
|
||||
|
||||
def get_urls(self):
|
||||
from django.conf.urls.defaults import patterns, url
|
||||
|
||||
def wrap(view):
|
||||
def wrapper(*args, **kwargs):
|
||||
return self.admin_site.admin_view(view)(*args, **kwargs)
|
||||
return update_wrapper(wrapper, view)
|
||||
|
||||
info = self.model._meta.app_label, self.model._meta.module_name
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'foreignkey_autocomplete/$',
|
||||
wrap(self.foreignkey_autocomplete),
|
||||
name='%s_%s_autocomplete' % info),
|
||||
) + super(ForeignKeyAutocompleteAdmin, self).get_urls()
|
||||
return urlpatterns
|
||||
|
||||
def foreignkey_autocomplete(self, request):
|
||||
"""
|
||||
Searches in the fields of the given related model and returns the
|
||||
result as a simple string to be used by the jQuery Autocomplete plugin
|
||||
"""
|
||||
query = request.GET.get('q', None)
|
||||
app_label = request.GET.get('app_label', None)
|
||||
model_name = request.GET.get('model_name', None)
|
||||
search_fields = request.GET.get('search_fields', None)
|
||||
object_pk = request.GET.get('object_pk', None)
|
||||
try:
|
||||
to_string_function = self.related_string_functions[model_name]
|
||||
except KeyError:
|
||||
to_string_function = lambda x: x.__unicode__()
|
||||
if search_fields and app_label and model_name and (query or object_pk):
|
||||
def construct_search(field_name):
|
||||
# use different lookup methods depending on the notation
|
||||
if field_name.startswith('^'):
|
||||
return "%s__istartswith" % field_name[1:]
|
||||
elif field_name.startswith('='):
|
||||
return "%s__iexact" % field_name[1:]
|
||||
elif field_name.startswith('@'):
|
||||
return "%s__search" % field_name[1:]
|
||||
else:
|
||||
return "%s__icontains" % field_name
|
||||
model = models.get_model(app_label, model_name)
|
||||
queryset = model._default_manager.all()
|
||||
data = ''
|
||||
if query:
|
||||
for bit in query.split():
|
||||
or_queries = [models.Q(**{construct_search(
|
||||
smart_str(field_name)): smart_str(bit)})
|
||||
for field_name in search_fields.split(',')]
|
||||
other_qs = QuerySet(model)
|
||||
other_qs.dup_select_related(queryset)
|
||||
other_qs = other_qs.filter(reduce(operator.or_, or_queries))
|
||||
queryset = queryset & other_qs
|
||||
data = ''.join([u'%s|%s\n' % (
|
||||
to_string_function(f), f.pk) for f in queryset])
|
||||
elif object_pk:
|
||||
try:
|
||||
obj = queryset.get(pk=object_pk)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
data = to_string_function(obj)
|
||||
return HttpResponse(data)
|
||||
return HttpResponseNotFound()
|
||||
|
||||
def get_help_text(self, field_name, model_name):
|
||||
searchable_fields = self.related_search_fields.get(field_name, None)
|
||||
if searchable_fields:
|
||||
help_kwargs = {
|
||||
'model_name': model_name,
|
||||
'field_list': get_text_list(searchable_fields, _('and')),
|
||||
}
|
||||
return _('Use the left field to do %(model_name)s lookups in the fields %(field_list)s.') % help_kwargs
|
||||
return ''
|
||||
|
||||
def formfield_for_dbfield(self, db_field, **kwargs):
|
||||
"""
|
||||
Overrides the default widget for Foreignkey fields if they are
|
||||
specified in the related_search_fields class attribute.
|
||||
"""
|
||||
if (isinstance(db_field, models.ForeignKey) and
|
||||
db_field.name in self.related_search_fields):
|
||||
model_name = db_field.rel.to._meta.object_name
|
||||
help_text = self.get_help_text(db_field.name, model_name)
|
||||
if kwargs.get('help_text'):
|
||||
help_text = u'%s %s' % (kwargs['help_text'], help_text)
|
||||
kwargs['widget'] = ForeignKeySearchInput(db_field.rel,
|
||||
self.related_search_fields[db_field.name])
|
||||
kwargs['help_text'] = help_text
|
||||
return super(ForeignKeyAutocompleteAdmin,
|
||||
self).formfield_for_dbfield(db_field, **kwargs)
|
|
@ -0,0 +1,77 @@
|
|||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.text import truncate_words
|
||||
from django.template.loader import render_to_string
|
||||
from django.contrib.admin.widgets import ForeignKeyRawIdWidget
|
||||
|
||||
|
||||
class ForeignKeySearchInput(ForeignKeyRawIdWidget):
|
||||
"""
|
||||
A Widget for displaying ForeignKeys in an autocomplete search input
|
||||
instead in a <select> box.
|
||||
"""
|
||||
# Set in subclass to render the widget with a different template
|
||||
widget_template = None
|
||||
# Set this to the patch of the search view
|
||||
search_path = '../foreignkey_autocomplete/'
|
||||
|
||||
class Media:
|
||||
css = {
|
||||
'all': ('django_extensions/css/jquery.autocomplete.css',)
|
||||
}
|
||||
js = (
|
||||
'django_extensions/js/jquery.js',
|
||||
'django_extensions/js/jquery.bgiframe.min.js',
|
||||
'django_extensions/js/jquery.ajaxQueue.js',
|
||||
'django_extensions/js/jquery.autocomplete.js',
|
||||
)
|
||||
|
||||
def label_for_value(self, value):
|
||||
key = self.rel.get_related_field().name
|
||||
obj = self.rel.to._default_manager.get(**{key: value})
|
||||
return truncate_words(obj, 14)
|
||||
|
||||
def __init__(self, rel, search_fields, attrs=None):
|
||||
self.search_fields = search_fields
|
||||
super(ForeignKeySearchInput, self).__init__(rel, attrs)
|
||||
|
||||
def render(self, name, value, attrs=None):
|
||||
if attrs is None:
|
||||
attrs = {}
|
||||
output = [super(ForeignKeySearchInput, self).render(name, value, attrs)]
|
||||
opts = self.rel.to._meta
|
||||
app_label = opts.app_label
|
||||
model_name = opts.object_name.lower()
|
||||
related_url = '../../../%s/%s/' % (app_label, model_name)
|
||||
params = self.url_parameters()
|
||||
if params:
|
||||
url = '?' + '&'.join(['%s=%s' % (k, v) for k, v in params.items()])
|
||||
else:
|
||||
url = ''
|
||||
if not 'class' in attrs:
|
||||
attrs['class'] = 'vForeignKeyRawIdAdminField'
|
||||
# Call the TextInput render method directly to have more control
|
||||
output = [forms.TextInput.render(self, name, value, attrs)]
|
||||
if value:
|
||||
label = self.label_for_value(value)
|
||||
else:
|
||||
label = u''
|
||||
context = {
|
||||
'url': url,
|
||||
'related_url': related_url,
|
||||
'admin_media_prefix': settings.ADMIN_MEDIA_PREFIX,
|
||||
'search_path': self.search_path,
|
||||
'search_fields': ','.join(self.search_fields),
|
||||
'model_name': model_name,
|
||||
'app_label': app_label,
|
||||
'label': label,
|
||||
'name': name,
|
||||
}
|
||||
output.append(render_to_string(self.widget_template or (
|
||||
'django_extensions/widgets/%s/%s/foreignkey_searchinput.html' % (app_label, model_name),
|
||||
'django_extensions/widgets/%s/foreignkey_searchinput.html' % app_label,
|
||||
'django_extensions/widgets/foreignkey_searchinput.html',
|
||||
), context))
|
||||
output.reverse()
|
||||
return mark_safe(u''.join(output))
|
|
@ -0,0 +1,275 @@
|
|||
"""
|
||||
Django Extensions additional model fields
|
||||
"""
|
||||
|
||||
from django.template.defaultfilters import slugify
|
||||
from django.db.models import DateTimeField, CharField, SlugField
|
||||
import datetime
|
||||
import re
|
||||
|
||||
try:
|
||||
import uuid
|
||||
except ImportError:
|
||||
from django_extensions.utils import uuid
|
||||
|
||||
|
||||
class AutoSlugField(SlugField):
|
||||
""" AutoSlugField
|
||||
|
||||
By default, sets editable=False, blank=True.
|
||||
|
||||
Required arguments:
|
||||
|
||||
populate_from
|
||||
Specifies which field or list of fields the slug is populated from.
|
||||
|
||||
Optional arguments:
|
||||
|
||||
separator
|
||||
Defines the used separator (default: '-')
|
||||
|
||||
overwrite
|
||||
If set to True, overwrites the slug on every save (default: False)
|
||||
|
||||
Inspired by SmileyChris' Unique Slugify snippet:
|
||||
http://www.djangosnippets.org/snippets/690/
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.setdefault('blank', True)
|
||||
kwargs.setdefault('editable', False)
|
||||
|
||||
populate_from = kwargs.pop('populate_from', None)
|
||||
if populate_from is None:
|
||||
raise ValueError("missing 'populate_from' argument")
|
||||
else:
|
||||
self._populate_from = populate_from
|
||||
self.separator = kwargs.pop('separator', u'-')
|
||||
self.overwrite = kwargs.pop('overwrite', False)
|
||||
self.allow_duplicates = kwargs.pop('allow_duplicates', False)
|
||||
super(AutoSlugField, self).__init__(*args, **kwargs)
|
||||
|
||||
def _slug_strip(self, value):
|
||||
"""
|
||||
Cleans up a slug by removing slug separator characters that occur at
|
||||
the beginning or end of a slug.
|
||||
|
||||
If an alternate separator is used, it will also replace any instances
|
||||
of the default '-' separator with the new separator.
|
||||
"""
|
||||
re_sep = '(?:-|%s)' % re.escape(self.separator)
|
||||
value = re.sub('%s+' % re_sep, self.separator, value)
|
||||
return re.sub(r'^%s+|%s+$' % (re_sep, re_sep), '', value)
|
||||
|
||||
def slugify_func(self, content):
|
||||
if content:
|
||||
return slugify(content)
|
||||
return ''
|
||||
|
||||
def create_slug(self, model_instance, add):
|
||||
# get fields to populate from and slug field to set
|
||||
if not isinstance(self._populate_from, (list, tuple)):
|
||||
self._populate_from = (self._populate_from, )
|
||||
slug_field = model_instance._meta.get_field(self.attname)
|
||||
|
||||
if add or self.overwrite:
|
||||
# slugify the original field content and set next step to 2
|
||||
slug_for_field = lambda field: self.slugify_func(getattr(model_instance, field))
|
||||
slug = self.separator.join(map(slug_for_field, self._populate_from))
|
||||
next = 2
|
||||
else:
|
||||
# get slug from the current model instance and calculate next
|
||||
# step from its number, clean-up
|
||||
slug = self._slug_strip(getattr(model_instance, self.attname))
|
||||
next = slug.split(self.separator)[-1]
|
||||
if next.isdigit() and not self.allow_duplicates:
|
||||
slug = self.separator.join(slug.split(self.separator)[:-1])
|
||||
next = int(next)
|
||||
else:
|
||||
next = 2
|
||||
|
||||
# strip slug depending on max_length attribute of the slug field
|
||||
# and clean-up
|
||||
slug_len = slug_field.max_length
|
||||
if slug_len:
|
||||
slug = slug[:slug_len]
|
||||
slug = self._slug_strip(slug)
|
||||
original_slug = slug
|
||||
|
||||
if self.allow_duplicates:
|
||||
return slug
|
||||
|
||||
# exclude the current model instance from the queryset used in finding
|
||||
# the next valid slug
|
||||
queryset = model_instance.__class__._default_manager.all()
|
||||
if model_instance.pk:
|
||||
queryset = queryset.exclude(pk=model_instance.pk)
|
||||
|
||||
# form a kwarg dict used to impliment any unique_together contraints
|
||||
kwargs = {}
|
||||
for params in model_instance._meta.unique_together:
|
||||
if self.attname in params:
|
||||
for param in params:
|
||||
kwargs[param] = getattr(model_instance, param, None)
|
||||
kwargs[self.attname] = slug
|
||||
|
||||
# increases the number while searching for the next valid slug
|
||||
# depending on the given slug, clean-up
|
||||
while not slug or queryset.filter(**kwargs):
|
||||
slug = original_slug
|
||||
end = '%s%s' % (self.separator, next)
|
||||
end_len = len(end)
|
||||
if slug_len and len(slug) + end_len > slug_len:
|
||||
slug = slug[:slug_len - end_len]
|
||||
slug = self._slug_strip(slug)
|
||||
slug = '%s%s' % (slug, end)
|
||||
kwargs[self.attname] = slug
|
||||
next += 1
|
||||
return slug
|
||||
|
||||
def pre_save(self, model_instance, add):
|
||||
value = unicode(self.create_slug(model_instance, add))
|
||||
setattr(model_instance, self.attname, value)
|
||||
return value
|
||||
|
||||
def get_internal_type(self):
|
||||
return "SlugField"
|
||||
|
||||
def south_field_triple(self):
|
||||
"Returns a suitable description of this field for South."
|
||||
# We'll just introspect the _actual_ field.
|
||||
from south.modelsinspector import introspector
|
||||
field_class = '%s.AutoSlugField' % self.__module__
|
||||
args, kwargs = introspector(self)
|
||||
kwargs.update({
|
||||
'populate_from': repr(self._populate_from),
|
||||
'separator': repr(self.separator),
|
||||
'overwrite': repr(self.overwrite),
|
||||
'allow_duplicates': repr(self.allow_duplicates),
|
||||
})
|
||||
# That's our definition!
|
||||
return (field_class, args, kwargs)
|
||||
|
||||
|
||||
class CreationDateTimeField(DateTimeField):
|
||||
""" CreationDateTimeField
|
||||
|
||||
By default, sets editable=False, blank=True, default=datetime.now
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.setdefault('editable', False)
|
||||
kwargs.setdefault('blank', True)
|
||||
kwargs.setdefault('default', datetime.datetime.now)
|
||||
DateTimeField.__init__(self, *args, **kwargs)
|
||||
|
||||
def get_internal_type(self):
|
||||
return "DateTimeField"
|
||||
|
||||
def south_field_triple(self):
|
||||
"Returns a suitable description of this field for South."
|
||||
# We'll just introspect ourselves, since we inherit.
|
||||
from south.modelsinspector import introspector
|
||||
field_class = "django.db.models.fields.DateTimeField"
|
||||
args, kwargs = introspector(self)
|
||||
return (field_class, args, kwargs)
|
||||
|
||||
|
||||
class ModificationDateTimeField(CreationDateTimeField):
|
||||
""" ModificationDateTimeField
|
||||
|
||||
By default, sets editable=False, blank=True, default=datetime.now
|
||||
|
||||
Sets value to datetime.now() on each save of the model.
|
||||
"""
|
||||
|
||||
def pre_save(self, model, add):
|
||||
value = datetime.datetime.now()
|
||||
setattr(model, self.attname, value)
|
||||
return value
|
||||
|
||||
def get_internal_type(self):
|
||||
return "DateTimeField"
|
||||
|
||||
def south_field_triple(self):
|
||||
"Returns a suitable description of this field for South."
|
||||
# We'll just introspect ourselves, since we inherit.
|
||||
from south.modelsinspector import introspector
|
||||
field_class = "django.db.models.fields.DateTimeField"
|
||||
args, kwargs = introspector(self)
|
||||
return (field_class, args, kwargs)
|
||||
|
||||
|
||||
class UUIDVersionError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class UUIDField(CharField):
|
||||
""" UUIDField
|
||||
|
||||
By default uses UUID version 1 (generate from host ID, sequence number and current time)
|
||||
|
||||
The field support all uuid versions which are natively supported by the uuid python module.
|
||||
For more information see: http://docs.python.org/lib/module-uuid.html
|
||||
"""
|
||||
|
||||
def __init__(self, verbose_name=None, name=None, auto=True, version=1, node=None, clock_seq=None, namespace=None, **kwargs):
|
||||
kwargs['max_length'] = 36
|
||||
if auto:
|
||||
kwargs['blank'] = True
|
||||
kwargs.setdefault('editable', False)
|
||||
self.auto = auto
|
||||
self.version = version
|
||||
if version == 1:
|
||||
self.node, self.clock_seq = node, clock_seq
|
||||
elif version == 3 or version == 5:
|
||||
self.namespace, self.name = namespace, name
|
||||
CharField.__init__(self, verbose_name, name, **kwargs)
|
||||
|
||||
def get_internal_type(self):
|
||||
return CharField.__name__
|
||||
|
||||
def contribute_to_class(self, cls, name):
|
||||
if self.primary_key:
|
||||
assert not cls._meta.has_auto_field, \
|
||||
"A model can't have more than one AutoField: %s %s %s; have %s" % \
|
||||
(self, cls, name, cls._meta.auto_field)
|
||||
super(UUIDField, self).contribute_to_class(cls, name)
|
||||
cls._meta.has_auto_field = True
|
||||
cls._meta.auto_field = self
|
||||
else:
|
||||
super(UUIDField, self).contribute_to_class(cls, name)
|
||||
|
||||
def create_uuid(self):
|
||||
if not self.version or self.version == 4:
|
||||
return uuid.uuid4()
|
||||
elif self.version == 1:
|
||||
return uuid.uuid1(self.node, self.clock_seq)
|
||||
elif self.version == 2:
|
||||
raise UUIDVersionError("UUID version 2 is not supported.")
|
||||
elif self.version == 3:
|
||||
return uuid.uuid3(self.namespace, self.name)
|
||||
elif self.version == 5:
|
||||
return uuid.uuid5(self.namespace, self.name)
|
||||
else:
|
||||
raise UUIDVersionError("UUID version %s is not valid." % self.version)
|
||||
|
||||
def pre_save(self, model_instance, add):
|
||||
if self.auto and add:
|
||||
value = unicode(self.create_uuid())
|
||||
setattr(model_instance, self.attname, value)
|
||||
return value
|
||||
else:
|
||||
value = super(UUIDField, self).pre_save(model_instance, add)
|
||||
if self.auto and not value:
|
||||
value = unicode(self.create_uuid())
|
||||
setattr(model_instance, self.attname, value)
|
||||
return value
|
||||
|
||||
def south_field_triple(self):
|
||||
"Returns a suitable description of this field for South."
|
||||
# We'll just introspect the _actual_ field.
|
||||
from south.modelsinspector import introspector
|
||||
field_class = "django.db.models.fields.CharField"
|
||||
args, kwargs = introspector(self)
|
||||
# That's our definition!
|
||||
return (field_class, args, kwargs)
|
|
@ -0,0 +1,79 @@
|
|||
from django.db import models
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
|
||||
try:
|
||||
from keyczar import keyczar
|
||||
except ImportError:
|
||||
raise ImportError('Using an encrypted field requires the Keyczar module. '
|
||||
'You can obtain Keyczar from http://www.keyczar.org/.')
|
||||
|
||||
|
||||
class BaseEncryptedField(models.Field):
|
||||
prefix = 'enc_str:::'
|
||||
def __init__(self, *args, **kwargs):
|
||||
if not hasattr(settings, 'ENCRYPTED_FIELD_KEYS_DIR'):
|
||||
raise ImproperlyConfigured('You must set the '
|
||||
'ENCRYPTED_FIELD_KEYS_DIR setting to your Keyczar keys directory.')
|
||||
self.crypt = keyczar.Crypter.Read(settings.ENCRYPTED_FIELD_KEYS_DIR)
|
||||
super(BaseEncryptedField, self).__init__(*args, **kwargs)
|
||||
|
||||
def to_python(self, value):
|
||||
if value and (value.startswith(self.prefix)):
|
||||
retval = self.crypt.Decrypt(value[len(self.prefix):])
|
||||
else:
|
||||
retval = value
|
||||
return retval
|
||||
|
||||
def get_db_prep_value(self, value, connection, prepared=False):
|
||||
if value and not value.startswith(self.prefix):
|
||||
value = self.prefix + self.crypt.Encrypt(value)
|
||||
return value
|
||||
|
||||
|
||||
class EncryptedTextField(BaseEncryptedField):
|
||||
__metaclass__ = models.SubfieldBase
|
||||
|
||||
def get_internal_type(self):
|
||||
return 'TextField'
|
||||
|
||||
def formfield(self, **kwargs):
|
||||
defaults = {'widget': forms.Textarea}
|
||||
defaults.update(kwargs)
|
||||
return super(EncryptedTextField, self).formfield(**defaults)
|
||||
|
||||
def south_field_triple(self):
|
||||
"Returns a suitable description of this field for South."
|
||||
# We'll just introspect the _actual_ field.
|
||||
from south.modelsinspector import introspector
|
||||
field_class = "django.db.models.fields.TextField"
|
||||
args, kwargs = introspector(self)
|
||||
# That's our definition!
|
||||
return (field_class, args, kwargs)
|
||||
|
||||
|
||||
class EncryptedCharField(BaseEncryptedField):
|
||||
__metaclass__ = models.SubfieldBase
|
||||
|
||||
def __init__(self, max_length=None, *args, **kwargs):
|
||||
if max_length:
|
||||
max_length += len(self.prefix)
|
||||
super(EncryptedCharField, self).__init__(max_length=max_length, *args, **kwargs)
|
||||
|
||||
def get_internal_type(self):
|
||||
return "CharField"
|
||||
|
||||
def formfield(self, **kwargs):
|
||||
defaults = {'max_length': self.max_length}
|
||||
defaults.update(kwargs)
|
||||
return super(EncryptedCharField, self).formfield(**defaults)
|
||||
|
||||
def south_field_triple(self):
|
||||
"Returns a suitable description of this field for South."
|
||||
# We'll just introspect the _actual_ field.
|
||||
from south.modelsinspector import introspector
|
||||
field_class = "django.db.models.fields.CharField"
|
||||
args, kwargs = introspector(self)
|
||||
# That's our definition!
|
||||
return (field_class, args, kwargs)
|
|
@ -0,0 +1,101 @@
|
|||
"""
|
||||
JSONField automatically serializes most Python terms to JSON data.
|
||||
Creates a TEXT field with a default value of "{}". See test_json.py for
|
||||
more information.
|
||||
|
||||
from django.db import models
|
||||
from django_extensions.db.fields import json
|
||||
|
||||
class LOL(models.Model):
|
||||
extra = json.JSONField()
|
||||
"""
|
||||
|
||||
import datetime
|
||||
from decimal import Decimal
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
from django.utils import simplejson
|
||||
from django.utils.encoding import smart_unicode
|
||||
|
||||
|
||||
class JSONEncoder(simplejson.JSONEncoder):
|
||||
def default(self, obj):
|
||||
if isinstance(obj, Decimal):
|
||||
return str(obj)
|
||||
elif isinstance(obj, datetime.datetime):
|
||||
assert settings.TIME_ZONE == 'UTC'
|
||||
return obj.strftime('%Y-%m-%dT%H:%M:%SZ')
|
||||
return simplejson.JSONEncoder.default(self, obj)
|
||||
|
||||
|
||||
def dumps(value):
|
||||
return JSONEncoder().encode(value)
|
||||
|
||||
|
||||
def loads(txt):
|
||||
value = simplejson.loads(
|
||||
txt,
|
||||
parse_float=Decimal,
|
||||
encoding=settings.DEFAULT_CHARSET
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
class JSONDict(dict):
|
||||
"""
|
||||
Hack so repr() called by dumpdata will output JSON instead of
|
||||
Python formatted data. This way fixtures will work!
|
||||
"""
|
||||
def __repr__(self):
|
||||
return dumps(self)
|
||||
|
||||
class JSONList(list):
|
||||
"""
|
||||
As above
|
||||
"""
|
||||
def __repr__(self):
|
||||
return dumps(self)
|
||||
|
||||
|
||||
class JSONField(models.TextField):
|
||||
"""JSONField is a generic textfield that neatly serializes/unserializes
|
||||
JSON objects seamlessly. Main thingy must be a dict object."""
|
||||
|
||||
# Used so to_python() is called
|
||||
__metaclass__ = models.SubfieldBase
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if 'default' not in kwargs:
|
||||
kwargs['default'] = '{}'
|
||||
models.TextField.__init__(self, *args, **kwargs)
|
||||
|
||||
def to_python(self, value):
|
||||
"""Convert our string value to JSON after we load it from the DB"""
|
||||
if not value:
|
||||
return {}
|
||||
elif isinstance(value, basestring):
|
||||
res = loads(value)
|
||||
if isinstance(res, dict):
|
||||
return JSONDict(**res)
|
||||
else:
|
||||
return JSONList(res)
|
||||
|
||||
else:
|
||||
return value
|
||||
|
||||
def get_db_prep_save(self, value, connection):
|
||||
"""Convert our JSON object to a string before we save"""
|
||||
if not isinstance(value, (list, dict)):
|
||||
return super(JSONField, self).get_db_prep_save("", connection)
|
||||
else:
|
||||
return super(JSONField, self).get_db_prep_save(dumps(value),
|
||||
connection)
|
||||
|
||||
def south_field_triple(self):
|
||||
"Returns a suitable description of this field for South."
|
||||
# We'll just introspect the _actual_ field.
|
||||
from south.modelsinspector import introspector
|
||||
field_class = "django.db.models.fields.TextField"
|
||||
args, kwargs = introspector(self)
|
||||
# That's our definition!
|
||||
return (field_class, args, kwargs)
|
|
@ -0,0 +1,75 @@
|
|||
"""
|
||||
Django Extensions abstract base model classes.
|
||||
"""
|
||||
import datetime
|
||||
from django.db import models
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django_extensions.db.fields import (ModificationDateTimeField,
|
||||
CreationDateTimeField, AutoSlugField)
|
||||
|
||||
|
||||
class TimeStampedModel(models.Model):
|
||||
""" TimeStampedModel
|
||||
An abstract base class model that provides self-managed "created" and
|
||||
"modified" fields.
|
||||
"""
|
||||
created = CreationDateTimeField(_('created'))
|
||||
modified = ModificationDateTimeField(_('modified'))
|
||||
|
||||
class Meta:
|
||||
get_latest_by = 'modified'
|
||||
ordering = ('-modified', '-created',)
|
||||
abstract = True
|
||||
|
||||
|
||||
class TitleSlugDescriptionModel(models.Model):
|
||||
""" TitleSlugDescriptionModel
|
||||
An abstract base class model that provides title and description fields
|
||||
and a self-managed "slug" field that populates from the title.
|
||||
"""
|
||||
title = models.CharField(_('title'), max_length=255)
|
||||
slug = AutoSlugField(_('slug'), populate_from='title')
|
||||
description = models.TextField(_('description'), blank=True, null=True)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class ActivatorModelManager(models.Manager):
|
||||
""" ActivatorModelManager
|
||||
Manager to return instances of ActivatorModel: SomeModel.objects.active() / .inactive()
|
||||
"""
|
||||
def active(self):
|
||||
""" Returns active instances of ActivatorModel: SomeModel.objects.active() """
|
||||
return self.get_query_set().filter(status=ActivatorModel.ACTIVE_STATUS)
|
||||
|
||||
def inactive(self):
|
||||
""" Returns inactive instances of ActivatorModel: SomeModel.objects.inactive() """
|
||||
return self.get_query_set().filter(status=ActivatorModel.INACTIVE_STATUS)
|
||||
|
||||
|
||||
class ActivatorModel(models.Model):
|
||||
""" ActivatorModel
|
||||
An abstract base class model that provides activate and deactivate fields.
|
||||
"""
|
||||
INACTIVE_STATUS, ACTIVE_STATUS = range(2)
|
||||
STATUS_CHOICES = (
|
||||
(INACTIVE_STATUS, _('Inactive')),
|
||||
(ACTIVE_STATUS, _('Active')),
|
||||
)
|
||||
status = models.IntegerField(_('status'), choices=STATUS_CHOICES,
|
||||
default=ACTIVE_STATUS)
|
||||
activate_date = models.DateTimeField(blank=True, null=True,
|
||||
help_text=_('keep empty for an immediate activation'))
|
||||
deactivate_date = models.DateTimeField(blank=True, null=True,
|
||||
help_text=_('keep empty for indefinite activation'))
|
||||
objects = ActivatorModelManager()
|
||||
|
||||
class Meta:
|
||||
ordering = ('status', '-activate_date',)
|
||||
abstract = True
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.activate_date:
|
||||
self.activate_date = datetime.datetime.now()
|
||||
super(ActivatorModel, self).save(*args, **kwargs)
|
|
@ -0,0 +1,26 @@
|
|||
"""
|
||||
Daily cleanup job.
|
||||
|
||||
Can be run as a cronjob to clean out old data from the database (only expired
|
||||
sessions at the moment).
|
||||
"""
|
||||
|
||||
from django_extensions.management.jobs import DailyJob
|
||||
|
||||
|
||||
class Job(DailyJob):
|
||||
help = "Cache (db) cleanup Job"
|
||||
|
||||
def execute(self):
|
||||
from django.conf import settings
|
||||
from django.db import connection
|
||||
import os
|
||||
|
||||
if settings.CACHE_BACKEND.startswith('db://'):
|
||||
os.environ['TZ'] = settings.TIME_ZONE
|
||||
table_name = settings.CACHE_BACKEND[5:]
|
||||
cursor = connection.cursor()
|
||||
cursor.execute("DELETE FROM %s WHERE %s < UTC_TIMESTAMP()" % \
|
||||
(connection.ops.quote_name(table_name),
|
||||
connection.ops.quote_name('expires')))
|
||||
transaction.commit_unless_managed()
|
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
Daily cleanup job.
|
||||
|
||||
Can be run as a cronjob to clean out old data from the database (only expired
|
||||
sessions at the moment).
|
||||
"""
|
||||
|
||||
from django_extensions.management.jobs import DailyJob
|
||||
|
||||
|
||||
class Job(DailyJob):
|
||||
help = "Django Daily Cleanup Job"
|
||||
|
||||
def execute(self):
|
||||
from django.core import management
|
||||
management.call_command("cleanup")
|
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
Sets up the terminal color scheme.
|
||||
"""
|
||||
|
||||
from django.core.management import color
|
||||
from django.utils import termcolors
|
||||
|
||||
|
||||
def color_style():
|
||||
style = color.color_style()
|
||||
if color.supports_color():
|
||||
style.URL = termcolors.make_style(fg='green', opts=('bold',))
|
||||
style.MODULE = termcolors.make_style(fg='yellow')
|
||||
style.MODULE_NAME = termcolors.make_style(opts=('bold',))
|
||||
style.URL_NAME = termcolors.make_style(fg='red')
|
||||
return style
|
|
@ -0,0 +1,42 @@
|
|||
from django.core.management.base import NoArgsCommand
|
||||
from django_extensions.management.utils import get_project_root
|
||||
from random import choice
|
||||
from optparse import make_option
|
||||
from os.path import join as _j
|
||||
import os
|
||||
|
||||
|
||||
class Command(NoArgsCommand):
|
||||
option_list = NoArgsCommand.option_list + (
|
||||
make_option('--optimize', '-o', '-O', action='store_true', dest='optimize',
|
||||
help='Remove optimized python bytecode files'),
|
||||
make_option('--path', '-p', action='store', dest='path',
|
||||
help='Specify path to recurse into'),
|
||||
)
|
||||
help = "Removes all python bytecode compiled files from the project."
|
||||
|
||||
requires_model_validation = False
|
||||
|
||||
def handle_noargs(self, **options):
|
||||
project_root = options.get("path", None)
|
||||
if not project_root:
|
||||
project_root = get_project_root()
|
||||
exts = options.get("optimize", False) and [".pyc", ".pyo"] or [".pyc"]
|
||||
verbose = int(options.get("verbosity", 1)) > 1
|
||||
|
||||
for root, dirs, files in os.walk(project_root):
|
||||
for file in files:
|
||||
ext = os.path.splitext(file)[1]
|
||||
if ext in exts:
|
||||
full_path = _j(root, file)
|
||||
if verbose:
|
||||
print full_path
|
||||
os.remove(full_path)
|
||||
|
||||
# Backwards compatibility for Django r9110
|
||||
if not [opt for opt in Command.option_list if opt.dest == 'verbosity']:
|
||||
Command.option_list += (
|
||||
make_option('--verbosity', '-v', action="store", dest="verbosity",
|
||||
default='1', type='choice', choices=['0', '1', '2'],
|
||||
help="Verbosity level; 0=minimal output, 1=normal output, 2=all output"),
|
||||
)
|
|
@ -0,0 +1,40 @@
|
|||
from django.core.management.base import NoArgsCommand
|
||||
from django_extensions.management.utils import get_project_root
|
||||
from random import choice
|
||||
from optparse import make_option
|
||||
from os.path import join as _j
|
||||
import py_compile
|
||||
import os
|
||||
|
||||
|
||||
class Command(NoArgsCommand):
|
||||
option_list = NoArgsCommand.option_list + (
|
||||
make_option('--path', '-p', action='store', dest='path',
|
||||
help='Specify path to recurse into'),
|
||||
)
|
||||
help = "Compile python bytecode files for the project."
|
||||
|
||||
requires_model_validation = False
|
||||
|
||||
def handle_noargs(self, **options):
|
||||
project_root = options.get("path", None)
|
||||
if not project_root:
|
||||
project_root = get_project_root()
|
||||
verbose = int(options.get("verbosity", 1)) > 1
|
||||
|
||||
for root, dirs, files in os.walk(project_root):
|
||||
for file in files:
|
||||
ext = os.path.splitext(file)[1]
|
||||
if ext == ".py":
|
||||
full_path = _j(root, file)
|
||||
if verbose:
|
||||
print "%sc" % full_path
|
||||
py_compile.compile(full_path)
|
||||
|
||||
# Backwards compatibility for Django r9110
|
||||
if not [opt for opt in Command.option_list if opt.dest == 'verbosity']:
|
||||
Command.option_list += (
|
||||
make_option('--verbosity', '-v', action="store", dest="verbosity",
|
||||
default='1', type='choice', choices=['0', '1', '2'],
|
||||
help="Verbosity level; 0=minimal output, 1=normal output, 2=all output"),
|
||||
)
|
|
@ -0,0 +1,146 @@
|
|||
import os
|
||||
import re
|
||||
import django_extensions
|
||||
from django.conf import settings
|
||||
from django.db import connection
|
||||
from django.core.management.base import CommandError, LabelCommand, _make_writeable
|
||||
from django.template import Template, Context
|
||||
from django_extensions.settings import REPLACEMENTS
|
||||
from django_extensions.utils.dia2django import dia2django
|
||||
from optparse import make_option
|
||||
|
||||
|
||||
class Command(LabelCommand):
|
||||
option_list = LabelCommand.option_list + (
|
||||
make_option('--template', '-t', action='store', dest='app_template',
|
||||
help='The path to the app template'),
|
||||
make_option('--parent_path', '-p', action='store', dest='parent_path',
|
||||
help='The parent path of the application to be created'),
|
||||
make_option('-d', action='store_true', dest='dia_parse',
|
||||
help='Generate model.py and admin.py from [APP_NAME].dia file'),
|
||||
make_option('--diagram', action='store', dest='dia_path',
|
||||
help='The diagram path of the app to be created. -d is implied'),
|
||||
)
|
||||
|
||||
help = ("Creates an application directory structure for the specified "
|
||||
"application name.")
|
||||
args = "APP_NAME"
|
||||
label = 'application name'
|
||||
|
||||
requires_model_validation = False
|
||||
can_import_settings = True
|
||||
|
||||
def handle_label(self, label, **options):
|
||||
project_dir = os.getcwd()
|
||||
project_name = os.path.split(project_dir)[-1]
|
||||
app_name = label
|
||||
app_template = options.get('app_template') or os.path.join(django_extensions.__path__[0], 'conf', 'app_template')
|
||||
app_dir = os.path.join(options.get('parent_path') or project_dir, app_name)
|
||||
dia_path = options.get('dia_path') or os.path.join(project_dir, '%s.dia' % app_name)
|
||||
|
||||
if not os.path.exists(app_template):
|
||||
raise CommandError("The template path, %r, does not exist." % app_template)
|
||||
|
||||
if not re.search(r'^\w+$', label):
|
||||
raise CommandError("%r is not a valid application name. Please use only numbers, letters and underscores." % label)
|
||||
|
||||
dia_parse = options.get('dia_path') or options.get('dia_parse')
|
||||
if dia_parse:
|
||||
if not os.path.exists(dia_path):
|
||||
raise CommandError("The diagram path, %r, does not exist."
|
||||
% dia_path)
|
||||
if app_name in settings.INSTALLED_APPS:
|
||||
raise CommandError("The application %s should not be defined "
|
||||
"in the settings file. Please remove %s now, and add it "
|
||||
"after using this command." % (app_name, app_name))
|
||||
tables = [name for name in connection.introspection.table_names()
|
||||
if name.startswith('%s_' % app_name)]
|
||||
if tables:
|
||||
raise CommandError("%r application has tables in the database. "
|
||||
"Please delete them." % app_name)
|
||||
|
||||
try:
|
||||
os.makedirs(app_dir)
|
||||
except OSError, e:
|
||||
raise CommandError(e)
|
||||
|
||||
copy_template(app_template, app_dir, project_name, app_name)
|
||||
|
||||
if dia_parse:
|
||||
generate_models_and_admin(dia_path, app_dir, project_name, app_name)
|
||||
print "Application %r created." % app_name
|
||||
print "Please add now %r and any other dependent application in " \
|
||||
"settings.INSTALLED_APPS, and run 'manage syncdb'" % app_name
|
||||
|
||||
|
||||
def copy_template(app_template, copy_to, project_name, app_name):
|
||||
"""copies the specified template directory to the copy_to location"""
|
||||
import shutil
|
||||
|
||||
app_template = os.path.normpath(app_template)
|
||||
# walks the template structure and copies it
|
||||
for d, subdirs, files in os.walk(app_template):
|
||||
relative_dir = d[len(app_template) + 1:]
|
||||
d_new = os.path.join(copy_to, relative_dir).replace('app_name', app_name)
|
||||
if relative_dir and not os.path.exists(d_new):
|
||||
os.mkdir(d_new)
|
||||
for i, subdir in enumerate(subdirs):
|
||||
if subdir.startswith('.'):
|
||||
del subdirs[i]
|
||||
replacements = {'app_name': app_name, 'project_name': project_name}
|
||||
replacements.update(REPLACEMENTS)
|
||||
for f in files:
|
||||
if f.endswith('.pyc') or f.startswith('.DS_Store'):
|
||||
continue
|
||||
path_old = os.path.join(d, f)
|
||||
path_new = os.path.join(d_new, f.replace('app_name', app_name))
|
||||
if os.path.exists(path_new):
|
||||
path_new = os.path.join(d_new, f)
|
||||
if os.path.exists(path_new):
|
||||
continue
|
||||
if path_new.endswith('.tmpl'):
|
||||
path_new = path_new[:-5]
|
||||
fp_old = open(path_old, 'r')
|
||||
fp_new = open(path_new, 'w')
|
||||
fp_new.write(Template(fp_old.read()).render(Context(replacements)))
|
||||
fp_old.close()
|
||||
fp_new.close()
|
||||
try:
|
||||
shutil.copymode(path_old, path_new)
|
||||
_make_writeable(path_new)
|
||||
except OSError:
|
||||
sys.stderr.write(style.NOTICE("Notice: Couldn't set permission bits on %s. You're probably using an uncommon filesystem setup. No problem.\n" % path_new))
|
||||
|
||||
|
||||
def generate_models_and_admin(dia_path, app_dir, project_name, app_name):
|
||||
"""Generates the models.py and admin.py files"""
|
||||
|
||||
def format_text(string, indent=False):
|
||||
"""format string in lines of 80 or less characters"""
|
||||
retval = ''
|
||||
while string:
|
||||
line = string[:77]
|
||||
last_space = line.rfind(' ')
|
||||
if last_space != -1 and len(string) > 77:
|
||||
retval += "%s \\\n" % string[:last_space]
|
||||
string = string[last_space + 1:]
|
||||
else:
|
||||
retval += "%s\n" % string
|
||||
string = ''
|
||||
if string and indent:
|
||||
string = ' %s' % string
|
||||
return retval
|
||||
|
||||
model_path = os.path.join(app_dir, 'models.py')
|
||||
admin_path = os.path.join(app_dir, 'admin.py')
|
||||
|
||||
models_txt = 'from django.db import models\n' + dia2django(dia_path)
|
||||
open(model_path, 'w').write(models_txt)
|
||||
|
||||
classes = re.findall('class (\w+)', models_txt)
|
||||
admin_txt = 'from django.contrib.admin import site, ModelAdmin\n' + \
|
||||
format_text('from %s.%s.models import %s' %
|
||||
(project_name, app_name, ', '.join(classes)), indent=True)
|
||||
admin_txt += format_text('\n\n%s' %
|
||||
'\n'.join(map((lambda t: 'site.register(%s)' % t), classes)))
|
||||
open(admin_path, 'w').write(admin_txt)
|
|
@ -0,0 +1,80 @@
|
|||
import os
|
||||
from django.core.management.base import CommandError, AppCommand, _make_writeable
|
||||
from optparse import make_option
|
||||
|
||||
|
||||
class Command(AppCommand):
|
||||
option_list = AppCommand.option_list + (
|
||||
make_option('--name', '-n', action='store', dest='command_name', default='sample',
|
||||
help='The name to use for the management command'),
|
||||
make_option('--base', '-b', action='store', dest='base_command', default='Base',
|
||||
help='The base class used for implementation of this command. Should be one of Base, App, Label, or NoArgs'),
|
||||
)
|
||||
|
||||
help = ("Creates a Django management command directory structure for the given app name"
|
||||
" in the current directory.")
|
||||
args = "[appname]"
|
||||
label = 'application name'
|
||||
|
||||
requires_model_validation = False
|
||||
# Can't import settings during this command, because they haven't
|
||||
# necessarily been created.
|
||||
can_import_settings = True
|
||||
|
||||
def handle_app(self, app, **options):
|
||||
directory = os.getcwd()
|
||||
app_name = app.__name__.split('.')[-2]
|
||||
project_dir = os.path.join(directory, app_name)
|
||||
if not os.path.exists(project_dir):
|
||||
try:
|
||||
os.mkdir(project_dir)
|
||||
except OSError, e:
|
||||
raise CommandError(e)
|
||||
|
||||
copy_template('command_template', project_dir, options.get('command_name'), '%sCommand' % options.get('base_command'))
|
||||
|
||||
|
||||
def copy_template(template_name, copy_to, command_name, base_command):
|
||||
"""copies the specified template directory to the copy_to location"""
|
||||
import django_extensions
|
||||
import re
|
||||
import shutil
|
||||
|
||||
template_dir = os.path.join(django_extensions.__path__[0], 'conf', template_name)
|
||||
|
||||
handle_method = "handle(self, *args, **options)"
|
||||
if base_command == 'AppCommand':
|
||||
handle_method = "handle_app(self, app, **options)"
|
||||
elif base_command == 'LabelCommand':
|
||||
handle_method = "handle_label(self, label, **options)"
|
||||
elif base_command == 'NoArgsCommand':
|
||||
handle_method = "handle_noargs(self, **options)"
|
||||
|
||||
# walks the template structure and copies it
|
||||
for d, subdirs, files in os.walk(template_dir):
|
||||
relative_dir = d[len(template_dir) + 1:]
|
||||
if relative_dir and not os.path.exists(os.path.join(copy_to, relative_dir)):
|
||||
os.mkdir(os.path.join(copy_to, relative_dir))
|
||||
for i, subdir in enumerate(subdirs):
|
||||
if subdir.startswith('.'):
|
||||
del subdirs[i]
|
||||
for f in files:
|
||||
if f.endswith('.pyc') or f.startswith('.DS_Store'):
|
||||
continue
|
||||
path_old = os.path.join(d, f)
|
||||
path_new = os.path.join(copy_to, relative_dir, f.replace('sample', command_name))
|
||||
if os.path.exists(path_new):
|
||||
path_new = os.path.join(copy_to, relative_dir, f)
|
||||
if os.path.exists(path_new):
|
||||
continue
|
||||
path_new = path_new.rstrip(".tmpl")
|
||||
fp_old = open(path_old, 'r')
|
||||
fp_new = open(path_new, 'w')
|
||||
fp_new.write(fp_old.read().replace('{{ command_name }}', command_name).replace('{{ base_command }}', base_command).replace('{{ handle_method }}', handle_method))
|
||||
fp_old.close()
|
||||
fp_new.close()
|
||||
try:
|
||||
shutil.copymode(path_old, path_new)
|
||||
_make_writeable(path_new)
|
||||
except OSError:
|
||||
sys.stderr.write(style.NOTICE("Notice: Couldn't set permission bits on %s. You're probably using an uncommon filesystem setup. No problem.\n" % path_new))
|
|
@ -0,0 +1,56 @@
|
|||
import os
|
||||
import sys
|
||||
from django.core.management.base import CommandError, AppCommand, _make_writeable
|
||||
|
||||
|
||||
class Command(AppCommand):
|
||||
help = ("Creates a Django jobs command directory structure for the given app name in the current directory.")
|
||||
args = "[appname]"
|
||||
label = 'application name'
|
||||
|
||||
requires_model_validation = False
|
||||
# Can't import settings during this command, because they haven't
|
||||
# necessarily been created.
|
||||
can_import_settings = True
|
||||
|
||||
def handle_app(self, app, **options):
|
||||
app_dir = os.path.dirname(app.__file__)
|
||||
copy_template('jobs_template', app_dir)
|
||||
|
||||
|
||||
def copy_template(template_name, copy_to):
|
||||
"""copies the specified template directory to the copy_to location"""
|
||||
import django_extensions
|
||||
import re
|
||||
import shutil
|
||||
|
||||
template_dir = os.path.join(django_extensions.__path__[0], 'conf', template_name)
|
||||
|
||||
# walks the template structure and copies it
|
||||
for d, subdirs, files in os.walk(template_dir):
|
||||
relative_dir = d[len(template_dir) + 1:]
|
||||
if relative_dir and not os.path.exists(os.path.join(copy_to, relative_dir)):
|
||||
os.mkdir(os.path.join(copy_to, relative_dir))
|
||||
for i, subdir in enumerate(subdirs):
|
||||
if subdir.startswith('.'):
|
||||
del subdirs[i]
|
||||
for f in files:
|
||||
if f.endswith('.pyc') or f.startswith('.DS_Store'):
|
||||
continue
|
||||
path_old = os.path.join(d, f)
|
||||
path_new = os.path.join(copy_to, relative_dir, f)
|
||||
if os.path.exists(path_new):
|
||||
path_new = os.path.join(copy_to, relative_dir, f)
|
||||
if os.path.exists(path_new):
|
||||
continue
|
||||
path_new = path_new.rstrip(".tmpl")
|
||||
fp_old = open(path_old, 'r')
|
||||
fp_new = open(path_new, 'w')
|
||||
fp_new.write(fp_old.read())
|
||||
fp_old.close()
|
||||
fp_new.close()
|
||||
try:
|
||||
shutil.copymode(path_old, path_new)
|
||||
_make_writeable(path_new)
|
||||
except OSError:
|
||||
sys.stderr.write(style.NOTICE("Notice: Couldn't set permission bits on %s. You're probably using an uncommon filesystem setup. No problem.\n" % path_new))
|
|
@ -0,0 +1,64 @@
|
|||
from django.core.management.base import LabelCommand, CommandError
|
||||
from django.utils.encoding import force_unicode
|
||||
|
||||
|
||||
class Command(LabelCommand):
|
||||
help = "Outputs the specified model as a form definition to the shell."
|
||||
args = "[app.model]"
|
||||
label = 'application name and model name'
|
||||
|
||||
requires_model_validation = True
|
||||
can_import_settings = True
|
||||
|
||||
def handle_label(self, label, **options):
|
||||
return describe_form(label)
|
||||
|
||||
|
||||
def describe_form(label, fields=None):
|
||||
"""
|
||||
Returns a string describing a form based on the model
|
||||
"""
|
||||
from django.db.models.loading import get_model
|
||||
try:
|
||||
app_name, model_name = label.split('.')[-2:]
|
||||
except (IndexError, ValueError):
|
||||
raise CommandError("Need application and model name in the form: appname.model")
|
||||
model = get_model(app_name, model_name)
|
||||
|
||||
opts = model._meta
|
||||
field_list = []
|
||||
for f in opts.fields + opts.many_to_many:
|
||||
if not f.editable:
|
||||
continue
|
||||
if fields and not f.name in fields:
|
||||
continue
|
||||
formfield = f.formfield()
|
||||
if not '__dict__' in dir(formfield):
|
||||
continue
|
||||
attrs = {}
|
||||
valid_fields = ['required', 'initial', 'max_length', 'min_length', 'max_value', 'min_value', 'max_digits', 'decimal_places', 'choices', 'help_text', 'label']
|
||||
for k, v in formfield.__dict__.items():
|
||||
if k in valid_fields and v != None:
|
||||
# ignore defaults, to minimize verbosity
|
||||
if k == 'required' and v:
|
||||
continue
|
||||
if k == 'help_text' and not v:
|
||||
continue
|
||||
if k == 'widget':
|
||||
attrs[k] = v.__class__
|
||||
elif k in ['help_text', 'label']:
|
||||
attrs[k] = force_unicode(v).strip()
|
||||
else:
|
||||
attrs[k] = v
|
||||
|
||||
params = ', '.join(['%s=%r' % (k, v) for k, v in attrs.items()])
|
||||
field_list.append(' %(field_name)s = forms.%(field_type)s(%(params)s)' % {'field_name': f.name,
|
||||
'field_type': formfield.__class__.__name__,
|
||||
'params': params})
|
||||
return '''
|
||||
from django import forms
|
||||
from %(app_name)s.models import %(object_name)s
|
||||
|
||||
class %(object_name)sForm(forms.Form):
|
||||
%(field_list)s
|
||||
''' % {'app_name': app_name, 'object_name': opts.object_name, 'field_list': '\n'.join(field_list)}
|
|
@ -0,0 +1,509 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: UTF-8 -*-
|
||||
"""
|
||||
Title: Dumpscript management command
|
||||
Project: Hardytools (queryset-refactor version)
|
||||
Author: Will Hardy (http://willhardy.com.au)
|
||||
Date: June 2008
|
||||
Usage: python manage.py dumpscript appname > scripts/scriptname.py
|
||||
$Revision: 217 $
|
||||
|
||||
Description:
|
||||
Generates a Python script that will repopulate the database using objects.
|
||||
The advantage of this approach is that it is easy to understand, and more
|
||||
flexible than directly populating the database, or using XML.
|
||||
|
||||
* It also allows for new defaults to take effect and only transfers what is
|
||||
needed.
|
||||
* If a new database schema has a NEW ATTRIBUTE, it is simply not
|
||||
populated (using a default value will make the transition smooth :)
|
||||
* If a new database schema REMOVES AN ATTRIBUTE, it is simply ignored
|
||||
and the data moves across safely (I'm assuming we don't want this
|
||||
attribute anymore.
|
||||
* Problems may only occur if there is a new model and is now a required
|
||||
ForeignKey for an existing model. But this is easy to fix by editing the
|
||||
populate script :)
|
||||
|
||||
Improvements:
|
||||
See TODOs and FIXMEs scattered throughout :-)
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
from django.db import models
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.utils.encoding import smart_unicode, force_unicode
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Dumps the data as a customised python script.'
|
||||
args = '[appname ...]'
|
||||
|
||||
def handle(self, *app_labels, **options):
|
||||
|
||||
# Get the models we want to export
|
||||
models = get_models(app_labels)
|
||||
|
||||
# A dictionary is created to keep track of all the processed objects,
|
||||
# so that foreign key references can be made using python variable names.
|
||||
# This variable "context" will be passed around like the town bicycle.
|
||||
context = {}
|
||||
|
||||
# Create a dumpscript object and let it format itself as a string
|
||||
print Script(models=models, context=context)
|
||||
|
||||
|
||||
def get_models(app_labels):
|
||||
""" Gets a list of models for the given app labels, with some exceptions.
|
||||
TODO: If a required model is referenced, it should also be included.
|
||||
Or at least discovered with a get_or_create() call.
|
||||
"""
|
||||
|
||||
from django.db.models import get_app, get_apps, get_model
|
||||
from django.db.models import get_models as get_all_models
|
||||
|
||||
# These models are not to be output, e.g. because they can be generated automatically
|
||||
# TODO: This should be "appname.modelname" string
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
EXCLUDED_MODELS = (ContentType, )
|
||||
|
||||
models = []
|
||||
|
||||
# If no app labels are given, return all
|
||||
if not app_labels:
|
||||
for app in get_apps():
|
||||
models += [m for m in get_all_models(app) if m not in EXCLUDED_MODELS]
|
||||
|
||||
# Get all relevant apps
|
||||
for app_label in app_labels:
|
||||
# If a specific model is mentioned, get only that model
|
||||
if "." in app_label:
|
||||
app_label, model_name = app_label.split(".", 1)
|
||||
models.append(get_model(app_label, model_name))
|
||||
# Get all models for a given app
|
||||
else:
|
||||
models += [m for m in get_all_models(get_app(app_label)) if m not in EXCLUDED_MODELS]
|
||||
|
||||
return models
|
||||
|
||||
|
||||
class Code(object):
|
||||
""" A snippet of python script.
|
||||
This keeps track of import statements and can be output to a string.
|
||||
In the future, other features such as custom indentation might be included
|
||||
in this class.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.imports = {}
|
||||
self.indent = -1
|
||||
|
||||
def __str__(self):
|
||||
""" Returns a string representation of this script.
|
||||
"""
|
||||
if self.imports:
|
||||
sys.stderr.write(repr(self.import_lines))
|
||||
return flatten_blocks([""] + self.import_lines + [""] + self.lines, num_indents=self.indent)
|
||||
else:
|
||||
return flatten_blocks(self.lines, num_indents=self.indent)
|
||||
|
||||
def get_import_lines(self):
|
||||
""" Takes the stored imports and converts them to lines
|
||||
"""
|
||||
if self.imports:
|
||||
return ["from %s import %s" % (value, key) for key, value in self.imports.items()]
|
||||
else:
|
||||
return []
|
||||
import_lines = property(get_import_lines)
|
||||
|
||||
|
||||
class ModelCode(Code):
|
||||
" Produces a python script that can recreate data for a given model class. "
|
||||
|
||||
def __init__(self, model, context={}):
|
||||
self.model = model
|
||||
self.context = context
|
||||
self.instances = []
|
||||
self.indent = 0
|
||||
|
||||
def get_imports(self):
|
||||
""" Returns a dictionary of import statements, with the variable being
|
||||
defined as the key.
|
||||
"""
|
||||
return {self.model.__name__: smart_unicode(self.model.__module__)}
|
||||
imports = property(get_imports)
|
||||
|
||||
def get_lines(self):
|
||||
""" Returns a list of lists or strings, representing the code body.
|
||||
Each list is a block, each string is a statement.
|
||||
"""
|
||||
code = []
|
||||
|
||||
for counter, item in enumerate(self.model._default_manager.all()):
|
||||
instance = InstanceCode(instance=item, id=counter + 1, context=self.context)
|
||||
self.instances.append(instance)
|
||||
if instance.waiting_list:
|
||||
code += instance.lines
|
||||
|
||||
# After each instance has been processed, try again.
|
||||
# This allows self referencing fields to work.
|
||||
for instance in self.instances:
|
||||
if instance.waiting_list:
|
||||
code += instance.lines
|
||||
|
||||
return code
|
||||
|
||||
lines = property(get_lines)
|
||||
|
||||
|
||||
class InstanceCode(Code):
|
||||
" Produces a python script that can recreate data for a given model instance. "
|
||||
|
||||
def __init__(self, instance, id, context={}):
|
||||
""" We need the instance in question and an id """
|
||||
|
||||
self.instance = instance
|
||||
self.model = self.instance.__class__
|
||||
self.context = context
|
||||
self.variable_name = "%s_%s" % (self.instance._meta.db_table, id)
|
||||
self.skip_me = None
|
||||
self.instantiated = False
|
||||
|
||||
self.indent = 0
|
||||
self.imports = {}
|
||||
|
||||
self.waiting_list = list(self.model._meta.fields)
|
||||
|
||||
self.many_to_many_waiting_list = {}
|
||||
for field in self.model._meta.many_to_many:
|
||||
self.many_to_many_waiting_list[field] = list(getattr(self.instance, field.name).all())
|
||||
|
||||
def get_lines(self, force=False):
|
||||
""" Returns a list of lists or strings, representing the code body.
|
||||
Each list is a block, each string is a statement.
|
||||
|
||||
force (True or False): if an attribute object cannot be included,
|
||||
it is usually skipped to be processed later. With 'force' set, there
|
||||
will be no waiting: a get_or_create() call is written instead.
|
||||
"""
|
||||
code_lines = []
|
||||
|
||||
# Don't return anything if this is an instance that should be skipped
|
||||
if self.skip():
|
||||
return []
|
||||
|
||||
# Initialise our new object
|
||||
# e.g. model_name_35 = Model()
|
||||
code_lines += self.instantiate()
|
||||
|
||||
# Add each field
|
||||
# e.g. model_name_35.field_one = 1034.91
|
||||
# model_name_35.field_two = "text"
|
||||
code_lines += self.get_waiting_list()
|
||||
|
||||
if force:
|
||||
# TODO: Check that M2M are not affected
|
||||
code_lines += self.get_waiting_list(force=force)
|
||||
|
||||
# Print the save command for our new object
|
||||
# e.g. model_name_35.save()
|
||||
if code_lines:
|
||||
code_lines.append("%s.save()\n" % (self.variable_name))
|
||||
|
||||
code_lines += self.get_many_to_many_lines(force=force)
|
||||
|
||||
return code_lines
|
||||
lines = property(get_lines)
|
||||
|
||||
def skip(self):
|
||||
""" Determine whether or not this object should be skipped.
|
||||
If this model is a parent of a single subclassed instance, skip it.
|
||||
The subclassed instance will create this parent instance for us.
|
||||
|
||||
TODO: Allow the user to force its creation?
|
||||
"""
|
||||
|
||||
if self.skip_me is not None:
|
||||
return self.skip_me
|
||||
|
||||
try:
|
||||
# Django trunk since r7722 uses CollectedObjects instead of dict
|
||||
from django.db.models.query import CollectedObjects
|
||||
sub_objects = CollectedObjects()
|
||||
except ImportError:
|
||||
# previous versions don't have CollectedObjects
|
||||
sub_objects = {}
|
||||
self.instance._collect_sub_objects(sub_objects)
|
||||
if reduce(lambda x, y: x + y, [self.model in so._meta.parents for so in sub_objects.keys()]) == 1:
|
||||
pk_name = self.instance._meta.pk.name
|
||||
key = '%s_%s' % (self.model.__name__, getattr(self.instance, pk_name))
|
||||
self.context[key] = None
|
||||
self.skip_me = True
|
||||
else:
|
||||
self.skip_me = False
|
||||
|
||||
return self.skip_me
|
||||
|
||||
def instantiate(self):
|
||||
" Write lines for instantiation "
|
||||
# e.g. model_name_35 = Model()
|
||||
code_lines = []
|
||||
|
||||
if not self.instantiated:
|
||||
code_lines.append("%s = %s()" % (self.variable_name, self.model.__name__))
|
||||
self.instantiated = True
|
||||
|
||||
# Store our variable name for future foreign key references
|
||||
pk_name = self.instance._meta.pk.name
|
||||
key = '%s_%s' % (self.model.__name__, getattr(self.instance, pk_name))
|
||||
self.context[key] = self.variable_name
|
||||
|
||||
return code_lines
|
||||
|
||||
def get_waiting_list(self, force=False):
|
||||
" Add lines for any waiting fields that can be completed now. "
|
||||
|
||||
code_lines = []
|
||||
|
||||
# Process normal fields
|
||||
for field in list(self.waiting_list):
|
||||
try:
|
||||
# Find the value, add the line, remove from waiting list and move on
|
||||
value = get_attribute_value(self.instance, field, self.context, force=force)
|
||||
code_lines.append('%s.%s = %s' % (self.variable_name, field.name, value))
|
||||
self.waiting_list.remove(field)
|
||||
except SkipValue, e:
|
||||
# Remove from the waiting list and move on
|
||||
self.waiting_list.remove(field)
|
||||
continue
|
||||
except DoLater, e:
|
||||
# Move on, maybe next time
|
||||
continue
|
||||
|
||||
return code_lines
|
||||
|
||||
def get_many_to_many_lines(self, force=False):
|
||||
""" Generates lines that define many to many relations for this instance. """
|
||||
|
||||
lines = []
|
||||
|
||||
for field, rel_items in self.many_to_many_waiting_list.items():
|
||||
for rel_item in list(rel_items):
|
||||
try:
|
||||
pk_name = rel_item._meta.pk.name
|
||||
key = '%s_%s' % (rel_item.__class__.__name__, getattr(rel_item, pk_name))
|
||||
value = "%s" % self.context[key]
|
||||
lines.append('%s.%s.add(%s)' % (self.variable_name, field.name, value))
|
||||
self.many_to_many_waiting_list[field].remove(rel_item)
|
||||
except KeyError:
|
||||
if force:
|
||||
value = "%s.objects.get(%s=%s)" % (rel_item._meta.object_name, pk_name, getattr(rel_item, pk_name))
|
||||
lines.append('%s.%s.add(%s)' % (self.variable_name, field.name, value))
|
||||
self.many_to_many_waiting_list[field].remove(rel_item)
|
||||
|
||||
if lines:
|
||||
lines.append("")
|
||||
|
||||
return lines
|
||||
|
||||
|
||||
class Script(Code):
|
||||
" Produces a complete python script that can recreate data for the given apps. "
|
||||
|
||||
def __init__(self, models, context={}):
|
||||
self.models = models
|
||||
self.context = context
|
||||
|
||||
self.indent = -1
|
||||
self.imports = {}
|
||||
|
||||
def get_lines(self):
|
||||
""" Returns a list of lists or strings, representing the code body.
|
||||
Each list is a block, each string is a statement.
|
||||
"""
|
||||
code = [self.FILE_HEADER.strip()]
|
||||
|
||||
# Queue and process the required models
|
||||
for model_class in queue_models(self.models, context=self.context):
|
||||
sys.stderr.write('Processing model: %s\n' % model_class.model.__name__)
|
||||
code.append(model_class.import_lines)
|
||||
code.append("")
|
||||
code.append(model_class.lines)
|
||||
|
||||
# Process left over foreign keys from cyclic models
|
||||
for model in self.models:
|
||||
sys.stderr.write('Re-processing model: %s\n' % model.model.__name__)
|
||||
for instance in model.instances:
|
||||
if instance.waiting_list or instance.many_to_many_waiting_list:
|
||||
code.append(instance.get_lines(force=True))
|
||||
|
||||
return code
|
||||
|
||||
lines = property(get_lines)
|
||||
|
||||
# A user-friendly file header
|
||||
FILE_HEADER = """
|
||||
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# This file has been automatically generated, changes may be lost if you
|
||||
# go and generate it again. It was generated with the following command:
|
||||
# %s
|
||||
|
||||
import datetime
|
||||
from decimal import Decimal
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
def run():
|
||||
|
||||
""" % " ".join(sys.argv)
|
||||
|
||||
|
||||
# HELPER FUNCTIONS
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
def flatten_blocks(lines, num_indents=-1):
|
||||
""" Takes a list (block) or string (statement) and flattens it into a string
|
||||
with indentation.
|
||||
"""
|
||||
|
||||
# The standard indent is four spaces
|
||||
INDENTATION = " " * 4
|
||||
|
||||
if not lines:
|
||||
return ""
|
||||
|
||||
# If this is a string, add the indentation and finish here
|
||||
if isinstance(lines, basestring):
|
||||
return INDENTATION * num_indents + lines
|
||||
|
||||
# If this is not a string, join the lines and recurse
|
||||
return "\n".join([flatten_blocks(line, num_indents + 1) for line in lines])
|
||||
|
||||
|
||||
def get_attribute_value(item, field, context, force=False):
|
||||
""" Gets a string version of the given attribute's value, like repr() might. """
|
||||
|
||||
# Find the value of the field, catching any database issues
|
||||
try:
|
||||
value = getattr(item, field.name)
|
||||
except ObjectDoesNotExist:
|
||||
raise SkipValue('Could not find object for %s.%s, ignoring.\n' % (item.__class__.__name__, field.name))
|
||||
|
||||
# AutoField: We don't include the auto fields, they'll be automatically recreated
|
||||
if isinstance(field, models.AutoField):
|
||||
raise SkipValue()
|
||||
|
||||
# Some databases (eg MySQL) might store boolean values as 0/1, this needs to be cast as a bool
|
||||
elif isinstance(field, models.BooleanField) and value is not None:
|
||||
return repr(bool(value))
|
||||
|
||||
# Post file-storage-refactor, repr() on File/ImageFields no longer returns the path
|
||||
elif isinstance(field, models.FileField):
|
||||
return repr(force_unicode(value))
|
||||
|
||||
# ForeignKey fields, link directly using our stored python variable name
|
||||
elif isinstance(field, models.ForeignKey) and value is not None:
|
||||
|
||||
# Special case for contenttype foreign keys: no need to output any
|
||||
# content types in this script, as they can be generated again
|
||||
# automatically.
|
||||
# NB: Not sure if "is" will always work
|
||||
if field.rel.to is ContentType:
|
||||
return 'ContentType.objects.get(app_label="%s", model="%s")' % (value.app_label, value.model)
|
||||
|
||||
# Generate an identifier (key) for this foreign object
|
||||
pk_name = value._meta.pk.name
|
||||
key = '%s_%s' % (value.__class__.__name__, getattr(value, pk_name))
|
||||
|
||||
if key in context:
|
||||
variable_name = context[key]
|
||||
# If the context value is set to None, this should be skipped.
|
||||
# This identifies models that have been skipped (inheritance)
|
||||
if variable_name is None:
|
||||
raise SkipValue()
|
||||
# Return the variable name listed in the context
|
||||
return "%s" % variable_name
|
||||
elif force:
|
||||
return "%s.objects.get(%s=%s)" % (value._meta.object_name, pk_name, getattr(value, pk_name))
|
||||
else:
|
||||
raise DoLater('(FK) %s.%s\n' % (item.__class__.__name__, field.name))
|
||||
|
||||
# A normal field (e.g. a python built-in)
|
||||
else:
|
||||
return repr(value)
|
||||
|
||||
|
||||
def queue_models(models, context):
|
||||
""" Works an an appropriate ordering for the models.
|
||||
This isn't essential, but makes the script look nicer because
|
||||
more instances can be defined on their first try.
|
||||
"""
|
||||
|
||||
# Max number of cycles allowed before we call it an infinite loop.
|
||||
MAX_CYCLES = 5
|
||||
|
||||
model_queue = []
|
||||
number_remaining_models = len(models)
|
||||
allowed_cycles = MAX_CYCLES
|
||||
|
||||
while number_remaining_models > 0:
|
||||
previous_number_remaining_models = number_remaining_models
|
||||
|
||||
model = models.pop(0)
|
||||
|
||||
# If the model is ready to be processed, add it to the list
|
||||
if check_dependencies(model, model_queue):
|
||||
model_class = ModelCode(model=model, context=context)
|
||||
model_queue.append(model_class)
|
||||
|
||||
# Otherwise put the model back at the end of the list
|
||||
else:
|
||||
models.append(model)
|
||||
|
||||
# Check for infinite loops.
|
||||
# This means there is a cyclic foreign key structure
|
||||
# That cannot be resolved by re-ordering
|
||||
number_remaining_models = len(models)
|
||||
if number_remaining_models == previous_number_remaining_models:
|
||||
allowed_cycles -= 1
|
||||
if allowed_cycles <= 0:
|
||||
# Add the remaining models, but do not remove them from the model list
|
||||
missing_models = [ModelCode(model=m, context=context) for m in models]
|
||||
model_queue += missing_models
|
||||
# Replace the models with the model class objects
|
||||
# (sure, this is a little bit of hackery)
|
||||
models[:] = missing_models
|
||||
break
|
||||
else:
|
||||
allowed_cycles = MAX_CYCLES
|
||||
|
||||
return model_queue
|
||||
|
||||
|
||||
def check_dependencies(model, model_queue):
|
||||
" Check that all the depenedencies for this model are already in the queue. "
|
||||
|
||||
# A list of allowed links: existing fields, itself and the special case ContentType
|
||||
allowed_links = [m.model.__name__ for m in model_queue] + [model.__name__, 'ContentType']
|
||||
|
||||
# For each ForeignKey or ManyToMany field, check that a link is possible
|
||||
for field in model._meta.fields + model._meta.many_to_many:
|
||||
if field.rel and field.rel.to.__name__ not in allowed_links:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
# EXCEPTIONS
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
class SkipValue(Exception):
|
||||
""" Value could not be parsed or should simply be skipped. """
|
||||
|
||||
|
||||
class DoLater(Exception):
|
||||
""" Value could not be parsed or should simply be skipped. """
|
|
@ -0,0 +1,119 @@
|
|||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.contrib.auth.models import User, Group
|
||||
from optparse import make_option
|
||||
from sys import stdout
|
||||
from csv import writer
|
||||
|
||||
FORMATS = [
|
||||
'address',
|
||||
'google',
|
||||
'outlook',
|
||||
'linkedin',
|
||||
'vcard',
|
||||
]
|
||||
|
||||
|
||||
def full_name(first_name, last_name, username, **extra):
|
||||
name = u" ".join(n for n in [first_name, last_name] if n)
|
||||
if not name:
|
||||
return username
|
||||
return name
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
option_list = BaseCommand.option_list + (
|
||||
make_option('--group', '-g', action='store', dest='group', default=None,
|
||||
help='Limit to users which are part of the supplied group name'),
|
||||
make_option('--format', '-f', action='store', dest='format', default=FORMATS[0],
|
||||
help="output format. May be one of '" + "', '".join(FORMATS) + "'."),
|
||||
)
|
||||
|
||||
help = ("Export user email address list in one of a number of formats.")
|
||||
args = "[output file]"
|
||||
label = 'filename to save to'
|
||||
|
||||
requires_model_validation = True
|
||||
can_import_settings = True
|
||||
encoding = 'utf-8' # RED_FLAG: add as an option -DougN
|
||||
|
||||
def handle(self, *args, **options):
|
||||
if len(args) > 1:
|
||||
raise CommandError("extra arguments supplied")
|
||||
group = options['group']
|
||||
if group and not Group.objects.filter(name=group).count() == 1:
|
||||
names = u"', '".join(g['name'] for g in Group.objects.values('name')).encode('utf-8')
|
||||
if names:
|
||||
names = "'" + names + "'."
|
||||
raise CommandError("Unknown group '" + group + "'. Valid group names are: " + names)
|
||||
if len(args) and args[0] != '-':
|
||||
outfile = file(args[0], 'w')
|
||||
else:
|
||||
outfile = stdout
|
||||
|
||||
qs = User.objects.all().order_by('last_name', 'first_name', 'username', 'email')
|
||||
if group:
|
||||
qs = qs.filter(group__name=group).distinct()
|
||||
qs = qs.values('last_name', 'first_name', 'username', 'email')
|
||||
getattr(self, options['format'])(qs, outfile)
|
||||
|
||||
def address(self, qs, out):
|
||||
"""simple single entry per line in the format of:
|
||||
"full name" <my@address.com>;
|
||||
"""
|
||||
out.write(u"\n".join(u'"%s" <%s>;' % (full_name(**ent), ent['email'])
|
||||
for ent in qs).encode(self.encoding))
|
||||
out.write("\n")
|
||||
|
||||
def google(self, qs, out):
|
||||
"""CSV format suitable for importing into google GMail
|
||||
"""
|
||||
csvf = writer(out)
|
||||
csvf.writerow(['Name', 'Email'])
|
||||
for ent in qs:
|
||||
csvf.writerow([full_name(**ent).encode(self.encoding),
|
||||
ent['email'].encode(self.encoding)])
|
||||
|
||||
def outlook(self, qs, out):
|
||||
"""CSV format suitable for importing into outlook
|
||||
"""
|
||||
csvf = writer(out)
|
||||
columns = ['Name', 'E-mail Address', 'Notes', 'E-mail 2 Address', 'E-mail 3 Address',
|
||||
'Mobile Phone', 'Pager', 'Company', 'Job Title', 'Home Phone', 'Home Phone 2',
|
||||
'Home Fax', 'Home Address', 'Business Phone', 'Business Phone 2',
|
||||
'Business Fax', 'Business Address', 'Other Phone', 'Other Fax', 'Other Address']
|
||||
csvf.writerow(columns)
|
||||
empty = [''] * (len(columns) - 2)
|
||||
for ent in qs:
|
||||
csvf.writerow([full_name(**ent).encode(self.encoding),
|
||||
ent['email'].encode(self.encoding)] + empty)
|
||||
|
||||
def linkedin(self, qs, out):
|
||||
"""CSV format suitable for importing into linkedin Groups.
|
||||
perfect for pre-approving members of a linkedin group.
|
||||
"""
|
||||
csvf = writer(out)
|
||||
csvf.writerow(['First Name', 'Last Name', 'Email'])
|
||||
for ent in qs:
|
||||
csvf.writerow([ent['first_name'].encode(self.encoding),
|
||||
ent['last_name'].encode(self.encoding),
|
||||
ent['email'].encode(self.encoding)])
|
||||
|
||||
def vcard(self, qs, out):
|
||||
try:
|
||||
import vobject
|
||||
except ImportError:
|
||||
print self.style.ERROR("Please install python-vobject to use the vcard export format.")
|
||||
import sys
|
||||
sys.exit(1)
|
||||
for ent in qs:
|
||||
card = vobject.vCard()
|
||||
card.add('fn').value = full_name(**ent)
|
||||
if not ent['last_name'] and not ent['first_name']:
|
||||
# fallback to fullname, if both first and lastname are not declared
|
||||
card.add('n').value = vobject.vcard.Name(full_name(**ent))
|
||||
else:
|
||||
card.add('n').value = vobject.vcard.Name(ent['last_name'], ent['first_name'])
|
||||
emailpart = card.add('email')
|
||||
emailpart.value = ent['email']
|
||||
emailpart.type_param = 'INTERNET'
|
||||
out.write(card.serialize().encode(self.encoding))
|
|
@ -0,0 +1,26 @@
|
|||
from django.core.management.base import LabelCommand
|
||||
from django.template.loader import find_template
|
||||
from django.template import TemplateDoesNotExist
|
||||
import sys
|
||||
|
||||
|
||||
def get_template_path(path):
|
||||
try:
|
||||
template = find_template(path)
|
||||
return template[1].name
|
||||
except TemplateDoesNotExist:
|
||||
return None
|
||||
|
||||
|
||||
class Command(LabelCommand):
|
||||
help = "Finds the location of the given template by resolving its path"
|
||||
args = "[template_path]"
|
||||
label = 'template path'
|
||||
|
||||
def handle_label(self, template_path, **options):
|
||||
path = get_template_path(template_path)
|
||||
if path is None:
|
||||
sys.stderr.write("No template found\n")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print path
|
|
@ -0,0 +1,11 @@
|
|||
from random import choice
|
||||
from django.core.management.base import NoArgsCommand
|
||||
|
||||
|
||||
class Command(NoArgsCommand):
|
||||
help = "Generates a new SECRET_KEY that can be used in a project settings file."
|
||||
|
||||
requires_model_validation = False
|
||||
|
||||
def handle_noargs(self, **options):
|
||||
return ''.join([choice('abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)') for i in range(50)])
|
|
@ -0,0 +1,69 @@
|
|||
from django.core.management.base import BaseCommand, CommandError
|
||||
from optparse import make_option
|
||||
from django_extensions.management.modelviz import generate_dot
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
option_list = BaseCommand.option_list + (
|
||||
make_option('--disable-fields', '-d', action='store_true', dest='disable_fields',
|
||||
help='Do not show the class member fields'),
|
||||
make_option('--group-models', '-g', action='store_true', dest='group_models',
|
||||
help='Group models together respective to their application'),
|
||||
make_option('--all-applications', '-a', action='store_true', dest='all_applications',
|
||||
help='Automatically include all applications from INSTALLED_APPS'),
|
||||
make_option('--output', '-o', action='store', dest='outputfile',
|
||||
help='Render output file. Type of output dependent on file extensions. Use png or jpg to render graph to image.'),
|
||||
make_option('--layout', '-l', action='store', dest='layout', default='dot',
|
||||
help='Layout to be used by GraphViz for visualization. Layouts: circo dot fdp neato nop nop1 nop2 twopi'),
|
||||
make_option('--verbose-names', '-n', action='store_true', dest='verbose_names',
|
||||
help='Use verbose_name of models and fields'),
|
||||
make_option('--language', '-L', action='store', dest='language',
|
||||
help='Specify language used for verbose_name localization'),
|
||||
make_option('--exclude-columns', '-x', action='store', dest='exclude_columns',
|
||||
help='Exclude specific column(s) from the graph. Can also load exclude list from file.'),
|
||||
make_option('--exclude-models', '-X', action='store', dest='exclude_models',
|
||||
help='Exclude specific model(s) from the graph. Can also load exclude list from file.'),
|
||||
)
|
||||
|
||||
help = ("Creates a GraphViz dot file for the specified app names. You can pass multiple app names and they will all be combined into a single model. Output is usually directed to a dot file.")
|
||||
args = "[appname]"
|
||||
label = 'application name'
|
||||
|
||||
requires_model_validation = True
|
||||
can_import_settings = True
|
||||
|
||||
def handle(self, *args, **options):
|
||||
if len(args) < 1 and not options['all_applications']:
|
||||
raise CommandError("need one or more arguments for appname")
|
||||
|
||||
dotdata = generate_dot(args, **options)
|
||||
if options['outputfile']:
|
||||
self.render_output(dotdata, **options)
|
||||
else:
|
||||
self.print_output(dotdata)
|
||||
|
||||
def print_output(self, dotdata):
|
||||
print dotdata.encode('utf-8')
|
||||
|
||||
def render_output(self, dotdata, **kwargs):
|
||||
try:
|
||||
import pygraphviz
|
||||
except ImportError, e:
|
||||
raise CommandError("need pygraphviz python module ( apt-get install python-pygraphviz )")
|
||||
|
||||
vizdata = ' '.join(dotdata.split("\n")).strip().encode('utf-8')
|
||||
version = pygraphviz.__version__.rstrip("-svn")
|
||||
try:
|
||||
if [int(v) for v in version.split('.')] < (0, 36):
|
||||
# HACK around old/broken AGraph before version 0.36 (ubuntu ships with this old version)
|
||||
import tempfile
|
||||
tmpfile = tempfile.NamedTemporaryFile()
|
||||
tmpfile.write(vizdata)
|
||||
tmpfile.seek(0)
|
||||
vizdata = tmpfile.name
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
graph = pygraphviz.AGraph(vizdata)
|
||||
graph.layout(prog=kwargs['layout'])
|
||||
graph.draw(kwargs['outputfile'])
|
|
@ -0,0 +1,42 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
import sys
|
||||
import smtpd
|
||||
import asyncore
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Starts a test mail server for development."
|
||||
args = '[optional port number or ippaddr:port]'
|
||||
|
||||
requires_model_validation = False
|
||||
|
||||
def handle(self, addrport='', *args, **options):
|
||||
if args:
|
||||
raise CommandError('Usage is runserver %s' % self.args)
|
||||
if not addrport:
|
||||
addr = ''
|
||||
port = '1025'
|
||||
else:
|
||||
try:
|
||||
addr, port = addrport.split(':')
|
||||
except ValueError:
|
||||
addr, port = '', addrport
|
||||
if not addr:
|
||||
addr = '127.0.0.1'
|
||||
|
||||
if not port.isdigit():
|
||||
raise CommandError("%r is not a valid port number." % port)
|
||||
else:
|
||||
port = int(port)
|
||||
|
||||
quit_command = (sys.platform == 'win32') and 'CTRL-BREAK' or 'CONTROL-C'
|
||||
|
||||
def inner_run():
|
||||
print "Now accepting mail at %s:%s" % (addr, port)
|
||||
server = smtpd.DebuggingServer((addr, port), None)
|
||||
asyncore.loop()
|
||||
|
||||
try:
|
||||
inner_run()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
|
@ -0,0 +1,38 @@
|
|||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.conf import settings
|
||||
import os
|
||||
import re
|
||||
|
||||
ANNOTATION_RE = re.compile("#[\s]*?(TODO|FIXME|HACK|XXX)[\s:]?(.+)")
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = 'Show all annotations like TODO, FIXME, HACK or XXX in your py files.'
|
||||
args = 'tag'
|
||||
label = 'annotation tag (TODO, FIXME, HACK, XXX)'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
# don't add django internal code
|
||||
apps = filter(lambda app: not app.startswith('django.contrib'),
|
||||
settings.INSTALLED_APPS)
|
||||
for app_dir in apps:
|
||||
for top, dirs, files in os.walk(app_dir):
|
||||
for f in files:
|
||||
if os.path.splitext(f)[1] in ['.py']:
|
||||
fpath = os.path.join(top, f)
|
||||
annotation_lines = []
|
||||
with open(fpath, 'r') as f:
|
||||
i = 0
|
||||
for line in f.readlines():
|
||||
i += 1
|
||||
if ANNOTATION_RE.search(line):
|
||||
tag, msg = ANNOTATION_RE.findall(line)[0]
|
||||
if len(args) == 1:
|
||||
search_for_tag = args[0].upper()
|
||||
if not search_for_tag == tag:
|
||||
break
|
||||
annotation_lines.append("[%3s] %-5s %s" % (i, tag, msg.strip()))
|
||||
if annotation_lines:
|
||||
print fpath+":"
|
||||
for annotation in annotation_lines:
|
||||
print " * "+annotation
|
||||
print
|
|
@ -0,0 +1,38 @@
|
|||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.contrib.auth.models import User
|
||||
import getpass
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Clone of the UNIX program ``passwd'', for django.contrib.auth."
|
||||
|
||||
requires_model_validation = False
|
||||
|
||||
def handle(self, *args, **options):
|
||||
if len(args) > 1:
|
||||
raise CommandError("need exactly one or zero arguments for username")
|
||||
|
||||
if args:
|
||||
username, = args
|
||||
else:
|
||||
username = getpass.getuser()
|
||||
|
||||
try:
|
||||
u = User.objects.get(username=username)
|
||||
except User.DoesNotExist:
|
||||
raise CommandError("user %s does not exist" % username)
|
||||
|
||||
print "Changing password for user", u.username
|
||||
p1 = p2 = ""
|
||||
while "" in (p1, p2) or p1 != p2:
|
||||
p1 = getpass.getpass()
|
||||
p2 = getpass.getpass("Password (again): ")
|
||||
if p1 != p2:
|
||||
print "Passwords do not match, try again"
|
||||
elif "" in (p1, p2):
|
||||
raise CommandError("aborted")
|
||||
|
||||
u.set_password(p1)
|
||||
u.save()
|
||||
|
||||
return "Password changed successfully for user %s\n" % u.username
|
|
@ -0,0 +1,47 @@
|
|||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.sessions.models import Session
|
||||
import re
|
||||
|
||||
SESSION_RE = re.compile("^[0-9a-f]{20,40}$")
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = ("print the user information for the provided session key. "
|
||||
"this is very helpful when trying to track down the person who "
|
||||
"experienced a site crash.")
|
||||
args = "session_key"
|
||||
label = 'session key for the user'
|
||||
|
||||
requires_model_validation = True
|
||||
can_import_settings = True
|
||||
|
||||
def handle(self, *args, **options):
|
||||
if len(args) > 1:
|
||||
raise CommandError("extra arguments supplied")
|
||||
if len(args) < 1:
|
||||
raise CommandError("session_key argument missing")
|
||||
key = args[0].lower()
|
||||
if not SESSION_RE.match(key):
|
||||
raise CommandError("malformed session key")
|
||||
try:
|
||||
session = Session.objects.get(pk=key)
|
||||
except Session.DoesNotExist:
|
||||
print "Session Key does not exist. Expired?"
|
||||
return
|
||||
|
||||
data = session.get_decoded()
|
||||
print 'Session to Expire:', session.expire_date
|
||||
print 'Raw Data:', data
|
||||
uid = data.get('_auth_user_id', None)
|
||||
if uid is None:
|
||||
print 'No user associated with session'
|
||||
return
|
||||
print "User id:", uid
|
||||
try:
|
||||
user = User.objects.get(pk=uid)
|
||||
except User.DoesNotExist:
|
||||
print "No user associated with that id."
|
||||
return
|
||||
for key in ['username', 'email', 'first_name', 'last_name']:
|
||||
print key + ': ' + getattr(user, key)
|
|
@ -0,0 +1,174 @@
|
|||
"""
|
||||
originally from http://www.djangosnippets.org/snippets/828/ by dnordberg
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import CommandError, BaseCommand
|
||||
from django.db import connection
|
||||
import django
|
||||
import logging
|
||||
import re
|
||||
from optparse import make_option
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
option_list = BaseCommand.option_list + (
|
||||
make_option('--noinput', action='store_false',
|
||||
dest='interactive', default=True,
|
||||
help='Tells Django to NOT prompt the user for input of any kind.'),
|
||||
make_option('--no-utf8', action='store_true',
|
||||
dest='no_utf8_support', default=False,
|
||||
help='Tells Django to not create a UTF-8 charset database'),
|
||||
make_option('-U', '--user', action='store',
|
||||
dest='user', default=None,
|
||||
help='Use another user for the database then defined in settings.py'),
|
||||
make_option('-P', '--password', action='store',
|
||||
dest='password', default=None,
|
||||
help='Use another password for the database then defined in settings.py'),
|
||||
make_option('-D', '--dbname', action='store',
|
||||
dest='dbname', default=None,
|
||||
help='Use another database name then defined in settings.py (For PostgreSQL this defaults to "template1")'),
|
||||
make_option('-R', '--router', action='store',
|
||||
dest='router', default=None,
|
||||
help='Use this router-database other then defined in settings.py'),
|
||||
)
|
||||
help = "Resets the database for this project."
|
||||
|
||||
def set_db_settings(self, *args, **options):
|
||||
if django.get_version() >= "1.2":
|
||||
router = options.get('router')
|
||||
if router == None:
|
||||
return False
|
||||
|
||||
# retrieve this with the 'using' argument
|
||||
dbinfo = settings.DATABASES.get(router)
|
||||
settings.DATABASE_ENGINE = dbinfo.get('ENGINE').split('.')[-1]
|
||||
settings.DATABASE_USER = dbinfo.get('USER')
|
||||
settings.DATABASE_PASSWORD = dbinfo.get('PASSWORD')
|
||||
settings.DATABASE_NAME = dbinfo.get('NAME')
|
||||
settings.DATABASE_HOST = dbinfo.get('HOST')
|
||||
settings.DATABASE_PORT = dbinfo.get('PORT')
|
||||
return True
|
||||
else:
|
||||
# settings are set for django < 1.2 no modification needed
|
||||
return True
|
||||
|
||||
def handle(self, *args, **options):
|
||||
"""
|
||||
Resets the database for this project.
|
||||
|
||||
Note: Transaction wrappers are in reverse as a work around for
|
||||
autocommit, anybody know how to do this the right way?
|
||||
"""
|
||||
|
||||
if django.get_version() >= "1.2":
|
||||
got_db_settings = self.set_db_settings(*args, **options)
|
||||
if not got_db_settings:
|
||||
raise CommandError("You are using Django %s which requires to specify the db-router.\nPlease specify the router by adding --router=<routername> to this command." % django.get_version())
|
||||
return
|
||||
|
||||
verbosity = int(options.get('verbosity', 1))
|
||||
if options.get('interactive'):
|
||||
confirm = raw_input("""
|
||||
You have requested a database reset.
|
||||
This will IRREVERSIBLY DESTROY
|
||||
ALL data in the database "%s".
|
||||
Are you sure you want to do this?
|
||||
|
||||
Type 'yes' to continue, or 'no' to cancel: """ % (settings.DATABASE_NAME,))
|
||||
else:
|
||||
confirm = 'yes'
|
||||
|
||||
if confirm != 'yes':
|
||||
print "Reset cancelled."
|
||||
return
|
||||
|
||||
postgis = re.compile('.*postgis')
|
||||
engine = settings.DATABASE_ENGINE
|
||||
user = options.get('user', settings.DATABASE_USER)
|
||||
if user == None:
|
||||
user = settings.DATABASE_USER
|
||||
password = options.get('password', settings.DATABASE_PASSWORD)
|
||||
if password == None:
|
||||
password = settings.DATABASE_PASSWORD
|
||||
|
||||
if engine == 'sqlite3':
|
||||
import os
|
||||
try:
|
||||
logging.info("Unlinking sqlite3 database")
|
||||
os.unlink(settings.DATABASE_NAME)
|
||||
except OSError:
|
||||
pass
|
||||
elif engine == 'mysql':
|
||||
import MySQLdb as Database
|
||||
kwargs = {
|
||||
'user': user,
|
||||
'passwd': password,
|
||||
}
|
||||
if settings.DATABASE_HOST.startswith('/'):
|
||||
kwargs['unix_socket'] = settings.DATABASE_HOST
|
||||
else:
|
||||
kwargs['host'] = settings.DATABASE_HOST
|
||||
if settings.DATABASE_PORT:
|
||||
kwargs['port'] = int(settings.DATABASE_PORT)
|
||||
|
||||
connection = Database.connect(**kwargs)
|
||||
drop_query = 'DROP DATABASE IF EXISTS %s' % settings.DATABASE_NAME
|
||||
utf8_support = options.get('no_utf8_support', False) and '' or 'CHARACTER SET utf8'
|
||||
create_query = 'CREATE DATABASE %s %s' % (settings.DATABASE_NAME, utf8_support)
|
||||
logging.info('Executing... "' + drop_query + '"')
|
||||
connection.query(drop_query)
|
||||
logging.info('Executing... "' + create_query + '"')
|
||||
connection.query(create_query)
|
||||
|
||||
elif engine == 'postgresql' or engine == 'postgresql_psycopg2' or postgis.match(engine):
|
||||
if engine == 'postgresql':
|
||||
import psycopg as Database
|
||||
elif engine == 'postgresql_psycopg2' or postgis.match(engine):
|
||||
import psycopg2 as Database
|
||||
|
||||
if settings.DATABASE_NAME == '':
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
raise ImproperlyConfigured("You need to specify DATABASE_NAME in your Django settings file.")
|
||||
|
||||
database_name = options.get('dbname', 'template1')
|
||||
if options.get('dbname') == None:
|
||||
database_name = 'template1'
|
||||
conn_string = "dbname=%s" % database_name
|
||||
if settings.DATABASE_USER:
|
||||
conn_string += " user=%s" % user
|
||||
if settings.DATABASE_PASSWORD:
|
||||
conn_string += " password='%s'" % password
|
||||
if settings.DATABASE_HOST:
|
||||
conn_string += " host=%s" % settings.DATABASE_HOST
|
||||
if settings.DATABASE_PORT:
|
||||
conn_string += " port=%s" % settings.DATABASE_PORT
|
||||
|
||||
connection = Database.connect(conn_string)
|
||||
connection.set_isolation_level(0) # autocommit false
|
||||
cursor = connection.cursor()
|
||||
drop_query = 'DROP DATABASE %s' % settings.DATABASE_NAME
|
||||
logging.info('Executing... "' + drop_query + '"')
|
||||
|
||||
try:
|
||||
cursor.execute(drop_query)
|
||||
except Database.ProgrammingError, e:
|
||||
logging.info("Error: %s" % str(e))
|
||||
|
||||
# Encoding should be SQL_ASCII (7-bit postgres default) or prefered UTF8 (8-bit)
|
||||
create_query = """CREATE DATABASE %s WITH OWNER = %s ENCODING = 'UTF8' """ % (settings.DATABASE_NAME, settings.DATABASE_USER)
|
||||
|
||||
if postgis.match(engine):
|
||||
create_query += 'TEMPLATE = template_postgis '
|
||||
if settings.DEFAULT_TABLESPACE:
|
||||
create_query += 'TABLESPACE = %s;' % (settings.DEFAULT_TABLESPACE)
|
||||
else:
|
||||
create_query += ';'
|
||||
logging.info('Executing... "' + create_query + '"')
|
||||
cursor.execute(create_query)
|
||||
|
||||
else:
|
||||
raise CommandError("Unknown database engine %s" % engine)
|
||||
|
||||
if verbosity >= 2 or options.get('interactive'):
|
||||
print "Reset successful."
|
|
@ -0,0 +1,60 @@
|
|||
from django.core.management.base import LabelCommand
|
||||
from optparse import make_option
|
||||
from django_extensions.management.jobs import get_job, print_jobs
|
||||
|
||||
|
||||
class Command(LabelCommand):
|
||||
option_list = LabelCommand.option_list + (
|
||||
make_option('--list', '-l', action="store_true", dest="list_jobs",
|
||||
help="List all jobs with their description"),
|
||||
)
|
||||
help = "Run a single maintenance job."
|
||||
args = "[app_name] job_name"
|
||||
label = ""
|
||||
|
||||
requires_model_validation = True
|
||||
|
||||
def runjob(self, app_name, job_name, options):
|
||||
verbosity = int(options.get('verbosity', 1))
|
||||
if verbosity > 1:
|
||||
print "Executing job: %s (app: %s)" % (job_name, app_name)
|
||||
try:
|
||||
job = get_job(app_name, job_name)
|
||||
except KeyError, e:
|
||||
if app_name:
|
||||
print "Error: Job %s for applabel %s not found" % (app_name, job_name)
|
||||
else:
|
||||
print "Error: Job %s not found" % job_name
|
||||
print "Use -l option to view all the available jobs"
|
||||
return
|
||||
try:
|
||||
job().execute()
|
||||
except Exception, e:
|
||||
import traceback
|
||||
print "ERROR OCCURED IN JOB: %s (APP: %s)" % (job_name, app_name)
|
||||
print "START TRACEBACK:"
|
||||
traceback.print_exc()
|
||||
print "END TRACEBACK\n"
|
||||
|
||||
def handle(self, *args, **options):
|
||||
app_name = None
|
||||
job_name = None
|
||||
if len(args) == 1:
|
||||
job_name = args[0]
|
||||
elif len(args) == 2:
|
||||
app_name, job_name = args
|
||||
if options.get('list_jobs'):
|
||||
print_jobs(only_scheduled=False, show_when=True, show_appname=True)
|
||||
else:
|
||||
if not job_name:
|
||||
print "Run a single maintenance job. Please specify the name of the job."
|
||||
return
|
||||
self.runjob(app_name, job_name, options)
|
||||
|
||||
# Backwards compatibility for Django r9110
|
||||
if not [opt for opt in Command.option_list if opt.dest == 'verbosity']:
|
||||
Command.option_list += (
|
||||
make_option('--verbosity', '-v', action="store", dest="verbosity",
|
||||
default='1', type='choice', choices=['0', '1', '2'],
|
||||
help="Verbosity level; 0=minimal output, 1=normal output, 2=all output"),
|
||||
)
|
|
@ -0,0 +1,95 @@
|
|||
from django.core.management.base import LabelCommand
|
||||
from optparse import make_option
|
||||
from django_extensions.management.jobs import get_jobs, print_jobs
|
||||
|
||||
|
||||
class Command(LabelCommand):
|
||||
option_list = LabelCommand.option_list + (
|
||||
make_option('--list', '-l', action="store_true", dest="list_jobs",
|
||||
help="List all jobs with their description"),
|
||||
)
|
||||
help = "Runs scheduled maintenance jobs."
|
||||
args = "[minutely hourly daily weekly monthly yearly]"
|
||||
label = ""
|
||||
|
||||
requires_model_validation = True
|
||||
|
||||
def usage_msg(self):
|
||||
print "Run scheduled jobs. Please specify 'minutely', 'hourly', 'daily', 'weekly', 'monthly' or 'yearly'"
|
||||
|
||||
def runjobs(self, when, options):
|
||||
verbosity = int(options.get('verbosity', 1))
|
||||
jobs = get_jobs(when, only_scheduled=True)
|
||||
list = jobs.keys()
|
||||
list.sort()
|
||||
for app_name, job_name in list:
|
||||
job = jobs[(app_name, job_name)]
|
||||
if verbosity > 1:
|
||||
print "Executing %s job: %s (app: %s)" % (when, job_name, app_name)
|
||||
try:
|
||||
job().execute()
|
||||
except Exception, e:
|
||||
import traceback
|
||||
print "ERROR OCCURED IN %s JOB: %s (APP: %s)" % (when.upper(), job_name, app_name)
|
||||
print "START TRACEBACK:"
|
||||
traceback.print_exc()
|
||||
print "END TRACEBACK\n"
|
||||
|
||||
def runjobs_by_signals(self, when, options):
|
||||
""" Run jobs from the signals """
|
||||
# Thanks for Ian Holsman for the idea and code
|
||||
from django_extensions.management import signals
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
|
||||
verbosity = int(options.get('verbosity', 1))
|
||||
for app_name in settings.INSTALLED_APPS:
|
||||
try:
|
||||
__import__(app_name + '.management', '', '', [''])
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
for app in models.get_apps():
|
||||
if verbosity > 1:
|
||||
app_name = '.'.join(app.__name__.rsplit('.')[:-1])
|
||||
print "Sending %s job signal for: %s" % (when, app_name)
|
||||
if when == 'minutely':
|
||||
signals.run_minutely_jobs.send(sender=app, app=app)
|
||||
elif when == 'hourly':
|
||||
signals.run_hourly_jobs.send(sender=app, app=app)
|
||||
elif when == 'daily':
|
||||
signals.run_daily_jobs.send(sender=app, app=app)
|
||||
elif when == 'weekly':
|
||||
signals.run_weekly_jobs.send(sender=app, app=app)
|
||||
elif when == 'monthly':
|
||||
signals.run_monthly_jobs.send(sender=app, app=app)
|
||||
elif when == 'yearly':
|
||||
signals.run_yearly_jobs.send(sender=app, app=app)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
when = None
|
||||
if len(args) > 1:
|
||||
self.usage_msg()
|
||||
return
|
||||
elif len(args) == 1:
|
||||
if not args[0] in ['minutely', 'hourly', 'daily', 'weekly', 'monthly', 'yearly']:
|
||||
self.usage_msg()
|
||||
return
|
||||
else:
|
||||
when = args[0]
|
||||
if options.get('list_jobs'):
|
||||
print_jobs(when, only_scheduled=True, show_when=True, show_appname=True)
|
||||
else:
|
||||
if not when:
|
||||
self.usage_msg()
|
||||
return
|
||||
self.runjobs(when, options)
|
||||
self.runjobs_by_signals(when, options)
|
||||
|
||||
# Backwards compatibility for Django r9110
|
||||
if not [opt for opt in Command.option_list if opt.dest == 'verbosity']:
|
||||
Command.option_list += (
|
||||
make_option('--verbosity', '-v', action="store", dest="verbosity",
|
||||
default='1', type='choice', choices=['0', '1', '2'],
|
||||
help="Verbosity level; 0=minimal output, 1=normal output, 2=all output"),
|
||||
)
|
|
@ -0,0 +1,226 @@
|
|||
"""
|
||||
runprofileserver.py
|
||||
|
||||
Starts a lightweight Web server with profiling enabled.
|
||||
|
||||
Credits for kcachegrind support taken from lsprofcalltree.py go to:
|
||||
David Allouche
|
||||
Jp Calderone & Itamar Shtull-Trauring
|
||||
Johan Dahlin
|
||||
"""
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from optparse import make_option
|
||||
from datetime import datetime
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def label(code):
|
||||
if isinstance(code, str):
|
||||
return ('~', 0, code) # built-in functions ('~' sorts at the end)
|
||||
else:
|
||||
return '%s %s:%d' % (code.co_name,
|
||||
code.co_filename,
|
||||
code.co_firstlineno)
|
||||
|
||||
|
||||
class KCacheGrind(object):
|
||||
def __init__(self, profiler):
|
||||
self.data = profiler.getstats()
|
||||
self.out_file = None
|
||||
|
||||
def output(self, out_file):
|
||||
self.out_file = out_file
|
||||
print >> out_file, 'events: Ticks'
|
||||
self._print_summary()
|
||||
for entry in self.data:
|
||||
self._entry(entry)
|
||||
|
||||
def _print_summary(self):
|
||||
max_cost = 0
|
||||
for entry in self.data:
|
||||
totaltime = int(entry.totaltime * 1000)
|
||||
max_cost = max(max_cost, totaltime)
|
||||
print >> self.out_file, 'summary: %d' % (max_cost,)
|
||||
|
||||
def _entry(self, entry):
|
||||
out_file = self.out_file
|
||||
|
||||
code = entry.code
|
||||
#print >> out_file, 'ob=%s' % (code.co_filename,)
|
||||
if isinstance(code, str):
|
||||
print >> out_file, 'fi=~'
|
||||
else:
|
||||
print >> out_file, 'fi=%s' % (code.co_filename,)
|
||||
print >> out_file, 'fn=%s' % (label(code),)
|
||||
|
||||
inlinetime = int(entry.inlinetime * 1000)
|
||||
if isinstance(code, str):
|
||||
print >> out_file, '0 ', inlinetime
|
||||
else:
|
||||
print >> out_file, '%d %d' % (code.co_firstlineno, inlinetime)
|
||||
|
||||
# recursive calls are counted in entry.calls
|
||||
if entry.calls:
|
||||
calls = entry.calls
|
||||
else:
|
||||
calls = []
|
||||
|
||||
if isinstance(code, str):
|
||||
lineno = 0
|
||||
else:
|
||||
lineno = code.co_firstlineno
|
||||
|
||||
for subentry in calls:
|
||||
self._subentry(lineno, subentry)
|
||||
print >> out_file
|
||||
|
||||
def _subentry(self, lineno, subentry):
|
||||
out_file = self.out_file
|
||||
code = subentry.code
|
||||
#print >> out_file, 'cob=%s' % (code.co_filename,)
|
||||
print >> out_file, 'cfn=%s' % (label(code),)
|
||||
if isinstance(code, str):
|
||||
print >> out_file, 'cfi=~'
|
||||
print >> out_file, 'calls=%d 0' % (subentry.callcount,)
|
||||
else:
|
||||
print >> out_file, 'cfi=%s' % (code.co_filename,)
|
||||
print >> out_file, 'calls=%d %d' % (
|
||||
subentry.callcount, code.co_firstlineno)
|
||||
|
||||
totaltime = int(subentry.totaltime * 1000)
|
||||
print >> out_file, '%d %d' % (lineno, totaltime)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
option_list = BaseCommand.option_list + (
|
||||
make_option('--noreload', action='store_false', dest='use_reloader', default=True,
|
||||
help='Tells Django to NOT use the auto-reloader.'),
|
||||
make_option('--adminmedia', dest='admin_media_path', default='',
|
||||
help='Specifies the directory from which to serve admin media.'),
|
||||
make_option('--prof-path', dest='prof_path', default='/tmp',
|
||||
help='Specifies the directory which to save profile information in.'),
|
||||
make_option('--nomedia', action='store_true', dest='no_media', default=False,
|
||||
help='Do not profile MEDIA_URL and ADMIN_MEDIA_URL'),
|
||||
make_option('--use-cprofile', action='store_true', dest='use_cprofile', default=False,
|
||||
help='Use cProfile if available, this is disabled per default because of incompatibilities.'),
|
||||
make_option('--kcachegrind', action='store_true', dest='use_lsprof', default=False,
|
||||
help='Create kcachegrind compatible lsprof files, this requires and automatically enables cProfile.'),
|
||||
)
|
||||
help = "Starts a lightweight Web server with profiling enabled."
|
||||
args = '[optional port number, or ipaddr:port]'
|
||||
|
||||
# Validation is called explicitly each time the server is reloaded.
|
||||
requires_model_validation = False
|
||||
|
||||
def handle(self, addrport='', *args, **options):
|
||||
import django
|
||||
from django.core.servers.basehttp import run, AdminMediaHandler, WSGIServerException
|
||||
from django.core.handlers.wsgi import WSGIHandler
|
||||
if args:
|
||||
raise CommandError('Usage is runserver %s' % self.args)
|
||||
if not addrport:
|
||||
addr = ''
|
||||
port = '8000'
|
||||
else:
|
||||
try:
|
||||
addr, port = addrport.split(':')
|
||||
except ValueError:
|
||||
addr, port = '', addrport
|
||||
if not addr:
|
||||
addr = '127.0.0.1'
|
||||
|
||||
if not port.isdigit():
|
||||
raise CommandError("%r is not a valid port number." % port)
|
||||
|
||||
use_reloader = options.get('use_reloader', True)
|
||||
admin_media_path = options.get('admin_media_path', '')
|
||||
shutdown_message = options.get('shutdown_message', '')
|
||||
no_media = options.get('no_media', False)
|
||||
quit_command = (sys.platform == 'win32') and 'CTRL-BREAK' or 'CONTROL-C'
|
||||
|
||||
def inner_run():
|
||||
from django.conf import settings
|
||||
|
||||
import os
|
||||
import time
|
||||
import hotshot
|
||||
USE_CPROFILE = options.get('use_cprofile', False)
|
||||
USE_LSPROF = options.get('use_lsprof', False)
|
||||
if USE_LSPROF:
|
||||
USE_CPROFILE = True
|
||||
if USE_CPROFILE:
|
||||
try:
|
||||
import cProfile
|
||||
USE_CPROFILE = True
|
||||
except ImportError:
|
||||
print "cProfile disabled, module cannot be imported!"
|
||||
USE_CPROFILE = False
|
||||
if USE_LSPROF and not USE_CPROFILE:
|
||||
raise SystemExit("Kcachegrind compatible output format required cProfile from Python 2.5")
|
||||
prof_path = options.get('prof_path', '/tmp')
|
||||
def make_profiler_handler(inner_handler):
|
||||
def handler(environ, start_response):
|
||||
path_info = environ['PATH_INFO']
|
||||
# normally /media/ is MEDIA_URL, but in case still check it in case it's differently
|
||||
# should be hardly a penalty since it's an OR expression.
|
||||
# TODO: fix this to check the configuration settings and not make assumpsions about where media are on the url
|
||||
if no_media and (path_info.startswith('/media') or path_info.startswith(settings.MEDIA_URL)):
|
||||
return inner_handler(environ, start_response)
|
||||
path_name = path_info.strip("/").replace('/', '.') or "root"
|
||||
profname = "%s.%s.prof" % (path_name, datetime.now().isoformat())
|
||||
profname = os.path.join(prof_path, profname)
|
||||
if USE_CPROFILE:
|
||||
prof = cProfile.Profile()
|
||||
else:
|
||||
prof = hotshot.Profile(profname)
|
||||
start = datetime.now()
|
||||
try:
|
||||
return prof.runcall(inner_handler, environ, start_response)
|
||||
finally:
|
||||
# seeing how long the request took is important!
|
||||
elap = datetime.now() - start
|
||||
elapms = elap.seconds * 1000.0 + elap.microseconds / 1000.0
|
||||
if USE_LSPROF:
|
||||
kg = KCacheGrind(prof)
|
||||
kg.output(file(profname, 'w'))
|
||||
elif USE_CPROFILE:
|
||||
prof.dump_stats(profname)
|
||||
profname2 = "%s.%06dms.%s.prof" % (path_name, elapms, datetime.now().isoformat())
|
||||
profname2 = os.path.join(prof_path, profname2)
|
||||
os.rename(profname, profname2)
|
||||
return handler
|
||||
|
||||
print "Validating models..."
|
||||
self.validate(display_num_errors=True)
|
||||
print "\nDjango version %s, using settings %r" % (django.get_version(), settings.SETTINGS_MODULE)
|
||||
print "Development server is running at http://%s:%s/" % (addr, port)
|
||||
print "Quit the server with %s." % quit_command
|
||||
try:
|
||||
path = admin_media_path or django.__path__[0] + '/contrib/admin/media'
|
||||
handler = make_profiler_handler(AdminMediaHandler(WSGIHandler(), path))
|
||||
run(addr, int(port), handler)
|
||||
except WSGIServerException, e:
|
||||
# Use helpful error messages instead of ugly tracebacks.
|
||||
ERRORS = {
|
||||
13: "You don't have permission to access that port.",
|
||||
98: "That port is already in use.",
|
||||
99: "That IP address can't be assigned-to.",
|
||||
}
|
||||
try:
|
||||
error_text = ERRORS[e.args[0].args[0]]
|
||||
except (AttributeError, KeyError):
|
||||
error_text = str(e)
|
||||
sys.stderr.write(self.style.ERROR("Error: %s" % error_text) + '\n')
|
||||
# Need to use an OS exit because sys.exit doesn't work in a thread
|
||||
os._exit(1)
|
||||
except KeyboardInterrupt:
|
||||
if shutdown_message:
|
||||
print shutdown_message
|
||||
sys.exit(0)
|
||||
if use_reloader:
|
||||
from django.utils import autoreload
|
||||
autoreload.main(inner_run)
|
||||
else:
|
||||
inner_run()
|
|
@ -0,0 +1,167 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
from django.core.management.color import no_style
|
||||
from optparse import make_option
|
||||
import sys
|
||||
import os
|
||||
import imp
|
||||
|
||||
try:
|
||||
set
|
||||
except NameError:
|
||||
from sets import Set as set # Python 2.3 fallback
|
||||
|
||||
|
||||
def vararg_callback(option, opt_str, opt_value, parser):
|
||||
parser.rargs.insert(0, opt_value)
|
||||
value = []
|
||||
for arg in parser.rargs:
|
||||
# stop on --foo like options
|
||||
if arg[:2] == "--" and len(arg) > 2:
|
||||
break
|
||||
# stop on -a like options
|
||||
if arg[:1] == "-":
|
||||
break
|
||||
value.append(arg)
|
||||
|
||||
del parser.rargs[:len(value)]
|
||||
setattr(parser.values, option.dest, value)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
option_list = BaseCommand.option_list + (
|
||||
make_option('--fixtures', action='store_true', dest='infixtures', default=False,
|
||||
help='Only look in app.fixtures subdir'),
|
||||
make_option('--noscripts', action='store_true', dest='noscripts', default=False,
|
||||
help='Look in app.scripts subdir'),
|
||||
make_option('-s', '--silent', action='store_true', dest='silent', default=False,
|
||||
help='Run silently, do not show errors and tracebacks'),
|
||||
make_option('--no-traceback', action='store_true', dest='no_traceback', default=False,
|
||||
help='Do not show tracebacks'),
|
||||
make_option('--script-args', action='callback', callback=vararg_callback, type='string',
|
||||
help='Space-separated argument list to be passed to the scripts. Note that the '
|
||||
'same arguments will be passed to all named scripts.'),
|
||||
)
|
||||
help = 'Runs a script in django context.'
|
||||
args = "script [script ...]"
|
||||
|
||||
def handle(self, *scripts, **options):
|
||||
from django.db.models import get_apps
|
||||
|
||||
NOTICE = self.style.SQL_TABLE
|
||||
NOTICE2 = self.style.SQL_FIELD
|
||||
ERROR = self.style.ERROR
|
||||
ERROR2 = self.style.NOTICE
|
||||
|
||||
subdirs = []
|
||||
|
||||
if not options.get('noscripts'):
|
||||
subdirs.append('scripts')
|
||||
if options.get('infixtures'):
|
||||
subdirs.append('fixtures')
|
||||
verbosity = int(options.get('verbosity', 1))
|
||||
show_traceback = options.get('traceback', True)
|
||||
if show_traceback is None:
|
||||
# XXX: traceback is set to None from Django ?
|
||||
show_traceback = True
|
||||
no_traceback = options.get('no_traceback', False)
|
||||
if no_traceback:
|
||||
show_traceback = False
|
||||
silent = options.get('silent', False)
|
||||
if silent:
|
||||
verbosity = 0
|
||||
|
||||
if len(subdirs) < 1:
|
||||
print NOTICE("No subdirs to run left.")
|
||||
return
|
||||
|
||||
if len(scripts) < 1:
|
||||
print ERROR("Script name required.")
|
||||
return
|
||||
|
||||
def run_script(mod, *script_args):
|
||||
try:
|
||||
mod.run(*script_args)
|
||||
except Exception, e:
|
||||
if silent:
|
||||
return
|
||||
if verbosity > 0:
|
||||
print ERROR("Exception while running run() in '%s'" % mod.__name__)
|
||||
if show_traceback:
|
||||
raise
|
||||
|
||||
def my_import(mod):
|
||||
if verbosity > 1:
|
||||
print NOTICE("Check for %s" % mod)
|
||||
# check if module exists before importing
|
||||
try:
|
||||
path = None
|
||||
full_path = mod.split('.')
|
||||
for package in mod.split('.')[:-1]:
|
||||
module_tuple = imp.find_module(package, path)
|
||||
path = imp.load_module(package, *module_tuple).__path__
|
||||
imp.find_module(mod.split('.')[-1], path)
|
||||
except (ImportError, AttributeError):
|
||||
return False
|
||||
|
||||
t = __import__(mod, [], [], [" "])
|
||||
#if verbosity > 1:
|
||||
# print NOTICE("Found script %s ..." % mod)
|
||||
if hasattr(t, "run"):
|
||||
if verbosity > 1:
|
||||
print NOTICE2("Found script '%s' ..." % mod)
|
||||
#if verbosity > 1:
|
||||
# print NOTICE("found run() in %s. executing..." % mod)
|
||||
return t
|
||||
else:
|
||||
if verbosity > 1:
|
||||
print ERROR2("Find script '%s' but no run() function found." % mod)
|
||||
|
||||
def find_modules_for_script(script):
|
||||
""" find script module which contains 'run' attribute """
|
||||
modules = []
|
||||
# first look in apps
|
||||
for app in get_apps():
|
||||
app_name = app.__name__.split(".")[:-1] # + ['fixtures']
|
||||
for subdir in subdirs:
|
||||
mod = my_import(".".join(app_name + [subdir, script]))
|
||||
if mod:
|
||||
modules.append(mod)
|
||||
|
||||
# try app.DIR.script import
|
||||
sa = script.split(".")
|
||||
for subdir in subdirs:
|
||||
nn = ".".join(sa[:-1] + [subdir, sa[-1]])
|
||||
mod = my_import(nn)
|
||||
if mod:
|
||||
modules.append(mod)
|
||||
|
||||
# try direct import
|
||||
if script.find(".") != -1:
|
||||
mod = my_import(script)
|
||||
if mod:
|
||||
modules.append(mod)
|
||||
|
||||
return modules
|
||||
|
||||
if options.get('script_args'):
|
||||
script_args = options['script_args']
|
||||
else:
|
||||
script_args = []
|
||||
for script in scripts:
|
||||
modules = find_modules_for_script(script)
|
||||
if not modules:
|
||||
if verbosity > 0 and not silent:
|
||||
print ERROR("No module for script '%s' found" % script)
|
||||
for mod in modules:
|
||||
if verbosity > 1:
|
||||
print NOTICE2("Running script '%s' ..." % mod.__name__)
|
||||
run_script(mod, *script_args)
|
||||
|
||||
|
||||
# Backwards compatibility for Django r9110
|
||||
if not [opt for opt in Command.option_list if opt.dest == 'verbosity']:
|
||||
Command.option_list += (
|
||||
make_option('--verbosity', '-v', action="store", dest="verbosity",
|
||||
default='1', type='choice', choices=['0', '1', '2'],
|
||||
help="Verbosity level; 0=minimal output, 1=normal output, 2=all output"),
|
||||
)
|
|
@ -0,0 +1,97 @@
|
|||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from optparse import make_option
|
||||
import os
|
||||
import sys
|
||||
|
||||
try:
|
||||
from django.contrib.staticfiles.handlers import StaticFilesHandler
|
||||
USE_STATICFILES = 'django.contrib.staticfiles' in settings.INSTALLED_APPS
|
||||
except ImportError, e:
|
||||
USE_STATICFILES = False
|
||||
|
||||
def null_technical_500_response(request, exc_type, exc_value, tb):
|
||||
raise exc_type, exc_value, tb
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
option_list = BaseCommand.option_list + (
|
||||
make_option('--noreload', action='store_false', dest='use_reloader', default=True,
|
||||
help='Tells Django to NOT use the auto-reloader.'),
|
||||
make_option('--browser', action='store_true', dest='open_browser',
|
||||
help='Tells Django to open a browser.'),
|
||||
make_option('--adminmedia', dest='admin_media_path', default='',
|
||||
help='Specifies the directory from which to serve admin media.'),
|
||||
make_option('--threaded', action='store_true', dest='threaded',
|
||||
help='Run in multithreaded mode.'),
|
||||
)
|
||||
if USE_STATICFILES:
|
||||
option_list += (
|
||||
make_option('--nostatic', action="store_false", dest='use_static_handler', default=True,
|
||||
help='Tells Django to NOT automatically serve static files at STATIC_URL.'),
|
||||
make_option('--insecure', action="store_true", dest='insecure_serving', default=False,
|
||||
help='Allows serving static files even if DEBUG is False.'),
|
||||
)
|
||||
help = "Starts a lightweight Web server for development."
|
||||
args = '[optional port number, or ipaddr:port]'
|
||||
|
||||
# Validation is called explicitly each time the server is reloaded.
|
||||
requires_model_validation = False
|
||||
|
||||
def handle(self, addrport='', *args, **options):
|
||||
import django
|
||||
from django.core.servers.basehttp import run, AdminMediaHandler, WSGIServerException
|
||||
from django.core.handlers.wsgi import WSGIHandler
|
||||
try:
|
||||
from werkzeug import run_simple, DebuggedApplication
|
||||
except ImportError:
|
||||
raise CommandError("Werkzeug is required to use runserver_plus. Please visit http://werkzeug.pocoo.org/download")
|
||||
|
||||
# usurp django's handler
|
||||
from django.views import debug
|
||||
debug.technical_500_response = null_technical_500_response
|
||||
|
||||
if args:
|
||||
raise CommandError('Usage is runserver %s' % self.args)
|
||||
if not addrport:
|
||||
addr = ''
|
||||
port = '8000'
|
||||
else:
|
||||
try:
|
||||
addr, port = addrport.split(':')
|
||||
except ValueError:
|
||||
addr, port = '', addrport
|
||||
if not addr:
|
||||
addr = '127.0.0.1'
|
||||
|
||||
if not port.isdigit():
|
||||
raise CommandError("%r is not a valid port number." % port)
|
||||
|
||||
threaded = options.get('threaded', False)
|
||||
use_reloader = options.get('use_reloader', True)
|
||||
open_browser = options.get('open_browser', False)
|
||||
admin_media_path = options.get('admin_media_path', '')
|
||||
shutdown_message = options.get('shutdown_message', '')
|
||||
quit_command = (sys.platform == 'win32') and 'CTRL-BREAK' or 'CONTROL-C'
|
||||
|
||||
def inner_run():
|
||||
print "Validating models..."
|
||||
self.validate(display_num_errors=True)
|
||||
print "\nDjango version %s, using settings %r" % (django.get_version(), settings.SETTINGS_MODULE)
|
||||
print "Development server is running at http://%s:%s/" % (addr, port)
|
||||
print "Using the Werkzeug debugger (http://werkzeug.pocoo.org/)"
|
||||
print "Quit the server with %s." % quit_command
|
||||
path = admin_media_path or django.__path__[0] + '/contrib/admin/media'
|
||||
handler = AdminMediaHandler(WSGIHandler(), path)
|
||||
if USE_STATICFILES:
|
||||
use_static_handler = options.get('use_static_handler', True)
|
||||
insecure_serving = options.get('insecure_serving', False)
|
||||
if use_static_handler and (settings.DEBUG or insecure_serving) and 'django.contrib.staticfiles' in settings.INSTALLED_APPS:
|
||||
handler = StaticFilesHandler(handler)
|
||||
if open_browser:
|
||||
import webbrowser
|
||||
url = "http://%s:%s/" % (addr, port)
|
||||
webbrowser.open(url)
|
||||
run_simple(addr, int(port), DebuggedApplication(handler, True),
|
||||
use_reloader=use_reloader, use_debugger=True, threaded=threaded)
|
||||
inner_run()
|
|
@ -0,0 +1,76 @@
|
|||
"""
|
||||
set_fake_emails.py
|
||||
|
||||
Give all users a new email account. Useful for testing in a
|
||||
development environment. As such, this command is only available when
|
||||
setting.DEBUG is True.
|
||||
|
||||
"""
|
||||
from optparse import make_option
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import NoArgsCommand, CommandError
|
||||
|
||||
DEFAULT_FAKE_EMAIL = '%(username)s@example.com'
|
||||
|
||||
|
||||
class Command(NoArgsCommand):
|
||||
option_list = NoArgsCommand.option_list + (
|
||||
make_option('--email', dest='default_email', default=DEFAULT_FAKE_EMAIL,
|
||||
help='Use this as the new email format.'),
|
||||
make_option('-a', '--no-admin', action="store_true", dest='no_admin', default=False,
|
||||
help='Do not change administrator accounts'),
|
||||
make_option('-s', '--no-staff', action="store_true", dest='no_staff', default=False,
|
||||
help='Do not change staff accounts'),
|
||||
make_option('--include', dest='include_regexp', default=None,
|
||||
help='Include usernames matching this regexp.'),
|
||||
make_option('--exclude', dest='exclude_regexp', default=None,
|
||||
help='Exclude usernames matching this regexp.'),
|
||||
make_option('--include-groups', dest='include_groups', default=None,
|
||||
help='Include users matching this group. (use comma seperation for multiple groups)'),
|
||||
make_option('--exclude-groups', dest='exclude_groups', default=None,
|
||||
help='Exclude users matching this group. (use comma seperation for multiple groups)'),
|
||||
)
|
||||
help = '''DEBUG only: give all users a new email based on their account data ("%s" by default). Possible parameters are: username, first_name, last_name''' % (DEFAULT_FAKE_EMAIL, )
|
||||
requires_model_validation = False
|
||||
|
||||
def handle_noargs(self, **options):
|
||||
if not settings.DEBUG:
|
||||
raise CommandError('Only available in debug mode')
|
||||
|
||||
from django.contrib.auth.models import User, Group
|
||||
email = options.get('default_email', DEFAULT_FAKE_EMAIL)
|
||||
include_regexp = options.get('include_regexp', None)
|
||||
exclude_regexp = options.get('exclude_regexp', None)
|
||||
include_groups = options.get('include_groups', None)
|
||||
exclude_groups = options.get('exclude_groups', None)
|
||||
no_admin = options.get('no_admin', False)
|
||||
no_staff = options.get('no_staff', False)
|
||||
|
||||
users = User.objects.all()
|
||||
if no_admin:
|
||||
users = users.exclude(is_superuser=True)
|
||||
if no_staff:
|
||||
users = users.exclude(is_staff=True)
|
||||
if exclude_groups:
|
||||
groups = Group.objects.filter(name__in=exclude_groups.split(","))
|
||||
if groups:
|
||||
users = users.exclude(groups__in=groups)
|
||||
else:
|
||||
raise CommandError("No group matches filter: %s" % exclude_groups)
|
||||
if include_groups:
|
||||
groups = Group.objects.filter(name__in=include_groups.split(","))
|
||||
if groups:
|
||||
users = users.filter(groups__in=groups)
|
||||
else:
|
||||
raise CommandError("No groups matches filter: %s" % include_groups)
|
||||
if exclude_regexp:
|
||||
users = users.exclude(username__regex=exclude_regexp)
|
||||
if include_regexp:
|
||||
users = users.filter(username__regex=include_regexp)
|
||||
for user in users:
|
||||
user.email = email % {'username': user.username,
|
||||
'first_name': user.first_name,
|
||||
'last_name': user.last_name}
|
||||
user.save()
|
||||
print 'Changed %d emails' % users.count()
|
|
@ -0,0 +1,44 @@
|
|||
"""
|
||||
set_fake_passwords.py
|
||||
|
||||
Reset all user passwords to a common value. Useful for testing in a
|
||||
development environment. As such, this command is only available when
|
||||
setting.DEBUG is True.
|
||||
|
||||
"""
|
||||
from optparse import make_option
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import NoArgsCommand, CommandError
|
||||
|
||||
DEFAULT_FAKE_PASSWORD = 'password'
|
||||
|
||||
|
||||
class Command(NoArgsCommand):
|
||||
option_list = NoArgsCommand.option_list + (
|
||||
make_option('--prompt', dest='prompt_passwd', default=False, action='store_true',
|
||||
help='Prompts for the new password to apply to all users'),
|
||||
make_option('--password', dest='default_passwd', default=DEFAULT_FAKE_PASSWORD,
|
||||
help='Use this as default password.'),
|
||||
)
|
||||
help = 'DEBUG only: sets all user passwords to a common value ("%s" by default)' % (DEFAULT_FAKE_PASSWORD, )
|
||||
requires_model_validation = False
|
||||
|
||||
def handle_noargs(self, **options):
|
||||
if not settings.DEBUG:
|
||||
raise CommandError('Only available in debug mode')
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
if options.get('prompt_passwd', False):
|
||||
from getpass import getpass
|
||||
passwd = getpass('Password: ')
|
||||
if not passwd:
|
||||
raise CommandError('You must enter a valid password')
|
||||
else:
|
||||
passwd = options.get('default_passwd', DEFAULT_FAKE_PASSWORD)
|
||||
|
||||
user = User()
|
||||
user.set_password(passwd)
|
||||
count = User.objects.all().update(password=user.password)
|
||||
|
||||
print 'Reset %d passwords' % count
|
|
@ -0,0 +1,151 @@
|
|||
import os
|
||||
from django.core.management.base import NoArgsCommand
|
||||
from optparse import make_option
|
||||
|
||||
|
||||
class Command(NoArgsCommand):
|
||||
option_list = NoArgsCommand.option_list + (
|
||||
make_option('--ipython', action='store_true', dest='ipython',
|
||||
help='Tells Django to use IPython, not BPython.'),
|
||||
make_option('--plain', action='store_true', dest='plain',
|
||||
help='Tells Django to use plain Python, not BPython nor IPython.'),
|
||||
make_option('--no-pythonrc', action='store_true', dest='no_pythonrc',
|
||||
help='Tells Django to use plain Python, not IPython.'),
|
||||
make_option('--print-sql', action='store_true', default=False,
|
||||
help="Print SQL queries as they're executed"),
|
||||
make_option('--dont-load', action='append', dest='dont_load', default=[],
|
||||
help='Ignore autoloading of some apps/models. Can be used several times.'),
|
||||
)
|
||||
help = "Like the 'shell' command but autoloads the models of all installed Django apps."
|
||||
|
||||
requires_model_validation = True
|
||||
|
||||
def handle_noargs(self, **options):
|
||||
# XXX: (Temporary) workaround for ticket #1796: force early loading of all
|
||||
# models from installed apps. (this is fixed by now, but leaving it here
|
||||
# for people using 0.96 or older trunk (pre [5919]) versions.
|
||||
from django.db.models.loading import get_models, get_apps
|
||||
loaded_models = get_models()
|
||||
|
||||
use_ipython = options.get('ipython', False)
|
||||
use_plain = options.get('plain', False)
|
||||
use_pythonrc = not options.get('no_pythonrc', True)
|
||||
|
||||
if options.get("print_sql", False):
|
||||
# Code from http://gist.github.com/118990
|
||||
from django.db.backends import util
|
||||
try:
|
||||
import sqlparse
|
||||
except ImportError:
|
||||
sqlparse = None
|
||||
|
||||
class PrintQueryWrapper(util.CursorDebugWrapper):
|
||||
def execute(self, sql, params=()):
|
||||
try:
|
||||
return self.cursor.execute(sql, params)
|
||||
finally:
|
||||
raw_sql = self.db.ops.last_executed_query(self.cursor, sql, params)
|
||||
if sqlparse:
|
||||
print sqlparse.format(raw_sql, reindent=True)
|
||||
else:
|
||||
print raw_sql
|
||||
print
|
||||
|
||||
util.CursorDebugWrapper = PrintQueryWrapper
|
||||
|
||||
# Set up a dictionary to serve as the environment for the shell, so
|
||||
# that tab completion works on objects that are imported at runtime.
|
||||
# See ticket 5082.
|
||||
from django.conf import settings
|
||||
imported_objects = {'settings': settings}
|
||||
|
||||
dont_load_cli = options.get('dont_load') # optparse will set this to [] if it doensnt exists
|
||||
dont_load_conf = getattr(settings, 'SHELL_PLUS_DONT_LOAD', [])
|
||||
dont_load = dont_load_cli + dont_load_conf
|
||||
|
||||
model_aliases = getattr(settings, 'SHELL_PLUS_MODEL_ALIASES', {})
|
||||
|
||||
for app_mod in get_apps():
|
||||
app_models = get_models(app_mod)
|
||||
if not app_models:
|
||||
continue
|
||||
|
||||
app_name = app_mod.__name__.split('.')[-2]
|
||||
if app_name in dont_load:
|
||||
continue
|
||||
|
||||
app_aliases = model_aliases.get(app_name, {})
|
||||
model_labels = []
|
||||
|
||||
for model in app_models:
|
||||
try:
|
||||
imported_object = getattr(__import__(app_mod.__name__, {}, {}, model.__name__), model.__name__)
|
||||
model_name = model.__name__
|
||||
|
||||
if "%s.%s" % (app_name, model_name) in dont_load:
|
||||
continue
|
||||
|
||||
alias = app_aliases.get(model_name, model_name)
|
||||
imported_objects[alias] = imported_object
|
||||
if model_name == alias:
|
||||
model_labels.append(model_name)
|
||||
else:
|
||||
model_labels.append("%s (as %s)" % (model_name, alias))
|
||||
|
||||
except AttributeError, e:
|
||||
print self.style.ERROR("Failed to import '%s' from '%s' reason: %s" % (model.__name__, app_name, str(e)))
|
||||
continue
|
||||
print self.style.SQL_COLTYPE("From '%s' autoload: %s" % (app_mod.__name__.split('.')[-2], ", ".join(model_labels)))
|
||||
|
||||
try:
|
||||
if use_plain:
|
||||
# Don't bother loading B/IPython, because the user wants plain Python.
|
||||
raise ImportError
|
||||
try:
|
||||
if use_ipython:
|
||||
# User wants IPython
|
||||
raise ImportError
|
||||
from bpython import embed
|
||||
embed(imported_objects)
|
||||
except ImportError:
|
||||
try:
|
||||
from IPython import embed
|
||||
embed(user_ns=imported_objects)
|
||||
except ImportError:
|
||||
# IPython < 0.11
|
||||
# Explicitly pass an empty list as arguments, because otherwise
|
||||
# IPython would use sys.argv from this script.
|
||||
try:
|
||||
from IPython.Shell import IPShell
|
||||
shell = IPShell(argv=[], user_ns=imported_objects)
|
||||
shell.mainloop()
|
||||
except ImportError:
|
||||
# IPython not found at all, raise ImportError
|
||||
raise
|
||||
except ImportError:
|
||||
# Using normal Python shell
|
||||
import code
|
||||
try:
|
||||
# Try activating rlcompleter, because it's handy.
|
||||
import readline
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
# We don't have to wrap the following import in a 'try', because
|
||||
# we already know 'readline' was imported successfully.
|
||||
import rlcompleter
|
||||
readline.set_completer(rlcompleter.Completer(imported_objects).complete)
|
||||
readline.parse_and_bind("tab:complete")
|
||||
|
||||
# We want to honor both $PYTHONSTARTUP and .pythonrc.py, so follow system
|
||||
# conventions and get $PYTHONSTARTUP first then import user.
|
||||
if use_pythonrc:
|
||||
pythonrc = os.environ.get("PYTHONSTARTUP")
|
||||
if pythonrc and os.path.isfile(pythonrc):
|
||||
try:
|
||||
execfile(pythonrc)
|
||||
except NameError:
|
||||
pass
|
||||
# This will import .pythonrc.py as a side-effect
|
||||
import user
|
||||
code.interact(local=imported_objects)
|
|
@ -0,0 +1,101 @@
|
|||
from django.conf import settings
|
||||
from django.template import get_library
|
||||
import os
|
||||
import inspect
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.core.management import color
|
||||
from django.utils import termcolors
|
||||
|
||||
|
||||
def color_style():
|
||||
style = color.color_style()
|
||||
style.FILTER = termcolors.make_style(fg='yellow', opts=('bold',))
|
||||
style.MODULE_NAME = termcolors.make_style(fg='green', opts=('bold',))
|
||||
style.TAG = termcolors.make_style(fg='red', opts=('bold',))
|
||||
style.TAGLIB = termcolors.make_style(fg='blue', opts=('bold',))
|
||||
return style
|
||||
|
||||
|
||||
def format_block(block, nlspaces=0):
|
||||
'''Format the given block of text, trimming leading/trailing
|
||||
empty lines and any leading whitespace that is common to all lines.
|
||||
The purpose is to let us list a code block as a multiline,
|
||||
triple-quoted Python string, taking care of
|
||||
indentation concerns.
|
||||
http://code.activestate.com/recipes/145672/'''
|
||||
|
||||
import re
|
||||
|
||||
# separate block into lines
|
||||
lines = str(block).split('\n')
|
||||
|
||||
# remove leading/trailing empty lines
|
||||
while lines and not lines[0]:
|
||||
del lines[0]
|
||||
while lines and not lines[-1]:
|
||||
del lines[-1]
|
||||
|
||||
# look at first line to see how much indentation to trim
|
||||
ws = re.match(r'\s*', lines[0]).group(0)
|
||||
if ws:
|
||||
lines = map(lambda x: x.replace(ws, '', 1), lines)
|
||||
|
||||
# remove leading/trailing blank lines (after leading ws removal)
|
||||
# we do this again in case there were pure-whitespace lines
|
||||
while lines and not lines[0]:
|
||||
del lines[0]
|
||||
while lines and not lines[-1]:
|
||||
del lines[-1]
|
||||
|
||||
# account for user-specified leading spaces
|
||||
flines = ['%s%s' % (' ' * nlspaces, line) for line in lines]
|
||||
|
||||
return '\n'.join(flines) + '\n'
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Displays template tags and filters available in the current project."
|
||||
results = ""
|
||||
|
||||
def add_result(self, s, depth=0):
|
||||
self.results += '\n%s\n' % s.rjust(depth * 4 + len(s))
|
||||
|
||||
def handle(self, *args, **options):
|
||||
if args:
|
||||
appname, = args
|
||||
|
||||
style = color_style()
|
||||
|
||||
if settings.ADMIN_FOR:
|
||||
settings_modules = [__import__(m, {}, {}, ['']) for m in settings.ADMIN_FOR]
|
||||
else:
|
||||
settings_modules = [settings]
|
||||
|
||||
for settings_mod in settings_modules:
|
||||
for app in settings_mod.INSTALLED_APPS:
|
||||
try:
|
||||
templatetag_mod = __import__(app + '.templatetags', {}, {}, [''])
|
||||
except ImportError:
|
||||
continue
|
||||
mod_path = inspect.getabsfile(templatetag_mod)
|
||||
mod_files = os.listdir(os.path.dirname(mod_path))
|
||||
tag_files = [i.rstrip('.py') for i in mod_files if i.endswith('.py') and i[0] != '_']
|
||||
app_labeled = False
|
||||
for taglib in tag_files:
|
||||
try:
|
||||
lib = get_library("django.templatetags.%s" % taglib)
|
||||
except:
|
||||
continue
|
||||
if not app_labeled:
|
||||
self.add_result('\nApp: %s' % style.MODULE_NAME(app))
|
||||
app_labeled = True
|
||||
self.add_result('load: %s' % style.TAGLIB(taglib), 1)
|
||||
for items, label, style_func in [(lib.tags, 'Tag:', style.TAG),
|
||||
(lib.filters, 'Filter:', style.FILTER)]:
|
||||
for item in items:
|
||||
self.add_result('%s %s' % (label, style_func(item)), 2)
|
||||
doc = inspect.getdoc(items[item])
|
||||
if doc:
|
||||
self.add_result(format_block(doc, 12))
|
||||
return self.results
|
||||
# return "\n".join(results)
|
|
@ -0,0 +1,77 @@
|
|||
from django.conf import settings
|
||||
from django.core.exceptions import ViewDoesNotExist
|
||||
from django.core.management.base import BaseCommand
|
||||
try:
|
||||
# 2008-05-30 admindocs found in newforms-admin brand
|
||||
from django.contrib.admindocs.views import simplify_regex
|
||||
except ImportError:
|
||||
# fall back to trunk, pre-NFA merge
|
||||
from django.contrib.admin.views.doc import simplify_regex
|
||||
import re
|
||||
|
||||
from django_extensions.management.color import color_style
|
||||
|
||||
|
||||
def extract_views_from_urlpatterns(urlpatterns, base=''):
|
||||
"""
|
||||
Return a list of views from a list of urlpatterns.
|
||||
|
||||
Each object in the returned list is a two-tuple: (view_func, regex)
|
||||
"""
|
||||
views = []
|
||||
for p in urlpatterns:
|
||||
if hasattr(p, '_get_callback'):
|
||||
try:
|
||||
views.append((p._get_callback(), base + p.regex.pattern, p.name))
|
||||
except ViewDoesNotExist:
|
||||
continue
|
||||
elif hasattr(p, '_get_url_patterns'):
|
||||
try:
|
||||
patterns = p.url_patterns
|
||||
except ImportError:
|
||||
continue
|
||||
views.extend(extract_views_from_urlpatterns(patterns, base + p.regex.pattern))
|
||||
else:
|
||||
raise TypeError, _("%s does not appear to be a urlpattern object") % p
|
||||
return views
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Displays all of the url matching routes for the project."
|
||||
|
||||
requires_model_validation = True
|
||||
|
||||
def handle(self, *args, **options):
|
||||
if args:
|
||||
appname, = args
|
||||
|
||||
style = color_style()
|
||||
|
||||
if settings.ADMIN_FOR:
|
||||
settings_modules = [__import__(m, {}, {}, ['']) for m in settings.ADMIN_FOR]
|
||||
else:
|
||||
settings_modules = [settings]
|
||||
|
||||
views = []
|
||||
for settings_mod in settings_modules:
|
||||
try:
|
||||
urlconf = __import__(settings_mod.ROOT_URLCONF, {}, {}, [''])
|
||||
except Exception, e:
|
||||
if options.get('traceback', None):
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
print style.ERROR("Error occurred while trying to load %s: %s" % (settings_mod.ROOT_URLCONF, str(e)))
|
||||
continue
|
||||
view_functions = extract_views_from_urlpatterns(urlconf.urlpatterns)
|
||||
for (func, regex, url_name) in view_functions:
|
||||
if hasattr(func, '__name__'):
|
||||
func_name = func.__name__
|
||||
elif hasattr(func, '__class__'):
|
||||
func_name = '%s()' % func.__class__.__name__
|
||||
else:
|
||||
func_name = re.sub(r' at 0x[0-9a-f]+', '', repr(func))
|
||||
views.append("%(url)s\t%(module)s.%(name)s\t%(url_name)s" % {'name': style.MODULE_NAME(func_name),
|
||||
'module': style.MODULE(func.__module__),
|
||||
'url_name': style.URL_NAME(url_name or ''),
|
||||
'url': style.URL(simplify_regex(regex))})
|
||||
return "\n".join([v for v in views])
|
|
@ -0,0 +1,70 @@
|
|||
from django.core.management.base import NoArgsCommand
|
||||
from django.conf import settings
|
||||
import sys
|
||||
|
||||
class Command(NoArgsCommand):
|
||||
help = """Generates the SQL to create your database for you, as specified in settings.py
|
||||
The envisioned use case is something like this:
|
||||
|
||||
./manage.py sqlcreate [--router=<routername>] | mysql -u <db_administrator> -p
|
||||
./manage.py sqlcreate [--router=<routername>] | psql -U <db_administrator> -W"""
|
||||
|
||||
requires_model_validation = False
|
||||
can_import_settings = True
|
||||
|
||||
def set_db_settings(self, *args, **options):
|
||||
if django.get_version() >= "1.2":
|
||||
router = options.get('router')
|
||||
if router == None:
|
||||
return False
|
||||
|
||||
# retrieve this with the 'using' argument
|
||||
dbinfo = settings.DATABASES.get(router)
|
||||
settings.DATABASE_ENGINE = dbinfo.get('ENGINE').split('.')[-1]
|
||||
settings.DATABASE_USER = dbinfo.get('USER')
|
||||
settings.DATABASE_PASSWORD = dbinfo.get('PASSWORD')
|
||||
settings.DATABASE_NAME = dbinfo.get('NAME')
|
||||
settings.DATABASE_HOST = dbinfo.get('HOST')
|
||||
settings.DATABASE_PORT = dbinfo.get('PORT')
|
||||
return True
|
||||
else:
|
||||
# settings are set for django < 1.2 no modification needed
|
||||
return True
|
||||
|
||||
def handle_noargs(self, **options):
|
||||
|
||||
if django.get_version() >= "1.2":
|
||||
got_db_settings = self.set_db_settings(*args, **options)
|
||||
if not got_db_settings:
|
||||
raise CommandError("You are using Django %s which requires to specify the db-router.\nPlease specify the router by adding --router=<routername> to this command." % django.get_version())
|
||||
|
||||
#print "%s %s %s %s" % (settings.DATABASE_ENGINE, settings.DATABASE_NAME, settings.DATABASE_USER, settings.DATABASE_PASSWORD)
|
||||
engine = settings.DATABASE_ENGINE
|
||||
dbname = settings.DATABASE_NAME
|
||||
dbuser = settings.DATABASE_USER
|
||||
dbpass = settings.DATABASE_PASSWORD
|
||||
dbhost = settings.DATABASE_HOST
|
||||
|
||||
# django settings file tells you that localhost should be specified by leaving
|
||||
# the DATABASE_HOST blank
|
||||
if not dbhost:
|
||||
dbhost = 'localhost'
|
||||
|
||||
if engine == 'mysql':
|
||||
sys.stderr.write("""-- WARNING!: https://docs.djangoproject.com/en/dev/ref/databases/#collation-settings
|
||||
-- Please read this carefully! Collation will be set to utf8_bin to have case-sensitive data.
|
||||
""");
|
||||
print "CREATE DATABASE %s CHARACTER SET utf8 COLLATE utf8_bin;" % dbname
|
||||
print "GRANT ALL PRIVILEGES ON %s.* to '%s'@'%s' identified by '%s';" % (
|
||||
dbname, dbuser, dbhost, dbpass)
|
||||
elif engine == 'postgresql_psycopg2':
|
||||
print "CREATE USER %s WITH ENCRYPTED PASSWORD '%s';" % (dbuser, dbpass)
|
||||
print "CREATE DATABASE %s WITH ENCODING 'UTF-8' OWNER \"%s\";" % (dbname, dbuser)
|
||||
#print "GRANT ALL PRIVILEGES ON DATABASE %s TO %s" % (dbname, dbuser)
|
||||
elif engine == 'sqlite3':
|
||||
sys.stderr.write("-- manage.py syncdb will automatically create a sqlite3 database file.\n")
|
||||
else:
|
||||
# CREATE DATABASE is not SQL standard, but seems to be supported by most.
|
||||
sys.stderr.write("-- Don't know how to handle '%s' falling back to SQL.\n" % engine)
|
||||
print "CREATE DATABASE %s;" % dbname
|
||||
print "GRANT ALL PRIVILEGES ON DATABASE %s to %s" % (dbname, dbuser)
|
|
@ -0,0 +1,627 @@
|
|||
"""
|
||||
sqldiff.py - Prints the (approximated) difference between models and database
|
||||
|
||||
TODO:
|
||||
- better support for relations
|
||||
- better support for constraints (mainly postgresql?)
|
||||
- support for table spaces with postgresql
|
||||
- when a table is not managed (meta.managed==False) then only do a one-way
|
||||
sqldiff ? show differences from db->table but not the other way around since
|
||||
it's not managed.
|
||||
|
||||
KNOWN ISSUES:
|
||||
- MySQL has by far the most problems with introspection. Please be
|
||||
carefull when using MySQL with sqldiff.
|
||||
- Booleans are reported back as Integers, so there's know way to know if
|
||||
there was a real change.
|
||||
- Varchar sizes are reported back without unicode support so their size
|
||||
may change in comparison to the real length of the varchar.
|
||||
- Some of the 'fixes' to counter these problems might create false
|
||||
positives or false negatives.
|
||||
"""
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.core.management import sql as _sql
|
||||
from django.core.management import CommandError
|
||||
from django.core.management.color import no_style
|
||||
from django.db import transaction, connection
|
||||
from django.db.models.fields import IntegerField
|
||||
from optparse import make_option
|
||||
|
||||
ORDERING_FIELD = IntegerField('_order', null=True)
|
||||
|
||||
|
||||
def flatten(l, ltypes=(list, tuple)):
|
||||
ltype = type(l)
|
||||
l = list(l)
|
||||
i = 0
|
||||
while i < len(l):
|
||||
while isinstance(l[i], ltypes):
|
||||
if not l[i]:
|
||||
l.pop(i)
|
||||
i -= 1
|
||||
break
|
||||
else:
|
||||
l[i:i + 1] = l[i]
|
||||
i += 1
|
||||
return ltype(l)
|
||||
|
||||
|
||||
def all_local_fields(meta):
|
||||
all_fields = meta.local_fields[:]
|
||||
for parent in meta.parents:
|
||||
all_fields.extend(all_local_fields(parent._meta))
|
||||
return all_fields
|
||||
|
||||
|
||||
class SQLDiff(object):
|
||||
DATA_TYPES_REVERSE_OVERRIDE = {}
|
||||
|
||||
DIFF_TYPES = [
|
||||
'error',
|
||||
'comment',
|
||||
'table-missing-in-db',
|
||||
'field-missing-in-db',
|
||||
'field-missing-in-model',
|
||||
'index-missing-in-db',
|
||||
'index-missing-in-model',
|
||||
'unique-missing-in-db',
|
||||
'unique-missing-in-model',
|
||||
'field-type-differ',
|
||||
'field-parameter-differ',
|
||||
]
|
||||
DIFF_TEXTS = {
|
||||
'error': 'error: %(0)s',
|
||||
'comment': 'comment: %(0)s',
|
||||
'table-missing-in-db': "table '%(0)s' missing in database",
|
||||
'field-missing-in-db': "field '%(1)s' defined in model but missing in database",
|
||||
'field-missing-in-model': "field '%(1)s' defined in database but missing in model",
|
||||
'index-missing-in-db': "field '%(1)s' INDEX defined in model but missing in database",
|
||||
'index-missing-in-model': "field '%(1)s' INDEX defined in database schema but missing in model",
|
||||
'unique-missing-in-db': "field '%(1)s' UNIQUE defined in model but missing in database",
|
||||
'unique-missing-in-model': "field '%(1)s' UNIQUE defined in database schema but missing in model",
|
||||
'field-type-differ': "field '%(1)s' not of same type: db='%(3)s', model='%(2)s'",
|
||||
'field-parameter-differ': "field '%(1)s' parameters differ: db='%(3)s', model='%(2)s'",
|
||||
}
|
||||
|
||||
SQL_FIELD_MISSING_IN_DB = lambda self, style, qn, args: "%s %s\n\t%s %s %s;" % (style.SQL_KEYWORD('ALTER TABLE'), style.SQL_TABLE(qn(args[0])), style.SQL_KEYWORD('ADD'), style.SQL_FIELD(qn(args[1])), style.SQL_COLTYPE(args[2]))
|
||||
SQL_FIELD_MISSING_IN_MODEL = lambda self, style, qn, args: "%s %s\n\t%s %s;" % (style.SQL_KEYWORD('ALTER TABLE'), style.SQL_TABLE(qn(args[0])), style.SQL_KEYWORD('DROP COLUMN'), style.SQL_FIELD(qn(args[1])))
|
||||
SQL_INDEX_MISSING_IN_DB = lambda self, style, qn, args: "%s %s\n\t%s %s (%s);" % (style.SQL_KEYWORD('CREATE INDEX'), style.SQL_TABLE(qn("%s_idx" % '_'.join(args[0:2]))), style.SQL_KEYWORD('ON'), style.SQL_TABLE(qn(args[0])), style.SQL_FIELD(qn(args[1])))
|
||||
# FIXME: need to lookup index name instead of just appending _idx to table + fieldname
|
||||
SQL_INDEX_MISSING_IN_MODEL = lambda self, style, qn, args: "%s %s;" % (style.SQL_KEYWORD('DROP INDEX'), style.SQL_TABLE(qn("%s_idx" % '_'.join(args[0:2]))))
|
||||
SQL_UNIQUE_MISSING_IN_DB = lambda self, style, qn, args: "%s %s\n\t%s %s (%s);" % (style.SQL_KEYWORD('ALTER TABLE'), style.SQL_TABLE(qn(args[0])), style.SQL_KEYWORD('ADD'), style.SQL_KEYWORD('UNIQUE'), style.SQL_FIELD(qn(args[1])))
|
||||
# FIXME: need to lookup unique constraint name instead of appending _key to table + fieldname
|
||||
SQL_UNIQUE_MISSING_IN_MODEL = lambda self, style, qn, args: "%s %s\n\t%s %s %s;" % (style.SQL_KEYWORD('ALTER TABLE'), style.SQL_TABLE(qn(args[0])), style.SQL_KEYWORD('DROP'), style.SQL_KEYWORD('CONSTRAINT'), style.SQL_TABLE(qn("%s_key" % ('_'.join(args[:2])))))
|
||||
SQL_FIELD_TYPE_DIFFER = lambda self, style, qn, args: "%s %s\n\t%s %s %s;" % (style.SQL_KEYWORD('ALTER TABLE'), style.SQL_TABLE(qn(args[0])), style.SQL_KEYWORD("MODIFY"), style.SQL_FIELD(qn(args[1])), style.SQL_COLTYPE(args[2]))
|
||||
SQL_FIELD_PARAMETER_DIFFER = lambda self, style, qn, args: "%s %s\n\t%s %s %s;" % (style.SQL_KEYWORD('ALTER TABLE'), style.SQL_TABLE(qn(args[0])), style.SQL_KEYWORD("MODIFY"), style.SQL_FIELD(qn(args[1])), style.SQL_COLTYPE(args[2]))
|
||||
SQL_ERROR = lambda self, style, qn, args: style.NOTICE('-- Error: %s' % style.ERROR(args[0]))
|
||||
SQL_COMMENT = lambda self, style, qn, args: style.NOTICE('-- Comment: %s' % style.SQL_TABLE(args[0]))
|
||||
SQL_TABLE_MISSING_IN_DB = lambda self, style, qn, args: style.NOTICE('-- Table missing: %s' % args[0])
|
||||
|
||||
def __init__(self, app_models, options):
|
||||
self.app_models = app_models
|
||||
self.options = options
|
||||
self.dense = options.get('dense_output', False)
|
||||
|
||||
try:
|
||||
self.introspection = connection.introspection
|
||||
except AttributeError:
|
||||
from django.db import get_introspection_module
|
||||
self.introspection = get_introspection_module()
|
||||
|
||||
self.cursor = connection.cursor()
|
||||
self.django_tables = self.get_django_tables(options.get('only_existing', True))
|
||||
self.db_tables = self.introspection.get_table_list(self.cursor)
|
||||
self.differences = []
|
||||
self.unknown_db_fields = {}
|
||||
|
||||
self.DIFF_SQL = {
|
||||
'error': self.SQL_ERROR,
|
||||
'comment': self.SQL_COMMENT,
|
||||
'table-missing-in-db': self.SQL_TABLE_MISSING_IN_DB,
|
||||
'field-missing-in-db': self.SQL_FIELD_MISSING_IN_DB,
|
||||
'field-missing-in-model': self.SQL_FIELD_MISSING_IN_MODEL,
|
||||
'index-missing-in-db': self.SQL_INDEX_MISSING_IN_DB,
|
||||
'index-missing-in-model': self.SQL_INDEX_MISSING_IN_MODEL,
|
||||
'unique-missing-in-db': self.SQL_UNIQUE_MISSING_IN_DB,
|
||||
'unique-missing-in-model': self.SQL_UNIQUE_MISSING_IN_MODEL,
|
||||
'field-type-differ': self.SQL_FIELD_TYPE_DIFFER,
|
||||
'field-parameter-differ': self.SQL_FIELD_PARAMETER_DIFFER,
|
||||
}
|
||||
|
||||
def add_app_model_marker(self, app_label, model_name):
|
||||
self.differences.append((app_label, model_name, []))
|
||||
|
||||
def add_difference(self, diff_type, *args):
|
||||
assert diff_type in self.DIFF_TYPES, 'Unknown difference type'
|
||||
self.differences[-1][-1].append((diff_type, args))
|
||||
|
||||
def get_django_tables(self, only_existing):
|
||||
try:
|
||||
django_tables = self.introspection.django_table_names(only_existing=only_existing)
|
||||
except AttributeError:
|
||||
# backwards compatibility for before introspection refactoring (r8296)
|
||||
try:
|
||||
django_tables = _sql.django_table_names(only_existing=only_existing)
|
||||
except AttributeError:
|
||||
# backwards compatibility for before svn r7568
|
||||
django_tables = _sql.django_table_list(only_existing=only_existing)
|
||||
return django_tables
|
||||
|
||||
def sql_to_dict(self, query, param):
|
||||
""" sql_to_dict(query, param) -> list of dicts
|
||||
|
||||
code from snippet at http://www.djangosnippets.org/snippets/1383/
|
||||
"""
|
||||
cursor = connection.cursor()
|
||||
cursor.execute(query, param)
|
||||
fieldnames = [name[0] for name in cursor.description]
|
||||
result = []
|
||||
for row in cursor.fetchall():
|
||||
rowset = []
|
||||
for field in zip(fieldnames, row):
|
||||
rowset.append(field)
|
||||
result.append(dict(rowset))
|
||||
return result
|
||||
|
||||
def get_field_model_type(self, field):
|
||||
return field.db_type(connection=connection)
|
||||
|
||||
def get_field_db_type(self, description, field=None, table_name=None):
|
||||
from django.db import models
|
||||
# DB-API cursor.description
|
||||
#(name, type_code, display_size, internal_size, precision, scale, null_ok) = description
|
||||
type_code = description[1]
|
||||
if type_code in self.DATA_TYPES_REVERSE_OVERRIDE:
|
||||
reverse_type = self.DATA_TYPES_REVERSE_OVERRIDE[type_code]
|
||||
else:
|
||||
try:
|
||||
try:
|
||||
reverse_type = self.introspection.data_types_reverse[type_code]
|
||||
except AttributeError:
|
||||
# backwards compatibility for before introspection refactoring (r8296)
|
||||
reverse_type = self.introspection.DATA_TYPES_REVERSE.get(type_code)
|
||||
except KeyError:
|
||||
# type_code not found in data_types_reverse map
|
||||
key = (self.differences[-1][:2], description[:2])
|
||||
if key not in self.unknown_db_fields:
|
||||
self.unknown_db_fields[key] = 1
|
||||
self.add_difference('comment', "Unknown database type for field '%s' (%s)" % (description[0], type_code))
|
||||
return None
|
||||
|
||||
kwargs = {}
|
||||
if isinstance(reverse_type, tuple):
|
||||
kwargs.update(reverse_type[1])
|
||||
reverse_type = reverse_type[0]
|
||||
|
||||
if reverse_type == "CharField" and description[3]:
|
||||
kwargs['max_length'] = description[3]
|
||||
|
||||
if reverse_type == "DecimalField":
|
||||
kwargs['max_digits'] = description[4]
|
||||
kwargs['decimal_places'] = description[5] and abs(description[5]) or description[5]
|
||||
|
||||
if description[6]:
|
||||
kwargs['blank'] = True
|
||||
if not reverse_type in ('TextField', 'CharField'):
|
||||
kwargs['null'] = True
|
||||
|
||||
if '.' in reverse_type:
|
||||
from django.utils import importlib
|
||||
# TODO: when was importlib added to django.utils ? and do we
|
||||
# need to add backwards compatibility code ?
|
||||
module_path, package_name = reverse_type.rsplit('.', 1)
|
||||
module = importlib.import_module(module_path)
|
||||
field_db_type = getattr(module, package_name)(**kwargs).db_type(connection=connection)
|
||||
else:
|
||||
field_db_type = getattr(models, reverse_type)(**kwargs).db_type(connection=connection)
|
||||
return field_db_type
|
||||
|
||||
def strip_parameters(self, field_type):
|
||||
if field_type and field_type != 'double precision':
|
||||
return field_type.split(" ")[0].split("(")[0]
|
||||
return field_type
|
||||
|
||||
def find_unique_missing_in_db(self, meta, table_indexes, table_name):
|
||||
for field in all_local_fields(meta):
|
||||
if field.unique:
|
||||
attname = field.db_column or field.attname
|
||||
if attname in table_indexes and table_indexes[attname]['unique']:
|
||||
continue
|
||||
self.add_difference('unique-missing-in-db', table_name, attname)
|
||||
|
||||
def find_unique_missing_in_model(self, meta, table_indexes, table_name):
|
||||
# TODO: Postgresql does not list unique_togethers in table_indexes
|
||||
# MySQL does
|
||||
fields = dict([(field.db_column or field.name, field.unique) for field in all_local_fields(meta)])
|
||||
for att_name, att_opts in table_indexes.iteritems():
|
||||
if att_opts['unique'] and att_name in fields and not fields[att_name]:
|
||||
if att_name in flatten(meta.unique_together):
|
||||
continue
|
||||
self.add_difference('unique-missing-in-model', table_name, att_name)
|
||||
|
||||
def find_index_missing_in_db(self, meta, table_indexes, table_name):
|
||||
for field in all_local_fields(meta):
|
||||
if field.db_index:
|
||||
attname = field.db_column or field.attname
|
||||
if not attname in table_indexes:
|
||||
self.add_difference('index-missing-in-db', table_name, attname)
|
||||
|
||||
def find_index_missing_in_model(self, meta, table_indexes, table_name):
|
||||
fields = dict([(field.name, field) for field in all_local_fields(meta)])
|
||||
for att_name, att_opts in table_indexes.iteritems():
|
||||
if att_name in fields:
|
||||
field = fields[att_name]
|
||||
if field.db_index:
|
||||
continue
|
||||
if att_opts['primary_key'] and field.primary_key:
|
||||
continue
|
||||
if att_opts['unique'] and field.unique:
|
||||
continue
|
||||
if att_opts['unique'] and att_name in flatten(meta.unique_together):
|
||||
continue
|
||||
self.add_difference('index-missing-in-model', table_name, att_name)
|
||||
|
||||
def find_field_missing_in_model(self, fieldmap, table_description, table_name):
|
||||
for row in table_description:
|
||||
if row[0] not in fieldmap:
|
||||
self.add_difference('field-missing-in-model', table_name, row[0])
|
||||
|
||||
def find_field_missing_in_db(self, fieldmap, table_description, table_name):
|
||||
db_fields = [row[0] for row in table_description]
|
||||
for field_name, field in fieldmap.iteritems():
|
||||
if field_name not in db_fields:
|
||||
self.add_difference('field-missing-in-db', table_name, field_name,
|
||||
field.db_type(connection=connection))
|
||||
|
||||
def find_field_type_differ(self, meta, table_description, table_name, func=None):
|
||||
db_fields = dict([(row[0], row) for row in table_description])
|
||||
for field in all_local_fields(meta):
|
||||
if field.name not in db_fields:
|
||||
continue
|
||||
description = db_fields[field.name]
|
||||
|
||||
model_type = self.strip_parameters(self.get_field_model_type(field))
|
||||
db_type = self.strip_parameters(self.get_field_db_type(description, field))
|
||||
|
||||
# use callback function if defined
|
||||
if func:
|
||||
model_type, db_type = func(field, description, model_type, db_type)
|
||||
|
||||
if not model_type == db_type:
|
||||
self.add_difference('field-type-differ', table_name, field.name, model_type, db_type)
|
||||
|
||||
def find_field_parameter_differ(self, meta, table_description, table_name, func=None):
|
||||
db_fields = dict([(row[0], row) for row in table_description])
|
||||
for field in all_local_fields(meta):
|
||||
if field.name not in db_fields:
|
||||
continue
|
||||
description = db_fields[field.name]
|
||||
|
||||
model_type = self.get_field_model_type(field)
|
||||
db_type = self.get_field_db_type(description, field, table_name)
|
||||
|
||||
if not self.strip_parameters(model_type) == self.strip_parameters(db_type):
|
||||
continue
|
||||
|
||||
# use callback function if defined
|
||||
if func:
|
||||
model_type, db_type = func(field, description, model_type, db_type)
|
||||
|
||||
if not model_type == db_type:
|
||||
self.add_difference('field-parameter-differ', table_name, field.name, model_type, db_type)
|
||||
|
||||
@transaction.commit_manually
|
||||
def find_differences(self):
|
||||
cur_app_label = None
|
||||
for app_model in self.app_models:
|
||||
meta = app_model._meta
|
||||
table_name = meta.db_table
|
||||
app_label = meta.app_label
|
||||
|
||||
if cur_app_label != app_label:
|
||||
# Marker indicating start of difference scan for this table_name
|
||||
self.add_app_model_marker(app_label, app_model.__name__)
|
||||
|
||||
#if not table_name in self.django_tables:
|
||||
if not table_name in self.db_tables:
|
||||
# Table is missing from database
|
||||
self.add_difference('table-missing-in-db', table_name)
|
||||
continue
|
||||
|
||||
table_indexes = self.introspection.get_indexes(self.cursor, table_name)
|
||||
fieldmap = dict([(field.db_column or field.get_attname(), field) for field in all_local_fields(meta)])
|
||||
|
||||
# add ordering field if model uses order_with_respect_to
|
||||
if meta.order_with_respect_to:
|
||||
fieldmap['_order'] = ORDERING_FIELD
|
||||
|
||||
try:
|
||||
table_description = self.introspection.get_table_description(self.cursor, table_name)
|
||||
except Exception, e:
|
||||
self.add_difference('error', 'unable to introspect table: %s' % str(e).strip())
|
||||
transaction.rollback() # reset transaction
|
||||
continue
|
||||
else:
|
||||
transaction.commit()
|
||||
# Fields which are defined in database but not in model
|
||||
# 1) find: 'unique-missing-in-model'
|
||||
self.find_unique_missing_in_model(meta, table_indexes, table_name)
|
||||
# 2) find: 'index-missing-in-model'
|
||||
self.find_index_missing_in_model(meta, table_indexes, table_name)
|
||||
# 3) find: 'field-missing-in-model'
|
||||
self.find_field_missing_in_model(fieldmap, table_description, table_name)
|
||||
|
||||
# Fields which are defined in models but not in database
|
||||
# 4) find: 'field-missing-in-db'
|
||||
self.find_field_missing_in_db(fieldmap, table_description, table_name)
|
||||
# 5) find: 'unique-missing-in-db'
|
||||
self.find_unique_missing_in_db(meta, table_indexes, table_name)
|
||||
# 6) find: 'index-missing-in-db'
|
||||
self.find_index_missing_in_db(meta, table_indexes, table_name)
|
||||
|
||||
# Fields which have a different type or parameters
|
||||
# 7) find: 'type-differs'
|
||||
self.find_field_type_differ(meta, table_description, table_name)
|
||||
# 8) find: 'type-parameter-differs'
|
||||
self.find_field_parameter_differ(meta, table_description, table_name)
|
||||
|
||||
def print_diff(self, style=no_style()):
|
||||
""" print differences to stdout """
|
||||
if self.options.get('sql', True):
|
||||
self.print_diff_sql(style)
|
||||
else:
|
||||
self.print_diff_text(style)
|
||||
|
||||
def print_diff_text(self, style):
|
||||
cur_app_label = None
|
||||
for app_label, model_name, diffs in self.differences:
|
||||
if not diffs:
|
||||
continue
|
||||
if not self.dense and cur_app_label != app_label:
|
||||
print style.NOTICE("+ Application:"), style.SQL_TABLE(app_label)
|
||||
cur_app_label = app_label
|
||||
if not self.dense:
|
||||
print style.NOTICE("|-+ Differences for model:"), style.SQL_TABLE(model_name)
|
||||
for diff in diffs:
|
||||
diff_type, diff_args = diff
|
||||
text = self.DIFF_TEXTS[diff_type] % dict((str(i), style.SQL_TABLE(e)) for i, e in enumerate(diff_args))
|
||||
text = "'".join(i % 2 == 0 and style.ERROR(e) or e for i, e in enumerate(text.split("'")))
|
||||
if not self.dense:
|
||||
print style.NOTICE("|--+"), text
|
||||
else:
|
||||
print style.NOTICE("App"), style.SQL_TABLE(app_label), style.NOTICE('Model'), style.SQL_TABLE(model_name), text
|
||||
|
||||
def print_diff_sql(self, style):
|
||||
cur_app_label = None
|
||||
qn = connection.ops.quote_name
|
||||
has_differences = max([len(diffs) for app_label, model_name, diffs in self.differences])
|
||||
if not has_differences:
|
||||
if not self.dense:
|
||||
print style.SQL_KEYWORD("-- No differences")
|
||||
else:
|
||||
print style.SQL_KEYWORD("BEGIN;")
|
||||
for app_label, model_name, diffs in self.differences:
|
||||
if not diffs:
|
||||
continue
|
||||
if not self.dense and cur_app_label != app_label:
|
||||
print style.NOTICE("-- Application: %s" % style.SQL_TABLE(app_label))
|
||||
cur_app_label = app_label
|
||||
if not self.dense:
|
||||
print style.NOTICE("-- Model: %s" % style.SQL_TABLE(model_name))
|
||||
for diff in diffs:
|
||||
diff_type, diff_args = diff
|
||||
text = self.DIFF_SQL[diff_type](style, qn, diff_args)
|
||||
if self.dense:
|
||||
text = text.replace("\n\t", " ")
|
||||
print text
|
||||
print style.SQL_KEYWORD("COMMIT;")
|
||||
|
||||
|
||||
class GenericSQLDiff(SQLDiff):
|
||||
pass
|
||||
|
||||
|
||||
class MySQLDiff(SQLDiff):
|
||||
# All the MySQL hacks together create something of a problem
|
||||
# Fixing one bug in MySQL creates another issue. So just keep in mind
|
||||
# that this is way unreliable for MySQL atm.
|
||||
def get_field_db_type(self, description, field=None, table_name=None):
|
||||
from MySQLdb.constants import FIELD_TYPE
|
||||
# weird bug? in mysql db-api where it returns three times the correct value for field length
|
||||
# if i remember correctly it had something todo with unicode strings
|
||||
# TODO: Fix this is a more meaningful and better understood manner
|
||||
description = list(description)
|
||||
if description[1] not in [FIELD_TYPE.TINY, FIELD_TYPE.SHORT]: # exclude tinyints from conversion.
|
||||
description[3] = description[3] / 3
|
||||
description[4] = description[4] / 3
|
||||
db_type = super(MySQLDiff, self).get_field_db_type(description)
|
||||
if not db_type:
|
||||
return
|
||||
if field:
|
||||
if field.primary_key and (db_type == 'integer' or db_type == 'bigint'):
|
||||
db_type += ' AUTO_INCREMENT'
|
||||
# MySQL isn't really sure about char's and varchar's like sqlite
|
||||
field_type = self.get_field_model_type(field)
|
||||
# Fix char/varchar inconsistencies
|
||||
if self.strip_parameters(field_type) == 'char' and self.strip_parameters(db_type) == 'varchar':
|
||||
db_type = db_type.lstrip("var")
|
||||
# They like to call 'bool's 'tinyint(1)' and introspection makes that a integer
|
||||
# just convert it back to it's proper type, a bool is a bool and nothing else.
|
||||
if db_type == 'integer' and description[1] == FIELD_TYPE.TINY and description[4] == 1:
|
||||
db_type = 'bool'
|
||||
if db_type == 'integer' and description[1] == FIELD_TYPE.SHORT:
|
||||
db_type = 'smallint UNSIGNED' # FIXME: what about if it's not UNSIGNED ?
|
||||
return db_type
|
||||
|
||||
|
||||
class SqliteSQLDiff(SQLDiff):
|
||||
# Unique does not seem to be implied on Sqlite for Primary_key's
|
||||
# if this is more generic among databases this might be usefull
|
||||
# to add to the superclass's find_unique_missing_in_db method
|
||||
def find_unique_missing_in_db(self, meta, table_indexes, table_name):
|
||||
for field in all_local_fields(meta):
|
||||
if field.unique:
|
||||
attname = field.db_column or field.attname
|
||||
if attname in table_indexes and table_indexes[attname]['unique']:
|
||||
continue
|
||||
if attname in table_indexes and table_indexes[attname]['primary_key']:
|
||||
continue
|
||||
self.add_difference('unique-missing-in-db', table_name, attname)
|
||||
|
||||
# Finding Indexes by using the get_indexes dictionary doesn't seem to work
|
||||
# for sqlite.
|
||||
def find_index_missing_in_db(self, meta, table_indexes, table_name):
|
||||
pass
|
||||
|
||||
def find_index_missing_in_model(self, meta, table_indexes, table_name):
|
||||
pass
|
||||
|
||||
def get_field_db_type(self, description, field=None, table_name=None):
|
||||
db_type = super(SqliteSQLDiff, self).get_field_db_type(description)
|
||||
if not db_type:
|
||||
return
|
||||
if field:
|
||||
field_type = self.get_field_model_type(field)
|
||||
# Fix char/varchar inconsistencies
|
||||
if self.strip_parameters(field_type) == 'char' and self.strip_parameters(db_type) == 'varchar':
|
||||
db_type = db_type.lstrip("var")
|
||||
return db_type
|
||||
|
||||
|
||||
class PostgresqlSQLDiff(SQLDiff):
|
||||
DATA_TYPES_REVERSE_OVERRIDE = {
|
||||
1042: 'CharField',
|
||||
# postgis types (TODO: support is very incomplete)
|
||||
17506: 'django.contrib.gis.db.models.fields.PointField',
|
||||
55902: 'django.contrib.gis.db.models.fields.MultiPolygonField',
|
||||
}
|
||||
|
||||
# Hopefully in the future we can add constraint checking and other more
|
||||
# advanced checks based on this database.
|
||||
SQL_LOAD_CONSTRAINTS = """
|
||||
SELECT nspname, relname, conname, attname, pg_get_constraintdef(pg_constraint.oid)
|
||||
FROM pg_constraint
|
||||
INNER JOIN pg_attribute ON pg_constraint.conrelid = pg_attribute.attrelid AND pg_attribute.attnum = any(pg_constraint.conkey)
|
||||
INNER JOIN pg_class ON conrelid=pg_class.oid
|
||||
INNER JOIN pg_namespace ON pg_namespace.oid=pg_class.relnamespace
|
||||
ORDER BY CASE WHEN contype='f' THEN 0 ELSE 1 END,contype,nspname,relname,conname;
|
||||
"""
|
||||
|
||||
SQL_FIELD_TYPE_DIFFER = lambda self, style, qn, args: "%s %s\n\t%s %s %s %s;" % (style.SQL_KEYWORD('ALTER TABLE'), style.SQL_TABLE(qn(args[0])), style.SQL_KEYWORD('ALTER'), style.SQL_FIELD(qn(args[1])), style.SQL_KEYWORD("TYPE"), style.SQL_COLTYPE(args[2]))
|
||||
SQL_FIELD_PARAMETER_DIFFER = lambda self, style, qn, args: "%s %s\n\t%s %s %s %s;" % (style.SQL_KEYWORD('ALTER TABLE'), style.SQL_TABLE(qn(args[0])), style.SQL_KEYWORD('ALTER'), style.SQL_FIELD(qn(args[1])), style.SQL_KEYWORD("TYPE"), style.SQL_COLTYPE(args[2]))
|
||||
|
||||
def __init__(self, app_models, options):
|
||||
SQLDiff.__init__(self, app_models, options)
|
||||
self.check_constraints = {}
|
||||
self.load_constraints()
|
||||
|
||||
def load_constraints(self):
|
||||
for dct in self.sql_to_dict(self.SQL_LOAD_CONSTRAINTS, []):
|
||||
key = (dct['nspname'], dct['relname'], dct['attname'])
|
||||
if 'CHECK' in dct['pg_get_constraintdef']:
|
||||
self.check_constraints[key] = dct
|
||||
|
||||
def get_field_db_type(self, description, field=None, table_name=None):
|
||||
db_type = super(PostgresqlSQLDiff, self).get_field_db_type(description)
|
||||
if not db_type:
|
||||
return
|
||||
if field:
|
||||
if field.primary_key and db_type == 'integer':
|
||||
db_type = 'serial'
|
||||
if table_name:
|
||||
tablespace = field.db_tablespace
|
||||
if tablespace == "":
|
||||
tablespace = "public"
|
||||
check_constraint = self.check_constraints.get((tablespace, table_name, field.attname), {}).get('pg_get_constraintdef', None)
|
||||
if check_constraint:
|
||||
check_constraint = check_constraint.replace("((", "(")
|
||||
check_constraint = check_constraint.replace("))", ")")
|
||||
check_constraint = '("'.join([')' in e and '" '.join(e.split(" ", 1)) or e for e in check_constraint.split("(")])
|
||||
# TODO: might be more then one constraint in definition ?
|
||||
db_type += ' ' + check_constraint
|
||||
return db_type
|
||||
|
||||
"""
|
||||
def find_field_type_differ(self, meta, table_description, table_name):
|
||||
def callback(field, description, model_type, db_type):
|
||||
if field.primary_key and db_type=='integer':
|
||||
db_type = 'serial'
|
||||
return model_type, db_type
|
||||
super(PostgresqlSQLDiff, self).find_field_type_differs(meta, table_description, table_name, callback)
|
||||
"""
|
||||
|
||||
DATABASE_SQLDIFF_CLASSES = {
|
||||
'postgresql_psycopg2' : PostgresqlSQLDiff,
|
||||
'postgresql': PostgresqlSQLDiff,
|
||||
'mysql': MySQLDiff,
|
||||
'sqlite3': SqliteSQLDiff,
|
||||
'oracle': GenericSQLDiff
|
||||
}
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
option_list = BaseCommand.option_list + (
|
||||
make_option('--all-applications', '-a', action='store_true', dest='all_applications',
|
||||
help="Automaticly include all application from INSTALLED_APPS."),
|
||||
make_option('--not-only-existing', '-e', action='store_false', dest='only_existing',
|
||||
help="Check all tables that exist in the database, not only tables that should exist based on models."),
|
||||
make_option('--dense-output', '-d', action='store_true', dest='dense_output',
|
||||
help="Shows the output in dense format, normally output is spreaded over multiple lines."),
|
||||
make_option('--output_text', '-t', action='store_false', dest='sql', default=True,
|
||||
help="Outputs the differences as descriptive text instead of SQL"),
|
||||
)
|
||||
|
||||
help = """Prints the (approximated) difference between models and fields in the database for the given app name(s).
|
||||
|
||||
It indicates how columns in the database are different from the sql that would
|
||||
be generated by Django. This command is not a database migration tool. (Though
|
||||
it can certainly help) It's purpose is to show the current differences as a way
|
||||
to check/debug ur models compared to the real database tables and columns."""
|
||||
|
||||
output_transaction = False
|
||||
args = '<appname appname ...>'
|
||||
|
||||
def handle(self, *app_labels, **options):
|
||||
from django import VERSION
|
||||
if VERSION[:2] < (1, 0):
|
||||
raise CommandError("SQLDiff only support Django 1.0 or higher!")
|
||||
|
||||
from django.db import models
|
||||
from django.conf import settings
|
||||
|
||||
if settings.DATABASE_ENGINE == 'dummy':
|
||||
# This must be the "dummy" database backend, which means the user
|
||||
# hasn't set DATABASE_ENGINE.
|
||||
raise CommandError("Django doesn't know which syntax to use for your SQL statements,\n" +
|
||||
"because you haven't specified the DATABASE_ENGINE setting.\n" +
|
||||
"Edit your settings file and change DATABASE_ENGINE to something like 'postgresql' or 'mysql'.")
|
||||
|
||||
if options.get('all_applications', False):
|
||||
app_models = models.get_models()
|
||||
else:
|
||||
if not app_labels:
|
||||
raise CommandError('Enter at least one appname.')
|
||||
try:
|
||||
app_list = [models.get_app(app_label) for app_label in app_labels]
|
||||
except (models.ImproperlyConfigured, ImportError), e:
|
||||
raise CommandError("%s. Are you sure your INSTALLED_APPS setting is correct?" % e)
|
||||
|
||||
app_models = []
|
||||
for app in app_list:
|
||||
app_models.extend(models.get_models(app))
|
||||
|
||||
## remove all models that are not managed by Django
|
||||
#app_models = [model for model in app_models if getattr(model._meta, 'managed', True)]
|
||||
|
||||
if not app_models:
|
||||
raise CommandError('Unable to execute sqldiff no models founds.')
|
||||
|
||||
engine = settings.DATABASE_ENGINE
|
||||
if not engine:
|
||||
engine = connection.__module__.split('.')[-2]
|
||||
cls = DATABASE_SQLDIFF_CLASSES.get(engine, GenericSQLDiff)
|
||||
sqldiff_instance = cls(app_models, options)
|
||||
sqldiff_instance.find_differences()
|
||||
sqldiff_instance.print_diff(self.style)
|
||||
return
|
|
@ -0,0 +1,271 @@
|
|||
"""
|
||||
Sync Media to S3
|
||||
================
|
||||
|
||||
Django command that scans all files in your settings.MEDIA_ROOT folder and
|
||||
uploads them to S3 with the same directory structure.
|
||||
|
||||
This command can optionally do the following but it is off by default:
|
||||
* gzip compress any CSS and Javascript files it finds and adds the appropriate
|
||||
'Content-Encoding' header.
|
||||
* set a far future 'Expires' header for optimal caching.
|
||||
|
||||
Note: This script requires the Python boto library and valid Amazon Web
|
||||
Services API keys.
|
||||
|
||||
Required settings.py variables:
|
||||
AWS_ACCESS_KEY_ID = ''
|
||||
AWS_SECRET_ACCESS_KEY = ''
|
||||
AWS_BUCKET_NAME = ''
|
||||
|
||||
Command options are:
|
||||
-p PREFIX, --prefix=PREFIX
|
||||
The prefix to prepend to the path on S3.
|
||||
--gzip Enables gzipping CSS and Javascript files.
|
||||
--expires Enables setting a far future expires header.
|
||||
--force Skip the file mtime check to force upload of all
|
||||
files.
|
||||
--filter-list Override default directory and file exclusion
|
||||
filters. (enter as comma seperated line)
|
||||
|
||||
TODO:
|
||||
* Use fnmatch (or regex) to allow more complex FILTER_LIST rules.
|
||||
|
||||
"""
|
||||
import datetime
|
||||
import email
|
||||
import mimetypes
|
||||
import optparse
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
# Make sure boto is available
|
||||
try:
|
||||
import boto
|
||||
import boto.exception
|
||||
except ImportError:
|
||||
raise ImportError, "The boto Python library is not installed."
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
# Extra variables to avoid passing these around
|
||||
AWS_ACCESS_KEY_ID = ''
|
||||
AWS_SECRET_ACCESS_KEY = ''
|
||||
AWS_BUCKET_NAME = ''
|
||||
DIRECTORY = ''
|
||||
FILTER_LIST = ['.DS_Store', '.svn', '.hg', '.git', 'Thumbs.db']
|
||||
GZIP_CONTENT_TYPES = (
|
||||
'text/css',
|
||||
'application/javascript',
|
||||
'application/x-javascript',
|
||||
'text/javascript'
|
||||
)
|
||||
|
||||
upload_count = 0
|
||||
skip_count = 0
|
||||
|
||||
option_list = BaseCommand.option_list + (
|
||||
optparse.make_option('-p', '--prefix',
|
||||
dest='prefix',
|
||||
default=getattr(settings, 'SYNC_MEDIA_S3_PREFIX', ''),
|
||||
help="The prefix to prepend to the path on S3."),
|
||||
optparse.make_option('-d', '--dir',
|
||||
dest='dir', default=settings.MEDIA_ROOT,
|
||||
help="The root directory to use instead of your MEDIA_ROOT"),
|
||||
optparse.make_option('--gzip',
|
||||
action='store_true', dest='gzip', default=False,
|
||||
help="Enables gzipping CSS and Javascript files."),
|
||||
optparse.make_option('--expires',
|
||||
action='store_true', dest='expires', default=False,
|
||||
help="Enables setting a far future expires header."),
|
||||
optparse.make_option('--force',
|
||||
action='store_true', dest='force', default=False,
|
||||
help="Skip the file mtime check to force upload of all files."),
|
||||
optparse.make_option('--filter-list', dest='filter_list',
|
||||
action='store', default='',
|
||||
help="Override default directory and file exclusion filters. (enter as comma seperated line)"),
|
||||
)
|
||||
|
||||
help = 'Syncs the complete MEDIA_ROOT structure and files to S3 into the given bucket name.'
|
||||
args = 'bucket_name'
|
||||
|
||||
can_import_settings = True
|
||||
|
||||
def handle(self, *args, **options):
|
||||
|
||||
# Check for AWS keys in settings
|
||||
if not hasattr(settings, 'AWS_ACCESS_KEY_ID') or \
|
||||
not hasattr(settings, 'AWS_SECRET_ACCESS_KEY'):
|
||||
raise CommandError('Missing AWS keys from settings file. Please' +
|
||||
'supply both AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY.')
|
||||
else:
|
||||
self.AWS_ACCESS_KEY_ID = settings.AWS_ACCESS_KEY_ID
|
||||
self.AWS_SECRET_ACCESS_KEY = settings.AWS_SECRET_ACCESS_KEY
|
||||
|
||||
if not hasattr(settings, 'AWS_BUCKET_NAME'):
|
||||
raise CommandError('Missing bucket name from settings file. Please' +
|
||||
' add the AWS_BUCKET_NAME to your settings file.')
|
||||
else:
|
||||
if not settings.AWS_BUCKET_NAME:
|
||||
raise CommandError('AWS_BUCKET_NAME cannot be empty.')
|
||||
self.AWS_BUCKET_NAME = settings.AWS_BUCKET_NAME
|
||||
|
||||
if not hasattr(settings, 'MEDIA_ROOT'):
|
||||
raise CommandError('MEDIA_ROOT must be set in your settings.')
|
||||
else:
|
||||
if not settings.MEDIA_ROOT:
|
||||
raise CommandError('MEDIA_ROOT must be set in your settings.')
|
||||
|
||||
self.verbosity = int(options.get('verbosity'))
|
||||
self.prefix = options.get('prefix')
|
||||
self.do_gzip = options.get('gzip')
|
||||
self.do_expires = options.get('expires')
|
||||
self.do_force = options.get('force')
|
||||
self.DIRECTORY = options.get('dir')
|
||||
self.FILTER_LIST = getattr(settings, 'FILTER_LIST', self.FILTER_LIST)
|
||||
filter_list = options.get('filter_list')
|
||||
if filter_list:
|
||||
# command line option overrides default filter_list and
|
||||
# settings.filter_list
|
||||
self.FILTER_LIST = filter_list.split(',')
|
||||
|
||||
# Now call the syncing method to walk the MEDIA_ROOT directory and
|
||||
# upload all files found.
|
||||
self.sync_s3()
|
||||
|
||||
print
|
||||
print "%d files uploaded." % (self.upload_count)
|
||||
print "%d files skipped." % (self.skip_count)
|
||||
|
||||
def sync_s3(self):
|
||||
"""
|
||||
Walks the media directory and syncs files to S3
|
||||
"""
|
||||
bucket, key = self.open_s3()
|
||||
os.path.walk(self.DIRECTORY, self.upload_s3,
|
||||
(bucket, key, self.AWS_BUCKET_NAME, self.DIRECTORY))
|
||||
|
||||
def compress_string(self, s):
|
||||
"""Gzip a given string."""
|
||||
import gzip
|
||||
try:
|
||||
from cStringIO import StringIO
|
||||
except ImportError:
|
||||
from StringIO import StringIO
|
||||
zbuf = StringIO()
|
||||
zfile = gzip.GzipFile(mode='wb', compresslevel=6, fileobj=zbuf)
|
||||
zfile.write(s)
|
||||
zfile.close()
|
||||
return zbuf.getvalue()
|
||||
|
||||
def open_s3(self):
|
||||
"""
|
||||
Opens connection to S3 returning bucket and key
|
||||
"""
|
||||
conn = boto.connect_s3(self.AWS_ACCESS_KEY_ID, self.AWS_SECRET_ACCESS_KEY)
|
||||
try:
|
||||
bucket = conn.get_bucket(self.AWS_BUCKET_NAME)
|
||||
except boto.exception.S3ResponseError:
|
||||
bucket = conn.create_bucket(self.AWS_BUCKET_NAME)
|
||||
return bucket, boto.s3.key.Key(bucket)
|
||||
|
||||
def upload_s3(self, arg, dirname, names):
|
||||
"""
|
||||
This is the callback to os.path.walk and where much of the work happens
|
||||
"""
|
||||
bucket, key, bucket_name, root_dir = arg
|
||||
|
||||
# Skip directories we don't want to sync
|
||||
if os.path.basename(dirname) in self.FILTER_LIST:
|
||||
# prevent walk from processing subfiles/subdirs below the ignored one
|
||||
del names[:]
|
||||
return
|
||||
|
||||
# Later we assume the MEDIA_ROOT ends with a trailing slash
|
||||
if not root_dir.endswith(os.path.sep):
|
||||
root_dir = root_dir + os.path.sep
|
||||
|
||||
for file in names:
|
||||
headers = {}
|
||||
|
||||
if file in self.FILTER_LIST:
|
||||
continue # Skip files we don't want to sync
|
||||
|
||||
filename = os.path.join(dirname, file)
|
||||
if os.path.isdir(filename):
|
||||
continue # Don't try to upload directories
|
||||
|
||||
file_key = filename[len(root_dir):]
|
||||
if self.prefix:
|
||||
file_key = '%s/%s' % (self.prefix, file_key)
|
||||
|
||||
# Check if file on S3 is older than local file, if so, upload
|
||||
if not self.do_force:
|
||||
s3_key = bucket.get_key(file_key)
|
||||
if s3_key:
|
||||
s3_datetime = datetime.datetime(*time.strptime(
|
||||
s3_key.last_modified, '%a, %d %b %Y %H:%M:%S %Z')[0:6])
|
||||
local_datetime = datetime.datetime.utcfromtimestamp(
|
||||
os.stat(filename).st_mtime)
|
||||
if local_datetime < s3_datetime:
|
||||
self.skip_count += 1
|
||||
if self.verbosity > 1:
|
||||
print "File %s hasn't been modified since last " \
|
||||
"being uploaded" % (file_key)
|
||||
continue
|
||||
|
||||
# File is newer, let's process and upload
|
||||
if self.verbosity > 0:
|
||||
print "Uploading %s..." % (file_key)
|
||||
|
||||
content_type = mimetypes.guess_type(filename)[0]
|
||||
if content_type:
|
||||
headers['Content-Type'] = content_type
|
||||
file_obj = open(filename, 'rb')
|
||||
file_size = os.fstat(file_obj.fileno()).st_size
|
||||
filedata = file_obj.read()
|
||||
if self.do_gzip:
|
||||
# Gzipping only if file is large enough (>1K is recommended)
|
||||
# and only if file is a common text type (not a binary file)
|
||||
if file_size > 1024 and content_type in self.GZIP_CONTENT_TYPES:
|
||||
filedata = self.compress_string(filedata)
|
||||
headers['Content-Encoding'] = 'gzip'
|
||||
if self.verbosity > 1:
|
||||
print "\tgzipped: %dk to %dk" % \
|
||||
(file_size / 1024, len(filedata) / 1024)
|
||||
if self.do_expires:
|
||||
# HTTP/1.0
|
||||
headers['Expires'] = '%s GMT' % (email.Utils.formatdate(
|
||||
time.mktime((datetime.datetime.now() +
|
||||
datetime.timedelta(days=365 * 2)).timetuple())))
|
||||
# HTTP/1.1
|
||||
headers['Cache-Control'] = 'max-age %d' % (3600 * 24 * 365 * 2)
|
||||
if self.verbosity > 1:
|
||||
print "\texpires: %s" % (headers['Expires'])
|
||||
print "\tcache-control: %s" % (headers['Cache-Control'])
|
||||
|
||||
try:
|
||||
key.name = file_key
|
||||
key.set_contents_from_string(filedata, headers, replace=True)
|
||||
key.set_acl('public-read')
|
||||
except boto.exception.S3CreateError, e:
|
||||
print "Failed: %s" % e
|
||||
except Exception, e:
|
||||
print e
|
||||
raise
|
||||
else:
|
||||
self.upload_count += 1
|
||||
|
||||
file_obj.close()
|
||||
|
||||
# Backwards compatibility for Django r9110
|
||||
if not [opt for opt in Command.option_list if opt.dest == 'verbosity']:
|
||||
Command.option_list += (
|
||||
optparse.make_option('-v', '--verbosity',
|
||||
dest='verbosity', default=1, action='count',
|
||||
help="Verbose mode. Multiple -v options increase the verbosity."),
|
||||
)
|
|
@ -0,0 +1,220 @@
|
|||
"""
|
||||
SyncData
|
||||
========
|
||||
|
||||
Django command similar to 'loaddata' but also deletes.
|
||||
After 'syncdata' has run, the database will have the same data as the fixture - anything
|
||||
missing will of been added, anything different will of been updated,
|
||||
and anything extra will of been deleted.
|
||||
"""
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.core.management.color import no_style
|
||||
from optparse import make_option
|
||||
import sys
|
||||
import os
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
""" syncdata command """
|
||||
|
||||
help = 'Makes the current database have the same data as the fixture(s), no more, no less.'
|
||||
args = "fixture [fixture ...]"
|
||||
|
||||
def remove_objects_not_in(self, objects_to_keep, verbosity):
|
||||
"""
|
||||
Deletes all the objects in the database that are not in objects_to_keep.
|
||||
- objects_to_keep: A map where the keys are classes, and the values are a
|
||||
set of the objects of that class we should keep.
|
||||
"""
|
||||
for class_ in objects_to_keep.keys():
|
||||
current = class_.objects.all()
|
||||
current_ids = set([x.id for x in current])
|
||||
keep_ids = set([x.id for x in objects_to_keep[class_]])
|
||||
|
||||
remove_these_ones = current_ids.difference(keep_ids)
|
||||
if remove_these_ones:
|
||||
for obj in current:
|
||||
if obj.id in remove_these_ones:
|
||||
obj.delete()
|
||||
if verbosity >= 2:
|
||||
print "Deleted object: %s" % unicode(obj)
|
||||
|
||||
if verbosity > 0 and remove_these_ones:
|
||||
num_deleted = len(remove_these_ones)
|
||||
if num_deleted > 1:
|
||||
type_deleted = unicode(class_._meta.verbose_name_plural)
|
||||
else:
|
||||
type_deleted = unicode(class_._meta.verbose_name)
|
||||
|
||||
print "Deleted %s %s" % (str(num_deleted), type_deleted)
|
||||
|
||||
def handle(self, *fixture_labels, **options):
|
||||
""" Main method of a Django command """
|
||||
from django.db.models import get_apps
|
||||
from django.core import serializers
|
||||
from django.db import connection, transaction
|
||||
from django.conf import settings
|
||||
|
||||
self.style = no_style()
|
||||
|
||||
verbosity = int(options.get('verbosity', 1))
|
||||
show_traceback = options.get('traceback', False)
|
||||
|
||||
# Keep a count of the installed objects and fixtures
|
||||
fixture_count = 0
|
||||
object_count = 0
|
||||
objects_per_fixture = []
|
||||
models = set()
|
||||
|
||||
humanize = lambda dirname: dirname and "'%s'" % dirname or 'absolute path'
|
||||
|
||||
# Get a cursor (even though we don't need one yet). This has
|
||||
# the side effect of initializing the test database (if
|
||||
# it isn't already initialized).
|
||||
cursor = connection.cursor()
|
||||
|
||||
# Start transaction management. All fixtures are installed in a
|
||||
# single transaction to ensure that all references are resolved.
|
||||
transaction.commit_unless_managed()
|
||||
transaction.enter_transaction_management()
|
||||
transaction.managed(True)
|
||||
|
||||
app_fixtures = [os.path.join(os.path.dirname(app.__file__), 'fixtures') \
|
||||
for app in get_apps()]
|
||||
for fixture_label in fixture_labels:
|
||||
parts = fixture_label.split('.')
|
||||
if len(parts) == 1:
|
||||
fixture_name = fixture_label
|
||||
formats = serializers.get_public_serializer_formats()
|
||||
else:
|
||||
fixture_name, format = '.'.join(parts[:-1]), parts[-1]
|
||||
if format in serializers.get_public_serializer_formats():
|
||||
formats = [format]
|
||||
else:
|
||||
formats = []
|
||||
|
||||
if formats:
|
||||
if verbosity > 1:
|
||||
print "Loading '%s' fixtures..." % fixture_name
|
||||
else:
|
||||
sys.stderr.write(
|
||||
self.style.ERROR("Problem installing fixture '%s': %s is not a known " + \
|
||||
"serialization format." % (fixture_name, format))
|
||||
)
|
||||
transaction.rollback()
|
||||
transaction.leave_transaction_management()
|
||||
return
|
||||
|
||||
if os.path.isabs(fixture_name):
|
||||
fixture_dirs = [fixture_name]
|
||||
else:
|
||||
fixture_dirs = app_fixtures + list(settings.FIXTURE_DIRS) + ['']
|
||||
|
||||
for fixture_dir in fixture_dirs:
|
||||
if verbosity > 1:
|
||||
print "Checking %s for fixtures..." % humanize(fixture_dir)
|
||||
|
||||
label_found = False
|
||||
for format in formats:
|
||||
serializer = serializers.get_serializer(format)
|
||||
if verbosity > 1:
|
||||
print "Trying %s for %s fixture '%s'..." % \
|
||||
(humanize(fixture_dir), format, fixture_name)
|
||||
try:
|
||||
full_path = os.path.join(fixture_dir, '.'.join([fixture_name, format]))
|
||||
fixture = open(full_path, 'r')
|
||||
if label_found:
|
||||
fixture.close()
|
||||
print self.style.ERROR("Multiple fixtures named '%s' in %s. Aborting." %
|
||||
(fixture_name, humanize(fixture_dir)))
|
||||
transaction.rollback()
|
||||
transaction.leave_transaction_management()
|
||||
return
|
||||
else:
|
||||
fixture_count += 1
|
||||
objects_per_fixture.append(0)
|
||||
if verbosity > 0:
|
||||
print "Installing %s fixture '%s' from %s." % \
|
||||
(format, fixture_name, humanize(fixture_dir))
|
||||
try:
|
||||
objects_to_keep = {}
|
||||
objects = serializers.deserialize(format, fixture)
|
||||
for obj in objects:
|
||||
object_count += 1
|
||||
objects_per_fixture[-1] += 1
|
||||
|
||||
class_ = obj.object.__class__
|
||||
if not class_ in objects_to_keep:
|
||||
objects_to_keep[class_] = set()
|
||||
objects_to_keep[class_].add(obj.object)
|
||||
|
||||
models.add(class_)
|
||||
obj.save()
|
||||
|
||||
self.remove_objects_not_in(objects_to_keep, verbosity)
|
||||
|
||||
label_found = True
|
||||
except (SystemExit, KeyboardInterrupt):
|
||||
raise
|
||||
except Exception:
|
||||
import traceback
|
||||
fixture.close()
|
||||
transaction.rollback()
|
||||
transaction.leave_transaction_management()
|
||||
if show_traceback:
|
||||
traceback.print_exc()
|
||||
else:
|
||||
sys.stderr.write(
|
||||
self.style.ERROR("Problem installing fixture '%s': %s\n" %
|
||||
(full_path, traceback.format_exc())))
|
||||
return
|
||||
fixture.close()
|
||||
except:
|
||||
if verbosity > 1:
|
||||
print "No %s fixture '%s' in %s." % \
|
||||
(format, fixture_name, humanize(fixture_dir))
|
||||
|
||||
# If any of the fixtures we loaded contain 0 objects, assume that an
|
||||
# error was encountered during fixture loading.
|
||||
if 0 in objects_per_fixture:
|
||||
sys.stderr.write(
|
||||
self.style.ERROR("No fixture data found for '%s'. (File format may be invalid.)" %
|
||||
(fixture_name)))
|
||||
transaction.rollback()
|
||||
transaction.leave_transaction_management()
|
||||
return
|
||||
|
||||
# If we found even one object in a fixture, we need to reset the
|
||||
# database sequences.
|
||||
if object_count > 0:
|
||||
sequence_sql = connection.ops.sequence_reset_sql(self.style, models)
|
||||
if sequence_sql:
|
||||
if verbosity > 1:
|
||||
print "Resetting sequences"
|
||||
for line in sequence_sql:
|
||||
cursor.execute(line)
|
||||
|
||||
transaction.commit()
|
||||
transaction.leave_transaction_management()
|
||||
|
||||
if object_count == 0:
|
||||
if verbosity > 1:
|
||||
print "No fixtures found."
|
||||
else:
|
||||
if verbosity > 0:
|
||||
print "Installed %d object(s) from %d fixture(s)" % (object_count, fixture_count)
|
||||
|
||||
# Close the DB connection. This is required as a workaround for an
|
||||
# edge case in MySQL: if the same connection is used to
|
||||
# create tables, load data, and query, the query can return
|
||||
# incorrect results. See Django #7572, MySQL #37735.
|
||||
connection.close()
|
||||
|
||||
# Backwards compatibility for Django r9110
|
||||
if not [opt for opt in Command.option_list if opt.dest == 'verbosity']:
|
||||
Command.option_list += (
|
||||
make_option('--verbosity', '-v', action="store", dest="verbosity",
|
||||
default='1', type='choice', choices=['0', '1', '2'],
|
||||
help="Verbosity level; 0=minimal output, 1=normal output, 2=all output"),
|
||||
)
|
|
@ -0,0 +1,45 @@
|
|||
from collections import defaultdict
|
||||
import os
|
||||
from django.conf import settings
|
||||
from django.core.management.base import NoArgsCommand
|
||||
from django.db import models
|
||||
from django.db.models.loading import cache
|
||||
|
||||
|
||||
class Command(NoArgsCommand):
|
||||
help = "Prints a list of all files in MEDIA_ROOT that are not referenced in the database."
|
||||
|
||||
def handle_noargs(self, **options):
|
||||
|
||||
if settings.MEDIA_ROOT == '':
|
||||
print "MEDIA_ROOT is not set, nothing to do"
|
||||
return
|
||||
|
||||
# Get a list of all files under MEDIA_ROOT
|
||||
media = []
|
||||
for root, dirs, files in os.walk(settings.MEDIA_ROOT):
|
||||
for f in files:
|
||||
media.append(os.path.abspath(os.path.join(root, f)))
|
||||
|
||||
# Get list of all fields (value) for each model (key)
|
||||
# that is a FileField or subclass of a FileField
|
||||
model_dict = defaultdict(list)
|
||||
for app in cache.get_apps():
|
||||
model_list = cache.get_models(app)
|
||||
for model in model_list:
|
||||
for field in model._meta.fields:
|
||||
if issubclass(field.__class__, models.FileField):
|
||||
model_dict[model].append(field)
|
||||
|
||||
# Get a list of all files referenced in the database
|
||||
referenced = []
|
||||
for model in model_dict.iterkeys():
|
||||
all = model.objects.all().iterator()
|
||||
for object in all:
|
||||
for field in model_dict[model]:
|
||||
referenced.append(os.path.abspath(getattr(object, field.name).path))
|
||||
|
||||
# Print each file in MEDIA_ROOT that is not referenced in the database
|
||||
for m in media:
|
||||
if m not in referenced:
|
||||
print m
|
|
@ -0,0 +1,21 @@
|
|||
from django.core.management.base import BaseCommand
|
||||
from django.db.models import get_models, get_app
|
||||
from django.contrib.auth.management import create_permissions
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
args = '<app app ...>'
|
||||
help = 'reloads permissions for specified apps, or all apps if no args are specified'
|
||||
|
||||
def handle(self, *args, **options):
|
||||
if not args:
|
||||
apps = []
|
||||
for model in get_models():
|
||||
apps.append(get_app(model._meta.app_label))
|
||||
else:
|
||||
apps = []
|
||||
for arg in args:
|
||||
apps.append(get_app(arg))
|
||||
for app in apps:
|
||||
create_permissions(app, get_models(), options.get('verbosity', 0))
|
||||
|
|
@ -0,0 +1,175 @@
|
|||
"""
|
||||
django_extensions.management.jobs
|
||||
"""
|
||||
|
||||
import os
|
||||
from imp import find_module
|
||||
|
||||
_jobs = None
|
||||
|
||||
|
||||
def noneimplementation(meth):
|
||||
return None
|
||||
|
||||
|
||||
class JobError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class BaseJob(object):
|
||||
help = "undefined job description."
|
||||
when = None
|
||||
|
||||
def execute(self):
|
||||
raise NotImplementedError("Job needs to implement the execute method")
|
||||
|
||||
|
||||
class MinutelyJob(BaseJob):
|
||||
when = "minutely"
|
||||
|
||||
|
||||
class HourlyJob(BaseJob):
|
||||
when = "hourly"
|
||||
|
||||
|
||||
class DailyJob(BaseJob):
|
||||
when = "daily"
|
||||
|
||||
|
||||
class WeeklyJob(BaseJob):
|
||||
when = "weekly"
|
||||
|
||||
|
||||
class MonthlyJob(BaseJob):
|
||||
when = "monthly"
|
||||
|
||||
|
||||
class YearlyJob(BaseJob):
|
||||
when = "yearly"
|
||||
|
||||
|
||||
def my_import(name):
|
||||
imp = __import__(name)
|
||||
mods = name.split('.')
|
||||
if len(mods) > 1:
|
||||
for mod in mods[1:]:
|
||||
imp = getattr(imp, mod)
|
||||
return imp
|
||||
|
||||
|
||||
def find_jobs(jobs_dir):
|
||||
try:
|
||||
return [f[:-3] for f in os.listdir(jobs_dir) \
|
||||
if not f.startswith('_') and f.endswith(".py")]
|
||||
except OSError:
|
||||
return []
|
||||
|
||||
|
||||
def find_job_module(app_name, when=None):
|
||||
parts = app_name.split('.')
|
||||
parts.append('jobs')
|
||||
if when:
|
||||
parts.append(when)
|
||||
parts.reverse()
|
||||
path = None
|
||||
while parts:
|
||||
part = parts.pop()
|
||||
f, path, descr = find_module(part, path and [path] or None)
|
||||
return path
|
||||
|
||||
|
||||
def import_job(app_name, name, when=None):
|
||||
jobmodule = "%s.jobs.%s%s" % (app_name, when and "%s." % when or "", name)
|
||||
job_mod = my_import(jobmodule)
|
||||
# todo: more friendly message for AttributeError if job_mod does not exist
|
||||
try:
|
||||
job = job_mod.Job
|
||||
except:
|
||||
raise JobError("Job module %s does not contain class instance named 'Job'" % jobmodule)
|
||||
if when and not (job.when == when or job.when == None):
|
||||
raise JobError("Job %s is not a %s job." % (jobmodule, when))
|
||||
return job
|
||||
|
||||
|
||||
def get_jobs(when=None, only_scheduled=False):
|
||||
"""
|
||||
Returns a dictionary mapping of job names together with their respective
|
||||
application class.
|
||||
"""
|
||||
# FIXME: HACK: make sure the project dir is on the path when executed as ./manage.py
|
||||
import sys
|
||||
try:
|
||||
cpath = os.path.dirname(os.path.realpath(sys.argv[0]))
|
||||
ppath = os.path.dirname(cpath)
|
||||
if ppath not in sys.path:
|
||||
sys.path.append(ppath)
|
||||
except:
|
||||
pass
|
||||
_jobs = {}
|
||||
if True:
|
||||
from django.conf import settings
|
||||
for app_name in settings.INSTALLED_APPS:
|
||||
scandirs = (None, 'minutely', 'hourly', 'daily', 'weekly', 'monthly', 'yearly')
|
||||
if when:
|
||||
scandirs = None, when
|
||||
for subdir in scandirs:
|
||||
try:
|
||||
path = find_job_module(app_name, subdir)
|
||||
for name in find_jobs(path):
|
||||
if (app_name, name) in _jobs:
|
||||
raise JobError("Duplicate job %s" % name)
|
||||
job = import_job(app_name, name, subdir)
|
||||
if only_scheduled and job.when == None:
|
||||
# only include jobs which are scheduled
|
||||
continue
|
||||
if when and job.when != when:
|
||||
# generic job not in same schedule
|
||||
continue
|
||||
_jobs[(app_name, name)] = job
|
||||
except ImportError:
|
||||
# No job module -- continue scanning
|
||||
pass
|
||||
return _jobs
|
||||
|
||||
|
||||
def get_job(app_name, job_name):
|
||||
jobs = get_jobs()
|
||||
if app_name:
|
||||
return jobs[(app_name, job_name)]
|
||||
else:
|
||||
for a, j in jobs.keys():
|
||||
if j == job_name:
|
||||
return jobs[(a, j)]
|
||||
raise KeyError("Job not found: %s" % job_name)
|
||||
|
||||
|
||||
def print_jobs(when=None, only_scheduled=False, show_when=True, \
|
||||
show_appname=False, show_header=True):
|
||||
jobmap = get_jobs(when, only_scheduled=only_scheduled)
|
||||
print "Job List: %i jobs" % len(jobmap)
|
||||
jlist = jobmap.keys()
|
||||
jlist.sort()
|
||||
appname_spacer = "%%-%is" % max(len(e[0]) for e in jlist)
|
||||
name_spacer = "%%-%is" % max(len(e[1]) for e in jlist)
|
||||
when_spacer = "%%-%is" % max(len(e.when) for e in jobmap.values() if e.when)
|
||||
if show_header:
|
||||
line = " "
|
||||
if show_appname:
|
||||
line += appname_spacer % "appname" + " - "
|
||||
line += name_spacer % "jobname"
|
||||
if show_when:
|
||||
line += " - " + when_spacer % "when"
|
||||
line += " - help"
|
||||
print line
|
||||
print "-" * 80
|
||||
|
||||
for app_name, job_name in jlist:
|
||||
job = jobmap[(app_name, job_name)]
|
||||
line = " "
|
||||
if show_appname:
|
||||
line += appname_spacer % app_name + " - "
|
||||
line += name_spacer % job_name
|
||||
if show_when:
|
||||
line += " - " + when_spacer % (job.when and job.when or "")
|
||||
line += " - " + job.help
|
||||
print line
|
|
@ -0,0 +1,305 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""Django model to DOT (Graphviz) converter
|
||||
by Antonio Cavedoni <antonio@cavedoni.org>
|
||||
|
||||
Make sure your DJANGO_SETTINGS_MODULE is set to your project or
|
||||
place this script in the same directory of the project and call
|
||||
the script like this:
|
||||
|
||||
$ python modelviz.py [-h] [-a] [-d] [-g] [-n] [-L <language>] [-i <model_names>] <app_label> ... <app_label> > <filename>.dot
|
||||
$ dot <filename>.dot -Tpng -o <filename>.png
|
||||
|
||||
options:
|
||||
-h, --help
|
||||
show this help message and exit.
|
||||
|
||||
-a, --all_applications
|
||||
show models from all applications.
|
||||
|
||||
-d, --disable_fields
|
||||
don't show the class member fields.
|
||||
|
||||
-g, --group_models
|
||||
draw an enclosing box around models from the same app.
|
||||
|
||||
-i, --include_models=User,Person,Car
|
||||
only include selected models in graph.
|
||||
|
||||
-n, --verbose_names
|
||||
use verbose_name for field and models.
|
||||
|
||||
-L, --language
|
||||
specify language used for verrbose_name localization
|
||||
|
||||
-x, --exclude_columns
|
||||
exclude specific column(s) from the graph.
|
||||
|
||||
-X, --exclude_models
|
||||
exclude specific model(s) from the graph.
|
||||
"""
|
||||
__version__ = "0.9"
|
||||
__svnid__ = "$Id$"
|
||||
__license__ = "Python"
|
||||
__author__ = "Antonio Cavedoni <http://cavedoni.com/>"
|
||||
__contributors__ = [
|
||||
"Stefano J. Attardi <http://attardi.org/>",
|
||||
"limodou <http://www.donews.net/limodou/>",
|
||||
"Carlo C8E Miron",
|
||||
"Andre Campos <cahenan@gmail.com>",
|
||||
"Justin Findlay <jfindlay@gmail.com>",
|
||||
"Alexander Houben <alexander@houben.ch>",
|
||||
"Bas van Oostveen <v.oostveen@gmail.com>",
|
||||
]
|
||||
|
||||
import os
|
||||
import sys
|
||||
import getopt
|
||||
|
||||
from django.core.management import setup_environ
|
||||
|
||||
try:
|
||||
import settings
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
setup_environ(settings)
|
||||
|
||||
from django.utils.translation import activate as activate_language
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.template import Template, Context, loader
|
||||
from django.db import models
|
||||
from django.db.models import get_models
|
||||
from django.db.models.fields.related import \
|
||||
ForeignKey, OneToOneField, ManyToManyField
|
||||
|
||||
try:
|
||||
from django.db.models.fields.generic import GenericRelation
|
||||
except ImportError:
|
||||
from django.contrib.contenttypes.generic import GenericRelation
|
||||
|
||||
|
||||
def parse_file_or_list(arg):
|
||||
if not arg:
|
||||
return []
|
||||
if not ',' in arg and os.path.isfile(arg):
|
||||
return [e.strip() for e in open(arg).readlines()]
|
||||
return arg.split(',')
|
||||
|
||||
|
||||
def generate_dot(app_labels, **kwargs):
|
||||
disable_fields = kwargs.get('disable_fields', False)
|
||||
include_models = parse_file_or_list(kwargs.get('include_models', ""))
|
||||
all_applications = kwargs.get('all_applications', False)
|
||||
use_subgraph = kwargs.get('group_models', False)
|
||||
verbose_names = kwargs.get('verbose_names', False)
|
||||
language = kwargs.get('language', None)
|
||||
if language is not None:
|
||||
activate_language(language)
|
||||
exclude_columns = parse_file_or_list(kwargs.get('exclude_columns', ""))
|
||||
exclude_models = parse_file_or_list(kwargs.get('exclude_models', ""))
|
||||
|
||||
def skip_field(field):
|
||||
if exclude_columns:
|
||||
if verbose_names and field.verbose_name:
|
||||
if field.verbose_name in exclude_columns:
|
||||
return True
|
||||
if field.name in exclude_columns:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
|
||||
|
||||
t = loader.get_template('django_extensions/graph_models/head.html')
|
||||
c = Context({})
|
||||
dot = t.render(c)
|
||||
|
||||
apps = []
|
||||
if all_applications:
|
||||
apps = models.get_apps()
|
||||
|
||||
for app_label in app_labels:
|
||||
app = models.get_app(app_label)
|
||||
if not app in apps:
|
||||
apps.append(app)
|
||||
|
||||
graphs = []
|
||||
for app in apps:
|
||||
graph = Context({
|
||||
'name': '"%s"' % app.__name__,
|
||||
'app_name': "%s" % '.'.join(app.__name__.split('.')[:-1]),
|
||||
'cluster_app_name': "cluster_%s" % app.__name__.replace(".", "_"),
|
||||
'disable_fields': disable_fields,
|
||||
'use_subgraph': use_subgraph,
|
||||
'models': []
|
||||
})
|
||||
|
||||
for appmodel in get_models(app):
|
||||
abstracts = [e.__name__ for e in appmodel.__bases__ if hasattr(e, '_meta') and e._meta.abstract]
|
||||
|
||||
# collect all attribs of abstract superclasses
|
||||
def getBasesAbstractFields(c):
|
||||
_abstract_fields = []
|
||||
for e in c.__bases__:
|
||||
if hasattr(e, '_meta') and e._meta.abstract:
|
||||
_abstract_fields.extend(e._meta.fields)
|
||||
_abstract_fields.extend(getBasesAbstractFields(e))
|
||||
return _abstract_fields
|
||||
abstract_fields = getBasesAbstractFields(appmodel)
|
||||
|
||||
model = {
|
||||
'app_name': appmodel.__module__.replace(".", "_"),
|
||||
'name': appmodel.__name__,
|
||||
'abstracts': abstracts,
|
||||
'fields': [],
|
||||
'relations': []
|
||||
}
|
||||
|
||||
# consider given model name ?
|
||||
def consider(model_name):
|
||||
if exclude_models and model_name in exclude_models:
|
||||
return False
|
||||
return not include_models or model_name in include_models
|
||||
|
||||
if not consider(appmodel._meta.object_name):
|
||||
continue
|
||||
|
||||
if verbose_names and appmodel._meta.verbose_name:
|
||||
model['label'] = appmodel._meta.verbose_name
|
||||
else:
|
||||
model['label'] = model['name']
|
||||
|
||||
# model attributes
|
||||
def add_attributes(field):
|
||||
if verbose_names and field.verbose_name:
|
||||
label = field.verbose_name
|
||||
else:
|
||||
label = field.name
|
||||
|
||||
model['fields'].append({
|
||||
'name': field.name,
|
||||
'label': label,
|
||||
'type': type(field).__name__,
|
||||
'blank': field.blank,
|
||||
'abstract': field in abstract_fields,
|
||||
})
|
||||
|
||||
for field in appmodel._meta.fields:
|
||||
if skip_field(field):
|
||||
continue
|
||||
add_attributes(field)
|
||||
|
||||
if appmodel._meta.many_to_many:
|
||||
for field in appmodel._meta.many_to_many:
|
||||
if skip_field(field):
|
||||
continue
|
||||
add_attributes(field)
|
||||
|
||||
# relations
|
||||
def add_relation(field, extras=""):
|
||||
if verbose_names and field.verbose_name:
|
||||
label = field.verbose_name
|
||||
else:
|
||||
label = field.name
|
||||
|
||||
# show related field name
|
||||
if hasattr(field, 'related_query_name'):
|
||||
label += ' (%s)' % field.related_query_name()
|
||||
|
||||
_rel = {
|
||||
'target_app': field.rel.to.__module__.replace('.', '_'),
|
||||
'target': field.rel.to.__name__,
|
||||
'type': type(field).__name__,
|
||||
'name': field.name,
|
||||
'label': label,
|
||||
'arrows': extras,
|
||||
'needs_node': True
|
||||
}
|
||||
if _rel not in model['relations'] and consider(_rel['target']):
|
||||
model['relations'].append(_rel)
|
||||
|
||||
for field in appmodel._meta.fields:
|
||||
if skip_field(field):
|
||||
continue
|
||||
if isinstance(field, OneToOneField):
|
||||
add_relation(field, '[arrowhead=none arrowtail=none]')
|
||||
elif isinstance(field, ForeignKey):
|
||||
add_relation(field)
|
||||
|
||||
if appmodel._meta.many_to_many:
|
||||
for field in appmodel._meta.many_to_many:
|
||||
if skip_field(field):
|
||||
continue
|
||||
if isinstance(field, ManyToManyField):
|
||||
if (getattr(field, 'creates_table', False) or # django 1.1.
|
||||
(hasattr(field.rel.through, '_meta') and field.rel.through._meta.auto_created)): # django 1.2
|
||||
add_relation(field, '[arrowhead=normal arrowtail=normal]')
|
||||
elif isinstance(field, GenericRelation):
|
||||
add_relation(field, mark_safe('[style="dotted"] [arrowhead=normal arrowtail=normal]'))
|
||||
graph['models'].append(model)
|
||||
graphs.append(graph)
|
||||
|
||||
nodes = []
|
||||
for graph in graphs:
|
||||
nodes.extend([e['name'] for e in graph['models']])
|
||||
|
||||
for graph in graphs:
|
||||
# don't draw duplication nodes because of relations
|
||||
for model in graph['models']:
|
||||
for relation in model['relations']:
|
||||
if relation['target'] in nodes:
|
||||
relation['needs_node'] = False
|
||||
# render templates
|
||||
t = loader.get_template('django_extensions/graph_models/body.html')
|
||||
dot += '\n' + t.render(graph)
|
||||
|
||||
for graph in graphs:
|
||||
t = loader.get_template('django_extensions/graph_models/rel.html')
|
||||
dot += '\n' + t.render(graph)
|
||||
|
||||
|
||||
t = loader.get_template('django_extensions/graph_models/tail.html')
|
||||
c = Context({})
|
||||
dot += '\n' + t.render(c)
|
||||
return dot
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
opts, args = getopt.getopt(sys.argv[1:], "hadgi:L:x:X:",
|
||||
["help", "all_applications", "disable_fields", "group_models", "include_models=", "verbose_names", "language=", "exclude_columns=", "exclude_models="])
|
||||
except getopt.GetoptError, error:
|
||||
print __doc__
|
||||
sys.exit(error)
|
||||
|
||||
kwargs = {}
|
||||
for opt, arg in opts:
|
||||
if opt in ("-h", "--help"):
|
||||
print __doc__
|
||||
sys.exit()
|
||||
if opt in ("-a", "--all_applications"):
|
||||
kwargs['all_applications'] = True
|
||||
if opt in ("-d", "--disable_fields"):
|
||||
kwargs['disable_fields'] = True
|
||||
if opt in ("-g", "--group_models"):
|
||||
kwargs['group_models'] = True
|
||||
if opt in ("-i", "--include_models"):
|
||||
kwargs['include_models'] = arg
|
||||
if opt in ("-n", "--verbose-names"):
|
||||
kwargs['verbose_names'] = True
|
||||
if opt in ("-L", "--language"):
|
||||
kwargs['language'] = arg
|
||||
if opt in ("-x", "--exclude_columns"):
|
||||
kwargs['exclude_columns'] = arg
|
||||
if opt in ("-X", "--exclude_models"):
|
||||
kwargs['exclude_models'] = arg
|
||||
|
||||
if not args and not kwargs.get('all_applications', False):
|
||||
print __doc__
|
||||
sys.exit()
|
||||
|
||||
print generate_dot(args, **kwargs)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,11 @@
|
|||
"""
|
||||
signals we use to trigger regular batch jobs
|
||||
"""
|
||||
from django.dispatch import Signal
|
||||
|
||||
run_minutely_jobs = Signal()
|
||||
run_hourly_jobs = Signal()
|
||||
run_daily_jobs = Signal()
|
||||
run_weekly_jobs = Signal()
|
||||
run_monthly_jobs = Signal()
|
||||
run_yearly_jobs = Signal()
|
|
@ -0,0 +1,8 @@
|
|||
from django.conf import settings
|
||||
import os
|
||||
|
||||
|
||||
def get_project_root():
|
||||
""" get the project root directory """
|
||||
settings_mod = __import__(settings.SETTINGS_MODULE, {}, {}, [''])
|
||||
return os.path.dirname(os.path.abspath(settings_mod.__file__))
|
|
@ -0,0 +1,239 @@
|
|||
"""
|
||||
MongoDB model fields emulating Django Extensions' additional model fields
|
||||
|
||||
These fields are essentially identical to existing Extensions fields, but South hooks have been removed (since mongo requires no schema migration)
|
||||
|
||||
"""
|
||||
|
||||
from django.template.defaultfilters import slugify
|
||||
from django import forms
|
||||
from mongoengine.fields import StringField, DateTimeField
|
||||
import datetime
|
||||
import re
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
try:
|
||||
import uuid
|
||||
except ImportError:
|
||||
from django_extensions.utils import uuid
|
||||
|
||||
class SlugField(StringField):
|
||||
description = _("String (up to %(max_length)s)")
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['max_length'] = kwargs.get('max_length', 50)
|
||||
# Set db_index=True unless it's been set manually.
|
||||
if 'db_index' not in kwargs:
|
||||
kwargs['db_index'] = True
|
||||
super(SlugField, self).__init__(*args, **kwargs)
|
||||
|
||||
def get_internal_type(self):
|
||||
return "SlugField"
|
||||
|
||||
def formfield(self, **kwargs):
|
||||
defaults = {'form_class': forms.SlugField}
|
||||
defaults.update(kwargs)
|
||||
return super(SlugField, self).formfield(**defaults)
|
||||
|
||||
class AutoSlugField(SlugField):
|
||||
""" AutoSlugField, adapted for MongoDB
|
||||
|
||||
By default, sets editable=False, blank=True.
|
||||
|
||||
Required arguments:
|
||||
|
||||
populate_from
|
||||
Specifies which field or list of fields the slug is populated from.
|
||||
|
||||
Optional arguments:
|
||||
|
||||
separator
|
||||
Defines the used separator (default: '-')
|
||||
|
||||
overwrite
|
||||
If set to True, overwrites the slug on every save (default: False)
|
||||
|
||||
Inspired by SmileyChris' Unique Slugify snippet:
|
||||
http://www.djangosnippets.org/snippets/690/
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.setdefault('blank', True)
|
||||
kwargs.setdefault('editable', False)
|
||||
|
||||
populate_from = kwargs.pop('populate_from', None)
|
||||
if populate_from is None:
|
||||
raise ValueError("missing 'populate_from' argument")
|
||||
else:
|
||||
self._populate_from = populate_from
|
||||
self.separator = kwargs.pop('separator', u'-')
|
||||
self.overwrite = kwargs.pop('overwrite', False)
|
||||
super(AutoSlugField, self).__init__(*args, **kwargs)
|
||||
|
||||
def _slug_strip(self, value):
|
||||
"""
|
||||
Cleans up a slug by removing slug separator characters that occur at
|
||||
the beginning or end of a slug.
|
||||
|
||||
If an alternate separator is used, it will also replace any instances
|
||||
of the default '-' separator with the new separator.
|
||||
"""
|
||||
re_sep = '(?:-|%s)' % re.escape(self.separator)
|
||||
value = re.sub('%s+' % re_sep, self.separator, value)
|
||||
return re.sub(r'^%s+|%s+$' % (re_sep, re_sep), '', value)
|
||||
|
||||
def slugify_func(self, content):
|
||||
return slugify(content)
|
||||
|
||||
def create_slug(self, model_instance, add):
|
||||
# get fields to populate from and slug field to set
|
||||
if not isinstance(self._populate_from, (list, tuple)):
|
||||
self._populate_from = (self._populate_from, )
|
||||
slug_field = model_instance._meta.get_field(self.attname)
|
||||
|
||||
if add or self.overwrite:
|
||||
# slugify the original field content and set next step to 2
|
||||
slug_for_field = lambda field: self.slugify_func(getattr(model_instance, field))
|
||||
slug = self.separator.join(map(slug_for_field, self._populate_from))
|
||||
next = 2
|
||||
else:
|
||||
# get slug from the current model instance and calculate next
|
||||
# step from its number, clean-up
|
||||
slug = self._slug_strip(getattr(model_instance, self.attname))
|
||||
next = slug.split(self.separator)[-1]
|
||||
if next.isdigit():
|
||||
slug = self.separator.join(slug.split(self.separator)[:-1])
|
||||
next = int(next)
|
||||
else:
|
||||
next = 2
|
||||
|
||||
# strip slug depending on max_length attribute of the slug field
|
||||
# and clean-up
|
||||
slug_len = slug_field.max_length
|
||||
if slug_len:
|
||||
slug = slug[:slug_len]
|
||||
slug = self._slug_strip(slug)
|
||||
original_slug = slug
|
||||
|
||||
# exclude the current model instance from the queryset used in finding
|
||||
# the next valid slug
|
||||
queryset = model_instance.__class__._default_manager.all()
|
||||
if model_instance.pk:
|
||||
queryset = queryset.exclude(pk=model_instance.pk)
|
||||
|
||||
# form a kwarg dict used to impliment any unique_together contraints
|
||||
kwargs = {}
|
||||
for params in model_instance._meta.unique_together:
|
||||
if self.attname in params:
|
||||
for param in params:
|
||||
kwargs[param] = getattr(model_instance, param, None)
|
||||
kwargs[self.attname] = slug
|
||||
|
||||
# increases the number while searching for the next valid slug
|
||||
# depending on the given slug, clean-up
|
||||
while not slug or queryset.filter(**kwargs):
|
||||
slug = original_slug
|
||||
end = '%s%s' % (self.separator, next)
|
||||
end_len = len(end)
|
||||
if slug_len and len(slug)+end_len > slug_len:
|
||||
slug = slug[:slug_len-end_len]
|
||||
slug = self._slug_strip(slug)
|
||||
slug = '%s%s' % (slug, end)
|
||||
kwargs[self.attname] = slug
|
||||
next += 1
|
||||
return slug
|
||||
|
||||
def pre_save(self, model_instance, add):
|
||||
value = unicode(self.create_slug(model_instance, add))
|
||||
setattr(model_instance, self.attname, value)
|
||||
return value
|
||||
|
||||
def get_internal_type(self):
|
||||
return "SlugField"
|
||||
|
||||
class CreationDateTimeField(DateTimeField):
|
||||
""" CreationDateTimeField
|
||||
|
||||
By default, sets editable=False, blank=True, default=datetime.now
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs.setdefault('default', datetime.datetime.now)
|
||||
DateTimeField.__init__(self, *args, **kwargs)
|
||||
|
||||
def get_internal_type(self):
|
||||
return "DateTimeField"
|
||||
|
||||
class ModificationDateTimeField(CreationDateTimeField):
|
||||
""" ModificationDateTimeField
|
||||
|
||||
By default, sets editable=False, blank=True, default=datetime.now
|
||||
|
||||
Sets value to datetime.now() on each save of the model.
|
||||
"""
|
||||
|
||||
def pre_save(self, model, add):
|
||||
value = datetime.datetime.now()
|
||||
setattr(model, self.attname, value)
|
||||
return value
|
||||
|
||||
def get_internal_type(self):
|
||||
return "DateTimeField"
|
||||
|
||||
class UUIDVersionError(Exception):
|
||||
pass
|
||||
|
||||
class UUIDField(StringField):
|
||||
""" UUIDField
|
||||
|
||||
By default uses UUID version 1 (generate from host ID, sequence number and current time)
|
||||
|
||||
The field support all uuid versions which are natively supported by the uuid python module.
|
||||
For more information see: http://docs.python.org/lib/module-uuid.html
|
||||
"""
|
||||
|
||||
def __init__(self, verbose_name=None, name=None, auto=True, version=1, node=None, clock_seq=None, namespace=None, **kwargs):
|
||||
kwargs['max_length'] = 36
|
||||
self.auto = auto
|
||||
self.version = version
|
||||
if version==1:
|
||||
self.node, self.clock_seq = node, clock_seq
|
||||
elif version==3 or version==5:
|
||||
self.namespace, self.name = namespace, name
|
||||
StringField.__init__(self, verbose_name, name, **kwargs)
|
||||
|
||||
def get_internal_type(self):
|
||||
return StringField.__name__
|
||||
|
||||
def contribute_to_class(self, cls, name):
|
||||
if self.primary_key:
|
||||
assert not cls._meta.has_auto_field, "A model can't have more than one AutoField: %s %s %s; have %s" % (self,cls,name,cls._meta.auto_field)
|
||||
super(UUIDField, self).contribute_to_class(cls, name)
|
||||
cls._meta.has_auto_field = True
|
||||
cls._meta.auto_field = self
|
||||
else:
|
||||
super(UUIDField, self).contribute_to_class(cls, name)
|
||||
|
||||
def create_uuid(self):
|
||||
if not self.version or self.version==4:
|
||||
return uuid.uuid4()
|
||||
elif self.version==1:
|
||||
return uuid.uuid1(self.node, self.clock_seq)
|
||||
elif self.version==2:
|
||||
raise UUIDVersionError("UUID version 2 is not supported.")
|
||||
elif self.version==3:
|
||||
return uuid.uuid3(self.namespace, self.name)
|
||||
elif self.version==5:
|
||||
return uuid.uuid5(self.namespace, self.name)
|
||||
else:
|
||||
raise UUIDVersionError("UUID version %s is not valid." % self.version)
|
||||
|
||||
def pre_save(self, model_instance, add):
|
||||
if self.auto and add:
|
||||
value = unicode(self.create_uuid())
|
||||
setattr(model_instance, self.attname, value)
|
||||
return value
|
||||
else:
|
||||
value = super(UUIDField, self).pre_save(model_instance, add)
|
||||
if self.auto and not value:
|
||||
value = unicode(self.create_uuid())
|
||||
setattr(model_instance, self.attname, value)
|
||||
return value
|
|
@ -0,0 +1,59 @@
|
|||
"""
|
||||
Encrypted fields from Django Extensions, modified for use with mongoDB
|
||||
"""
|
||||
from mongoengine.base import BaseField
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
|
||||
try:
|
||||
from keyczar import keyczar
|
||||
except ImportError:
|
||||
raise ImportError('Using an encrypted field requires the Keyczar module. You can obtain Keyczar from http://www.keyczar.org/.')
|
||||
|
||||
class BaseEncryptedField(BaseField):
|
||||
prefix = 'enc_str:::'
|
||||
def __init__(self, *args, **kwargs):
|
||||
if not hasattr(settings, 'ENCRYPTED_FIELD_KEYS_DIR'):
|
||||
raise ImproperlyConfigured('You must set settings.ENCRYPTED_FIELD_KEYS_DIR to your Keyczar keys directory.')
|
||||
self.crypt = keyczar.Crypter.Read(settings.ENCRYPTED_FIELD_KEYS_DIR)
|
||||
super(BaseEncryptedField, self).__init__(*args, **kwargs)
|
||||
|
||||
def to_python(self, value):
|
||||
if (value.startswith(self.prefix)):
|
||||
retval = self.crypt.Decrypt(value[len(self.prefix):])
|
||||
else:
|
||||
retval = value
|
||||
|
||||
return retval
|
||||
|
||||
def get_db_prep_value(self, value):
|
||||
if not value.startswith(self.prefix):
|
||||
value = self.prefix + self.crypt.Encrypt(value)
|
||||
return value
|
||||
|
||||
class EncryptedTextField(BaseEncryptedField):
|
||||
|
||||
def get_internal_type(self):
|
||||
return 'StringField'
|
||||
|
||||
def formfield(self, **kwargs):
|
||||
defaults = {'widget': forms.Textarea}
|
||||
defaults.update(kwargs)
|
||||
return super(EncryptedTextField, self).formfield(**defaults)
|
||||
|
||||
class EncryptedCharField(BaseEncryptedField):
|
||||
|
||||
def __init__(self, max_length=None, *args, **kwargs):
|
||||
if max_length:
|
||||
max_length += len(self.prefix)
|
||||
|
||||
super(EncryptedCharField, self).__init__(max_length=max_length, *args, **kwargs)
|
||||
|
||||
def get_internal_type(self):
|
||||
return "StringField"
|
||||
|
||||
def formfield(self, **kwargs):
|
||||
defaults = {'max_length': self.max_length}
|
||||
defaults.update(kwargs)
|
||||
return super(EncryptedCharField, self).formfield(**defaults)
|
|
@ -0,0 +1,75 @@
|
|||
"""
|
||||
JSONField automatically serializes most Python terms to JSON data.
|
||||
Creates a TEXT field with a default value of "{}". See test_json.py for
|
||||
more information.
|
||||
|
||||
from django.db import models
|
||||
from django_extensions.db.fields import json
|
||||
|
||||
class LOL(models.Model):
|
||||
extra = json.JSONField()
|
||||
"""
|
||||
|
||||
import datetime
|
||||
from decimal import Decimal
|
||||
from django.conf import settings
|
||||
from django.utils import simplejson
|
||||
from django.utils.encoding import smart_unicode
|
||||
from mongoengine.fields import StringField
|
||||
|
||||
class JSONEncoder(simplejson.JSONEncoder):
|
||||
def default(self, obj):
|
||||
if isinstance(obj, Decimal):
|
||||
return str(obj)
|
||||
elif isinstance(obj, datetime.datetime):
|
||||
assert settings.TIME_ZONE == 'UTC'
|
||||
return obj.strftime('%Y-%m-%dT%H:%M:%SZ')
|
||||
return simplejson.JSONEncoder.default(self, obj)
|
||||
|
||||
def dumps(value):
|
||||
assert isinstance(value, dict)
|
||||
return JSONEncoder().encode(value)
|
||||
|
||||
def loads(txt):
|
||||
value = simplejson.loads(
|
||||
txt,
|
||||
parse_float = Decimal,
|
||||
encoding = settings.DEFAULT_CHARSET)
|
||||
assert isinstance(value, dict)
|
||||
return value
|
||||
|
||||
class JSONDict(dict):
|
||||
"""
|
||||
Hack so repr() called by dumpdata will output JSON instead of
|
||||
Python formatted data. This way fixtures will work!
|
||||
"""
|
||||
def __repr__(self):
|
||||
return dumps(self)
|
||||
|
||||
class JSONField(StringField):
|
||||
"""JSONField is a generic textfield that neatly serializes/unserializes
|
||||
JSON objects seamlessly. Main thingy must be a dict object."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if 'default' not in kwargs:
|
||||
kwargs['default'] = '{}'
|
||||
StringField.__init__(self, *args, **kwargs)
|
||||
|
||||
def to_python(self, value):
|
||||
"""Convert our string value to JSON after we load it from the DB"""
|
||||
if not value:
|
||||
return {}
|
||||
elif isinstance(value, basestring):
|
||||
res = loads(value)
|
||||
assert isinstance(res, dict)
|
||||
return JSONDict(**res)
|
||||
else:
|
||||
return value
|
||||
|
||||
def get_db_prep_save(self, value):
|
||||
"""Convert our JSON object to a string before we save"""
|
||||
if not value:
|
||||
return super(JSONField, self).get_db_prep_save("")
|
||||
else:
|
||||
return super(JSONField, self).get_db_prep_save(dumps(value))
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
"""
|
||||
Django Extensions abstract base mongoengine Document classes.
|
||||
"""
|
||||
import datetime
|
||||
from mongoengine.document import Document
|
||||
from mongoengine.fields import StringField, IntField, DateTimeField
|
||||
from mongoengine.queryset import QuerySetManager
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django_extensions.mongodb.fields import (ModificationDateTimeField,
|
||||
CreationDateTimeField, AutoSlugField)
|
||||
|
||||
class TimeStampedModel(Document):
|
||||
""" TimeStampedModel
|
||||
An abstract base class model that provides self-managed "created" and
|
||||
"modified" fields.
|
||||
"""
|
||||
created = CreationDateTimeField(_('created'))
|
||||
modified = ModificationDateTimeField(_('modified'))
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
class TitleSlugDescriptionModel(Document):
|
||||
""" TitleSlugDescriptionModel
|
||||
An abstract base class model that provides title and description fields
|
||||
and a self-managed "slug" field that populates from the title.
|
||||
"""
|
||||
title = StringField(_('title'), max_length=255)
|
||||
slug = AutoSlugField(_('slug'), populate_from='title')
|
||||
description = StringField(_('description'), blank=True, null=True)
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
class ActivatorModelManager(QuerySetManager):
|
||||
""" ActivatorModelManager
|
||||
Manager to return instances of ActivatorModel: SomeModel.objects.active() / .inactive()
|
||||
"""
|
||||
def active(self):
|
||||
""" Returns active instances of ActivatorModel: SomeModel.objects.active() """
|
||||
return super(ActivatorModelManager, self).get_query_set().filter(status=1)
|
||||
|
||||
def inactive(self):
|
||||
""" Returns inactive instances of ActivatorModel: SomeModel.objects.inactive() """
|
||||
return super(ActivatorModelManager, self).get_query_set().filter(status=0)
|
||||
|
||||
class ActivatorModel(Document):
|
||||
""" ActivatorModel
|
||||
An abstract base class model that provides activate and deactivate fields.
|
||||
"""
|
||||
STATUS_CHOICES = (
|
||||
(0, _('Inactive')),
|
||||
(1, _('Active')),
|
||||
)
|
||||
status = IntField(_('status'), choices=STATUS_CHOICES,
|
||||
default=1)
|
||||
activate_date = DateTimeField(blank=True, null=True,
|
||||
help_text=_('keep empty for an immediate activation'))
|
||||
deactivate_date = DateTimeField(blank=True, null=True,
|
||||
help_text=_('keep empty for indefinite activation'))
|
||||
objects = ActivatorModelManager()
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
if not self.activate_date:
|
||||
self.activate_date = datetime.datetime.now()
|
||||
super(ActivatorModel, self).save(*args, **kwargs)
|
|
@ -0,0 +1,7 @@
|
|||
from django.conf import settings
|
||||
|
||||
REPLACEMENTS = {
|
||||
}
|
||||
add_replacements = getattr(settings, 'EXTENSIONS_REPLACEMENTS', {})
|
||||
REPLACEMENTS.update(add_replacements)
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
"""
|
||||
Similar to syntax_color.py but this is intended more for being able to
|
||||
copy+paste actual code into your Django templates without needing to
|
||||
escape or anything crazy.
|
||||
|
||||
http://lobstertech.com/2008/aug/30/django_syntax_highlight_template_tag/
|
||||
|
||||
Example:
|
||||
|
||||
{% load highlighting %}
|
||||
|
||||
<style>
|
||||
@import url("http://lobstertech.com/media/css/highlight.css");
|
||||
.highlight { background: #f8f8f8; }
|
||||
.highlight { font-size: 11px; margin: 1em; border: 1px solid #ccc;
|
||||
border-left: 3px solid #F90; padding: 0; }
|
||||
.highlight pre { padding: 1em; overflow: auto; line-height: 120%; margin: 0; }
|
||||
.predesc { margin: 1.5em 1.5em -2.5em 1em; text-align: right;
|
||||
font: bold 12px Tahoma, Arial, sans-serif;
|
||||
letter-spacing: 1px; color: #333; }
|
||||
</style>
|
||||
|
||||
<h2>check out this code</h2>
|
||||
|
||||
{% highlight 'python' 'Excerpt: blah.py' %}
|
||||
def need_food(self):
|
||||
print "Love is <colder> than &death&"
|
||||
{% endhighlight %}
|
||||
|
||||
"""
|
||||
|
||||
from pygments import highlight as pyghighlight
|
||||
from pygments.lexers import get_lexer_by_name, guess_lexer
|
||||
from pygments.formatters import HtmlFormatter
|
||||
from django.conf import settings
|
||||
from django import template
|
||||
from django.template import Template, Context, Node, Variable
|
||||
from django.template.defaultfilters import stringfilter
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter
|
||||
@stringfilter
|
||||
def parse_template(value):
|
||||
return mark_safe(Template(value).render(Context()))
|
||||
parse_template.is_safe = True
|
||||
|
||||
|
||||
class CodeNode(Node):
|
||||
def __init__(self, language, nodelist, name=''):
|
||||
self.language = Variable(language)
|
||||
self.nodelist = nodelist
|
||||
if name:
|
||||
self.name = Variable(name)
|
||||
else:
|
||||
self.name = None
|
||||
|
||||
def render(self, context):
|
||||
code = self.nodelist.render(context).strip()
|
||||
lexer = get_lexer_by_name(self.language.resolve(context))
|
||||
formatter = HtmlFormatter(linenos=False)
|
||||
html = ""
|
||||
if self.name:
|
||||
name = self.name.resolve(context)
|
||||
html = '<div class="predesc"><span>%s</span></div>' % (name)
|
||||
return html + pyghighlight(code, lexer, formatter)
|
||||
|
||||
|
||||
@register.tag
|
||||
def highlight(parser, token):
|
||||
"""
|
||||
Allows you to put a highlighted source code <pre> block in your code.
|
||||
This takes two arguments, the language and a little explaination message
|
||||
that will be generated before the code. The second argument is optional.
|
||||
|
||||
Your code will be fed through pygments so you can use any language it
|
||||
supports.
|
||||
|
||||
{% load highlighting %}
|
||||
{% highlight 'python' 'Excerpt: blah.py' %}
|
||||
def need_food(self):
|
||||
print "Love is colder than death"
|
||||
{% endhighlight %}
|
||||
"""
|
||||
nodelist = parser.parse(('endhighlight',))
|
||||
parser.delete_first_token()
|
||||
bits = token.split_contents()[1:]
|
||||
if len(bits) < 1:
|
||||
raise TemplateSyntaxError("'highlight' statement requires an argument")
|
||||
return CodeNode(bits[0], nodelist, *bits[1:])
|
|
@ -0,0 +1,97 @@
|
|||
r"""
|
||||
Template filter for rendering a string with syntax highlighting.
|
||||
It relies on Pygments to accomplish this.
|
||||
|
||||
Some standard usage examples (from within Django templates).
|
||||
Coloring a string with the Python lexer:
|
||||
|
||||
{% load syntax_color %}
|
||||
{{ code_string|colorize:"python" }}
|
||||
|
||||
You may use any lexer in Pygments. The complete list of which
|
||||
can be found [on the Pygments website][1].
|
||||
|
||||
[1]: http://pygments.org/docs/lexers/
|
||||
|
||||
You may also have Pygments attempt to guess the correct lexer for
|
||||
a particular string. However, if may not be able to choose a lexer,
|
||||
in which case it will simply return the string unmodified. This is
|
||||
less efficient compared to specifying the lexer to use.
|
||||
|
||||
{{ code_string|colorize }}
|
||||
|
||||
You may also render the syntax highlighed text with line numbers.
|
||||
|
||||
{% load syntax_color %}
|
||||
{{ some_code|colorize_table:"html+django" }}
|
||||
{{ let_pygments_pick_for_this_code|colorize_table }}
|
||||
|
||||
Please note that before you can load the ``syntax_color`` template filters
|
||||
you will need to add the ``django_extensions.utils`` application to the
|
||||
``INSTALLED_APPS``setting in your project's ``settings.py`` file.
|
||||
"""
|
||||
|
||||
__author__ = 'Will Larson <lethain@gmail.com>'
|
||||
|
||||
|
||||
from django import template
|
||||
from django.template.defaultfilters import stringfilter
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
|
||||
try:
|
||||
from pygments import highlight
|
||||
from pygments.formatters import HtmlFormatter
|
||||
from pygments.lexers import get_lexer_by_name, guess_lexer, ClassNotFound
|
||||
except ImportError:
|
||||
raise ImproperlyConfigured(
|
||||
"Please install 'pygments' library to use syntax_color.")
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.simple_tag
|
||||
def pygments_css():
|
||||
return HtmlFormatter().get_style_defs('.highlight')
|
||||
|
||||
|
||||
def generate_pygments_css(path=None):
|
||||
if path is None:
|
||||
import os
|
||||
path = os.path.join(os.getcwd(), 'pygments.css')
|
||||
f = open(path, 'w')
|
||||
f.write(pygments_css())
|
||||
f.close()
|
||||
|
||||
|
||||
def get_lexer(value, arg):
|
||||
if arg is None:
|
||||
return guess_lexer(value)
|
||||
return get_lexer_by_name(arg)
|
||||
|
||||
|
||||
@register.filter(name='colorize')
|
||||
@stringfilter
|
||||
def colorize(value, arg=None):
|
||||
try:
|
||||
return mark_safe(highlight(value, get_lexer(value, arg), HtmlFormatter()))
|
||||
except ClassNotFound:
|
||||
return value
|
||||
|
||||
|
||||
@register.filter(name='colorize_table')
|
||||
@stringfilter
|
||||
def colorize_table(value, arg=None):
|
||||
try:
|
||||
return mark_safe(highlight(value, get_lexer(value, arg), HtmlFormatter(linenos='table')))
|
||||
except ClassNotFound:
|
||||
return value
|
||||
|
||||
|
||||
@register.filter(name='colorize_noclasses')
|
||||
@stringfilter
|
||||
def colorize_noclasses(value, arg=None):
|
||||
try:
|
||||
return mark_safe(highlight(value, get_lexer(value, arg), HtmlFormatter(noclasses=True)))
|
||||
except ClassNotFound:
|
||||
return value
|
|
@ -0,0 +1,22 @@
|
|||
from django import template
|
||||
from django.template.defaultfilters import stringfilter
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
def truncateletters(value, arg):
|
||||
"""
|
||||
Truncates a string after a certain number of letters
|
||||
|
||||
Argument: Number of letters to truncate after
|
||||
"""
|
||||
from django_extensions.utils.text import truncate_letters
|
||||
try:
|
||||
length = int(arg)
|
||||
except ValueError: # invalid literal for int()
|
||||
return value # Fail silently
|
||||
return truncate_letters(value, length)
|
||||
|
||||
truncateletters.is_safe = True
|
||||
truncateletters = stringfilter(truncateletters)
|
||||
register.filter(truncateletters)
|
|
@ -0,0 +1,61 @@
|
|||
from django.template import Library
|
||||
from django.utils.encoding import force_unicode
|
||||
import re
|
||||
|
||||
register = Library()
|
||||
re_widont = re.compile(r'\s+(\S+\s*)$')
|
||||
re_widont_html = re.compile(r'([^<>\s])\s+([^<>\s]+\s*)(</?(?:address|blockquote|br|dd|div|dt|fieldset|form|h[1-6]|li|noscript|p|td|th)[^>]*>|$)', re.IGNORECASE)
|
||||
|
||||
|
||||
def widont(value, count=1):
|
||||
"""
|
||||
Adds an HTML non-breaking space between the final two words of the string to
|
||||
avoid "widowed" words.
|
||||
|
||||
Examples:
|
||||
|
||||
>>> print widont('Test me out')
|
||||
Test me out
|
||||
|
||||
>>> widont('It works with trailing spaces too ')
|
||||
u'It works with trailing spaces too '
|
||||
|
||||
>>> print widont('NoEffect')
|
||||
NoEffect
|
||||
"""
|
||||
def replace(matchobj):
|
||||
return u' %s' % matchobj.group(1)
|
||||
for i in range(count):
|
||||
value = re_widont.sub(replace, force_unicode(value))
|
||||
return value
|
||||
|
||||
|
||||
def widont_html(value):
|
||||
"""
|
||||
Adds an HTML non-breaking space between the final two words at the end of
|
||||
(and in sentences just outside of) block level tags to avoid "widowed"
|
||||
words.
|
||||
|
||||
Examples:
|
||||
|
||||
>>> print widont_html('<h2>Here is a simple example </h2> <p>Single</p>')
|
||||
<h2>Here is a simple example </h2> <p>Single</p>
|
||||
|
||||
>>> print widont_html('<p>test me<br /> out</p><h2>Ok?</h2>Not in a p<p title="test me">and this</p>')
|
||||
<p>test me<br /> out</p><h2>Ok?</h2>Not in a p<p title="test me">and this</p>
|
||||
|
||||
>>> print widont_html('leading text <p>test me out</p> trailing text')
|
||||
leading text <p>test me out</p> trailing text
|
||||
"""
|
||||
def replace(matchobj):
|
||||
return u'%s %s%s' % matchobj.groups()
|
||||
return re_widont_html.sub(replace, force_unicode(value))
|
||||
|
||||
register.filter(widont)
|
||||
register.filter(widont_html)
|
||||
|
||||
if __name__ == "__main__":
|
||||
def _test():
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
_test()
|
|
@ -0,0 +1,13 @@
|
|||
from django.db import models
|
||||
from django_extensions.tests.utils import UTILS_TRUNCATE_LETTERS_TESTS
|
||||
from django_extensions.tests.utils import UTILS_UUID_TESTS
|
||||
try:
|
||||
from django_extensions.tests.encrypted_fields import EncryptedFieldsTestCase
|
||||
from django_extensions.tests.models import Secret
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
__test__ = {
|
||||
'UTILS_TRUNCATE_LETTERS_TESTS': UTILS_TRUNCATE_LETTERS_TESTS,
|
||||
'UTILS_UUID_TESTS': UTILS_UUID_TESTS,
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
import unittest
|
||||
|
||||
|
||||
from django.db import connection
|
||||
from django.conf import settings
|
||||
from django.core.management import call_command
|
||||
from django.db.models import loading
|
||||
|
||||
# Only perform encrypted fields tests if keyczar is present
|
||||
# Resolves http://github.com/django-extensions/django-extensions/issues/#issue/17
|
||||
try:
|
||||
from keyczar import keyczar
|
||||
from django_extensions.tests.models import Secret
|
||||
from django_extensions.db.fields.encrypted import EncryptedTextField, EncryptedCharField
|
||||
keyczar_active = True
|
||||
except ImportError:
|
||||
keyczar_active = False
|
||||
|
||||
|
||||
class EncryptedFieldsTestCase(unittest.TestCase):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if keyczar_active:
|
||||
self.crypt = keyczar.Crypter.Read(settings.ENCRYPTED_FIELD_KEYS_DIR)
|
||||
super(EncryptedFieldsTestCase, self).__init__(*args, **kwargs)
|
||||
|
||||
def setUp(self):
|
||||
self.old_installed_apps = settings.INSTALLED_APPS
|
||||
settings.INSTALLED_APPS.append('django_extensions.tests')
|
||||
loading.cache.loaded = False
|
||||
call_command('syncdb', verbosity=0)
|
||||
|
||||
def tearDown(self):
|
||||
settings.INSTALLED_APPS = self.old_installed_apps
|
||||
|
||||
def testCharFieldCreate(self):
|
||||
if not keyczar_active:
|
||||
return
|
||||
test_val = "Test Secret"
|
||||
secret = Secret.objects.create(name=test_val)
|
||||
cursor = connection.cursor()
|
||||
query = "SELECT name FROM %s WHERE id = %d" % (Secret._meta.db_table, secret.id)
|
||||
cursor.execute(query)
|
||||
db_val, = cursor.fetchone()
|
||||
decrypted_val = self.crypt.Decrypt(db_val[len(EncryptedCharField.prefix):])
|
||||
self.assertEqual(test_val, decrypted_val)
|
||||
|
||||
def testCharFieldRead(self):
|
||||
if not keyczar_active:
|
||||
return
|
||||
test_val = "Test Secret"
|
||||
secret = Secret.objects.create(name=test_val)
|
||||
retrieved_secret = Secret.objects.get(id=secret.id)
|
||||
self.assertEqual(test_val, retrieved_secret.name)
|
||||
|
||||
def testTextFieldCreate(self):
|
||||
if not keyczar_active:
|
||||
return
|
||||
test_val = "Test Secret"
|
||||
secret = Secret.objects.create(text=test_val)
|
||||
cursor = connection.cursor()
|
||||
query = "SELECT text FROM %s WHERE id = %d" % (Secret._meta.db_table, secret.id)
|
||||
cursor.execute(query)
|
||||
db_val, = cursor.fetchone()
|
||||
decrypted_val = self.crypt.Decrypt(db_val[len(EncryptedCharField.prefix):])
|
||||
self.assertEqual(test_val, decrypted_val)
|
||||
|
||||
def testTextFieldRead(self):
|
||||
if not keyczar_active:
|
||||
return
|
||||
test_val = "Test Secret"
|
||||
secret = Secret.objects.create(text=test_val)
|
||||
retrieved_secret = Secret.objects.get(id=secret.id)
|
||||
self.assertEqual(test_val, retrieved_secret.text)
|
|
@ -0,0 +1,17 @@
|
|||
from django.db import models
|
||||
|
||||
try:
|
||||
from django_extensions.db.fields.encrypted import EncryptedTextField, EncryptedCharField
|
||||
except ImportError:
|
||||
class EncryptedCharField():
|
||||
def __init__(self, **kwargs):
|
||||
pass
|
||||
|
||||
class EncryptedTextField():
|
||||
def __init__(self, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
class Secret(models.Model):
|
||||
name = EncryptedCharField(blank=True, max_length=255)
|
||||
text = EncryptedTextField(blank=True)
|
|
@ -0,0 +1,51 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
UTILS_TRUNCATE_LETTERS_TESTS = """
|
||||
>>> from django_extensions.utils.text import truncate_letters
|
||||
>>> truncate_letters("hello tests", 100)
|
||||
u'hello tests'
|
||||
>>> truncate_letters("hello tests", 5)
|
||||
u'hello...'
|
||||
>>> for i in range(10,-1,-1): truncate_letters("hello tests", i),i
|
||||
(u'hello test...', 10)
|
||||
(u'hello tes...', 9)
|
||||
(u'hello te...', 8)
|
||||
(u'hello t...', 7)
|
||||
(u'hello ...', 6)
|
||||
(u'hello...', 5)
|
||||
(u'hell...', 4)
|
||||
(u'hel...', 3)
|
||||
(u'he...', 2)
|
||||
(u'h...', 1)
|
||||
(u'...', 0)
|
||||
>>> truncate_letters("峠 (とうげ tōge - mountain pass)", 10)
|
||||
u'\u5ce0 (\u3068\u3046\u3052 t\u014dg...'
|
||||
|
||||
"""
|
||||
|
||||
UTILS_UUID_TESTS = """
|
||||
>>> from django_extensions.utils import uuid
|
||||
|
||||
# make a UUID using an MD5 hash of a namespace UUID and a name
|
||||
>>> uuid.uuid3(uuid.NAMESPACE_DNS, 'python.org')
|
||||
UUID('6fa459ea-ee8a-3ca4-894e-db77e160355e')
|
||||
|
||||
# make a UUID using a SHA-1 hash of a namespace UUID and a name
|
||||
>>> uuid.uuid5(uuid.NAMESPACE_DNS, 'python.org')
|
||||
UUID('886313e1-3b8a-5372-9b90-0c9aee199e5d')
|
||||
|
||||
# make a UUID from a string of hex digits (braces and hyphens ignored)
|
||||
>>> x = uuid.UUID('{00010203-0405-0607-0809-0a0b0c0d0e0f}')
|
||||
|
||||
# convert a UUID to a string of hex digits in standard form
|
||||
>>> str(x)
|
||||
'00010203-0405-0607-0809-0a0b0c0d0e0f'
|
||||
|
||||
# get the raw 16 bytes of the UUID
|
||||
>>> x.bytes
|
||||
'\\x00\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\t\\n\\x0b\\x0c\\r\\x0e\\x0f'
|
||||
|
||||
# make a UUID from a 16-byte string
|
||||
>>> uuid.UUID(bytes=x.bytes)
|
||||
UUID('00010203-0405-0607-0809-0a0b0c0d0e0f')
|
||||
"""
|
|
@ -0,0 +1,214 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: UTF-8 -*-
|
||||
##Author Igor Támara igor@tamarapatino.org
|
||||
##Use this little program as you wish, if you
|
||||
#include it in your work, let others know you
|
||||
#are using it preserving this note, you have
|
||||
#the right to make derivative works, Use it
|
||||
#at your own risk.
|
||||
#Tested to work on(etch testing 13-08-2007):
|
||||
# Python 2.4.4 (#2, Jul 17 2007, 11:56:54)
|
||||
# [GCC 4.1.3 20070629 (prerelease) (Debian 4.1.2-13)] on linux2
|
||||
|
||||
dependclasses = ["User", "Group", "Permission", "Message"]
|
||||
|
||||
import codecs
|
||||
import sys
|
||||
import gzip
|
||||
from xml.dom.minidom import *
|
||||
import re
|
||||
|
||||
#Type dictionary translation types SQL -> Django
|
||||
tsd = {
|
||||
"text": "TextField",
|
||||
"date": "DateField",
|
||||
"varchar": "CharField",
|
||||
"int": "IntegerField",
|
||||
"float": "FloatField",
|
||||
"serial": "AutoField",
|
||||
"boolean": "BooleanField",
|
||||
"numeric": "FloatField",
|
||||
"timestamp": "DateTimeField",
|
||||
"bigint": "IntegerField",
|
||||
"datetime": "DateTimeField",
|
||||
"date": "DateField",
|
||||
"time": "TimeField",
|
||||
"bool": "BooleanField",
|
||||
"int": "IntegerField",
|
||||
}
|
||||
|
||||
#convert varchar -> CharField
|
||||
v2c = re.compile('varchar\((\d+)\)')
|
||||
|
||||
|
||||
def index(fks, id):
|
||||
"""Looks for the id on fks, fks is an array of arrays, each array has on [1]
|
||||
the id of the class in a dia diagram. When not present returns None, else
|
||||
it returns the position of the class with id on fks"""
|
||||
for i, j in fks.items():
|
||||
if fks[i][1] == id:
|
||||
return i
|
||||
return None
|
||||
|
||||
|
||||
def addparentstofks(rels, fks):
|
||||
"""Gets a list of relations, between parents and sons and a dict of
|
||||
clases named in dia, and modifies the fks to add the parent as fk to get
|
||||
order on the output of classes and replaces the base class of the son, to
|
||||
put the class parent name.
|
||||
"""
|
||||
for j in rels:
|
||||
son = index(fks, j[1])
|
||||
parent = index(fks, j[0])
|
||||
fks[son][2] = fks[son][2].replace("models.Model", parent)
|
||||
if parent not in fks[son][0]:
|
||||
fks[son][0].append(parent)
|
||||
|
||||
|
||||
def dia2django(archivo):
|
||||
models_txt = ''
|
||||
f = codecs.open(archivo, "rb")
|
||||
#dia files are gzipped
|
||||
data = gzip.GzipFile(fileobj=f).read()
|
||||
ppal = parseString(data)
|
||||
#diagram -> layer -> object -> UML - Class -> name, (attribs : composite -> name,type)
|
||||
datos = ppal.getElementsByTagName("dia:diagram")[0].getElementsByTagName("dia:layer")[0].getElementsByTagName("dia:object")
|
||||
clases = {}
|
||||
herit = []
|
||||
imports = u""
|
||||
for i in datos:
|
||||
#Look for the classes
|
||||
if i.getAttribute("type") == "UML - Class":
|
||||
myid = i.getAttribute("id")
|
||||
for j in i.childNodes:
|
||||
if j.nodeType == Node.ELEMENT_NODE and j.hasAttributes():
|
||||
if j.getAttribute("name") == "name":
|
||||
actclas = j.getElementsByTagName("dia:string")[0].childNodes[0].data[1:-1]
|
||||
myname = "\nclass %s(models.Model) :\n" % actclas
|
||||
clases[actclas] = [[], myid, myname, 0]
|
||||
if j.getAttribute("name") == "attributes":
|
||||
for l in j.getElementsByTagName("dia:composite"):
|
||||
if l.getAttribute("type") == "umlattribute":
|
||||
#Look for the attribute name and type
|
||||
for k in l.getElementsByTagName("dia:attribute"):
|
||||
if k.getAttribute("name") == "name":
|
||||
nc = k.getElementsByTagName("dia:string")[0].childNodes[0].data[1:-1]
|
||||
elif k.getAttribute("name") == "type":
|
||||
tc = k.getElementsByTagName("dia:string")[0].childNodes[0].data[1:-1]
|
||||
elif k.getAttribute("name") == "value":
|
||||
val = k.getElementsByTagName("dia:string")[0].childNodes[0].data[1:-1]
|
||||
if val == '##':
|
||||
val = ''
|
||||
elif k.getAttribute("name") == "visibility" and k.getElementsByTagName("dia:enum")[0].getAttribute("val") == "2":
|
||||
if tc.replace(" ", "").lower().startswith("manytomanyfield("):
|
||||
#If we find a class not in our model that is marked as being to another model
|
||||
newc = tc.replace(" ", "")[16:-1]
|
||||
if dependclasses.count(newc) == 0:
|
||||
dependclasses.append(newc)
|
||||
if tc.replace(" ", "").lower().startswith("foreignkey("):
|
||||
#If we find a class not in our model that is marked as being to another model
|
||||
newc = tc.replace(" ", "")[11:-1]
|
||||
if dependclasses.count(newc) == 0:
|
||||
dependclasses.append(newc)
|
||||
|
||||
#Mapping SQL types to Django
|
||||
varch = v2c.search(tc)
|
||||
if tc.replace(" ", "").startswith("ManyToManyField("):
|
||||
myfor = tc.replace(" ", "")[16:-1]
|
||||
if actclas == myfor:
|
||||
#In case of a recursive type, we use 'self'
|
||||
tc = tc.replace(myfor, "'self'")
|
||||
elif clases[actclas][0].count(myfor) == 0:
|
||||
#Adding related class
|
||||
if myfor not in dependclasses:
|
||||
#In case we are using Auth classes or external via protected dia visibility
|
||||
clases[actclas][0].append(myfor)
|
||||
tc = "models." + tc
|
||||
if len(val) > 0:
|
||||
tc = tc.replace(")", "," + val + ")")
|
||||
elif tc.find("Field") != -1:
|
||||
if tc.count("()") > 0 and len(val) > 0:
|
||||
tc = "models.%s" % tc.replace(")", "," + val + ")")
|
||||
else:
|
||||
tc = "models.%s(%s)" % (tc, val)
|
||||
elif tc.replace(" ", "").startswith("ForeignKey("):
|
||||
myfor = tc.replace(" ", "")[11:-1]
|
||||
if actclas == myfor:
|
||||
#In case of a recursive type, we use 'self'
|
||||
tc = tc.replace(myfor, "'self'")
|
||||
elif clases[actclas][0].count(myfor) == 0:
|
||||
#Adding foreign classes
|
||||
if myfor not in dependclasses:
|
||||
#In case we are using Auth classes
|
||||
clases[actclas][0].append(myfor)
|
||||
tc = "models." + tc
|
||||
if len(val) > 0:
|
||||
tc = tc.replace(")", "," + val + ")")
|
||||
elif varch == None:
|
||||
tc = "models." + tsd[tc.strip().lower()] + "(" + val + ")"
|
||||
else:
|
||||
tc = "models.CharField(max_length=" + varch.group(1) + ")"
|
||||
if len(val) > 0:
|
||||
tc = tc.replace(")", ", " + val + " )")
|
||||
if not (nc == "id" and tc == "AutoField()"):
|
||||
clases[actclas][2] = clases[actclas][2] + (" %s = %s\n" % (nc, tc))
|
||||
elif i.getAttribute("type") == "UML - Generalization":
|
||||
mycons = ['A', 'A']
|
||||
a = i.getElementsByTagName("dia:connection")
|
||||
for j in a:
|
||||
if len(j.getAttribute("to")):
|
||||
mycons[int(j.getAttribute("handle"))] = j.getAttribute("to")
|
||||
print mycons
|
||||
if not 'A' in mycons:
|
||||
herit.append(mycons)
|
||||
elif i.getAttribute("type") == "UML - SmallPackage":
|
||||
a = i.getElementsByTagName("dia:string")
|
||||
for j in a:
|
||||
if len(j.childNodes[0].data[1:-1]):
|
||||
imports += u"from %s.models import *" % j.childNodes[0].data[1:-1]
|
||||
|
||||
addparentstofks(herit, clases)
|
||||
#Ordering the appearance of classes
|
||||
#First we make a list of the classes each classs is related to.
|
||||
ordered = []
|
||||
for j, k in clases.iteritems():
|
||||
k[2] = k[2] + "\n def __unicode__(self):\n return u\"\"\n"
|
||||
for fk in k[0]:
|
||||
if fk not in dependclasses:
|
||||
clases[fk][3] += 1
|
||||
ordered.append([j] + k)
|
||||
|
||||
i = 0
|
||||
while i < len(ordered):
|
||||
mark = i
|
||||
j = i + 1
|
||||
while j < len(ordered):
|
||||
if ordered[i][0] in ordered[j][1]:
|
||||
mark = j
|
||||
j += 1
|
||||
if mark == i:
|
||||
i += 1
|
||||
else:
|
||||
# swap %s in %s" % ( ordered[i] , ordered[mark]) to make ordered[i] to be at the end
|
||||
if ordered[i][0] in ordered[mark][1] and ordered[mark][0] in ordered[i][1]:
|
||||
#Resolving simplistic circular ForeignKeys
|
||||
print "Not able to resolve circular ForeignKeys between %s and %s" % (ordered[i][1], ordered[mark][0])
|
||||
break
|
||||
a = ordered[i]
|
||||
ordered[i] = ordered[mark]
|
||||
ordered[mark] = a
|
||||
if i == len(ordered) - 1:
|
||||
break
|
||||
ordered.reverse()
|
||||
if imports:
|
||||
models_txt = str(imports)
|
||||
for i in ordered:
|
||||
models_txt += '%s\n' % str(i[3])
|
||||
|
||||
return models_txt
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) == 2:
|
||||
dia2django(sys.argv[1])
|
||||
else:
|
||||
print " Use:\n \n " + sys.argv[0] + " diagram.dia\n\n"
|
|
@ -0,0 +1,14 @@
|
|||
from django.utils.encoding import force_unicode
|
||||
from django.utils.functional import allow_lazy
|
||||
|
||||
|
||||
def truncate_letters(s, num):
|
||||
""" truncates a string to a number of letters, similar to truncate_words """
|
||||
s = force_unicode(s)
|
||||
length = int(num)
|
||||
if len(s) > length:
|
||||
s = s[:length]
|
||||
if not s.endswith('...'):
|
||||
s += '...'
|
||||
return s
|
||||
truncate_letters = allow_lazy(truncate_letters, unicode)
|
|
@ -0,0 +1,564 @@
|
|||
r"""UUID objects (universally unique identifiers) according to RFC 4122.
|
||||
|
||||
This module provides immutable UUID objects (class UUID) and the functions
|
||||
uuid1(), uuid3(), uuid4(), uuid5() for generating version 1, 3, 4, and 5
|
||||
UUIDs as specified in RFC 4122.
|
||||
|
||||
If all you want is a unique ID, you should probably call uuid1() or uuid4().
|
||||
Note that uuid1() may compromise privacy since it creates a UUID containing
|
||||
the computer's network address. uuid4() creates a random UUID.
|
||||
|
||||
Typical usage:
|
||||
|
||||
>>> import uuid
|
||||
|
||||
# make a UUID based on the host ID and current time
|
||||
>>> uuid.uuid1()
|
||||
UUID('a8098c1a-f86e-11da-bd1a-00112444be1e')
|
||||
|
||||
# make a UUID using an MD5 hash of a namespace UUID and a name
|
||||
>>> uuid.uuid3(uuid.NAMESPACE_DNS, 'python.org')
|
||||
UUID('6fa459ea-ee8a-3ca4-894e-db77e160355e')
|
||||
|
||||
# make a random UUID
|
||||
>>> uuid.uuid4()
|
||||
UUID('16fd2706-8baf-433b-82eb-8c7fada847da')
|
||||
|
||||
# make a UUID using a SHA-1 hash of a namespace UUID and a name
|
||||
>>> uuid.uuid5(uuid.NAMESPACE_DNS, 'python.org')
|
||||
UUID('886313e1-3b8a-5372-9b90-0c9aee199e5d')
|
||||
|
||||
# make a UUID from a string of hex digits (braces and hyphens ignored)
|
||||
>>> x = uuid.UUID('{00010203-0405-0607-0809-0a0b0c0d0e0f}')
|
||||
|
||||
# convert a UUID to a string of hex digits in standard form
|
||||
>>> str(x)
|
||||
'00010203-0405-0607-0809-0a0b0c0d0e0f'
|
||||
|
||||
# get the raw 16 bytes of the UUID
|
||||
>>> x.bytes
|
||||
'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f'
|
||||
|
||||
# make a UUID from a 16-byte string
|
||||
>>> uuid.UUID(bytes=x.bytes)
|
||||
UUID('00010203-0405-0607-0809-0a0b0c0d0e0f')
|
||||
"""
|
||||
|
||||
__author__ = 'Ka-Ping Yee <ping@zesty.ca>'
|
||||
|
||||
RESERVED_NCS, RFC_4122, RESERVED_MICROSOFT, RESERVED_FUTURE = [
|
||||
'reserved for NCS compatibility', 'specified in RFC 4122',
|
||||
'reserved for Microsoft compatibility', 'reserved for future definition'
|
||||
]
|
||||
|
||||
|
||||
class UUID(object):
|
||||
"""Instances of the UUID class represent UUIDs as specified in RFC 4122.
|
||||
UUID objects are immutable, hashable, and usable as dictionary keys.
|
||||
Converting a UUID to a string with str() yields something in the form
|
||||
'12345678-1234-1234-1234-123456789abc'. The UUID constructor accepts
|
||||
five possible forms: a similar string of hexadecimal digits, or a tuple
|
||||
of six integer fields (with 32-bit, 16-bit, 16-bit, 8-bit, 8-bit, and
|
||||
48-bit values respectively) as an argument named 'fields', or a string
|
||||
of 16 bytes (with all the integer fields in big-endian order) as an
|
||||
argument named 'bytes', or a string of 16 bytes (with the first three
|
||||
fields in little-endian order) as an argument named 'bytes_le', or a
|
||||
single 128-bit integer as an argument named 'int'.
|
||||
|
||||
UUIDs have these read-only attributes:
|
||||
|
||||
bytes the UUID as a 16-byte string (containing the six
|
||||
integer fields in big-endian byte order)
|
||||
|
||||
bytes_le the UUID as a 16-byte string (with time_low, time_mid,
|
||||
and time_hi_version in little-endian byte order)
|
||||
|
||||
fields a tuple of the six integer fields of the UUID,
|
||||
which are also available as six individual attributes
|
||||
and two derived attributes:
|
||||
|
||||
time_low the first 32 bits of the UUID
|
||||
time_mid the next 16 bits of the UUID
|
||||
time_hi_version the next 16 bits of the UUID
|
||||
clock_seq_hi_variant the next 8 bits of the UUID
|
||||
clock_seq_low the next 8 bits of the UUID
|
||||
node the last 48 bits of the UUID
|
||||
|
||||
time the 60-bit timestamp
|
||||
clock_seq the 14-bit sequence number
|
||||
|
||||
hex the UUID as a 32-character hexadecimal string
|
||||
|
||||
int the UUID as a 128-bit integer
|
||||
|
||||
urn the UUID as a URN as specified in RFC 4122
|
||||
|
||||
variant the UUID variant (one of the constants RESERVED_NCS,
|
||||
RFC_4122, RESERVED_MICROSOFT, or RESERVED_FUTURE)
|
||||
|
||||
version the UUID version number (1 through 5, meaningful only
|
||||
when the variant is RFC_4122)
|
||||
"""
|
||||
|
||||
def __init__(self, hex=None, bytes=None, bytes_le=None, fields=None,
|
||||
int=None, version=None):
|
||||
r"""Create a UUID from either a string of 32 hexadecimal digits,
|
||||
a string of 16 bytes as the 'bytes' argument, a string of 16 bytes
|
||||
in little-endian order as the 'bytes_le' argument, a tuple of six
|
||||
integers (32-bit time_low, 16-bit time_mid, 16-bit time_hi_version,
|
||||
8-bit clock_seq_hi_variant, 8-bit clock_seq_low, 48-bit node) as
|
||||
the 'fields' argument, or a single 128-bit integer as the 'int'
|
||||
argument. When a string of hex digits is given, curly braces,
|
||||
hyphens, and a URN prefix are all optional. For example, these
|
||||
expressions all yield the same UUID:
|
||||
|
||||
UUID('{12345678-1234-5678-1234-567812345678}')
|
||||
UUID('12345678123456781234567812345678')
|
||||
UUID('urn:uuid:12345678-1234-5678-1234-567812345678')
|
||||
UUID(bytes='\x12\x34\x56\x78'*4)
|
||||
UUID(bytes_le='\x78\x56\x34\x12\x34\x12\x78\x56' +
|
||||
'\x12\x34\x56\x78\x12\x34\x56\x78')
|
||||
UUID(fields=(0x12345678, 0x1234, 0x5678, 0x12, 0x34, 0x567812345678))
|
||||
UUID(int=0x12345678123456781234567812345678)
|
||||
|
||||
Exactly one of 'hex', 'bytes', 'bytes_le', 'fields', or 'int' must
|
||||
be given. The 'version' argument is optional; if given, the resulting
|
||||
UUID will have its variant and version set according to RFC 4122,
|
||||
overriding the given 'hex', 'bytes', 'bytes_le', 'fields', or 'int'.
|
||||
"""
|
||||
|
||||
if [hex, bytes, bytes_le, fields, int].count(None) != 4:
|
||||
raise TypeError('need one of hex, bytes, bytes_le, fields, or int')
|
||||
if hex is not None:
|
||||
hex = hex.replace('urn:', '').replace('uuid:', '')
|
||||
hex = hex.strip('{}').replace('-', '')
|
||||
if len(hex) != 32:
|
||||
raise ValueError('badly formed hexadecimal UUID string')
|
||||
int = long(hex, 16)
|
||||
if bytes_le is not None:
|
||||
if len(bytes_le) != 16:
|
||||
raise ValueError('bytes_le is not a 16-char string')
|
||||
bytes = (bytes_le[3] + bytes_le[2] + bytes_le[1] + bytes_le[0] +
|
||||
bytes_le[5] + bytes_le[4] + bytes_le[7] + bytes_le[6] +
|
||||
bytes_le[8:])
|
||||
if bytes is not None:
|
||||
if len(bytes) != 16:
|
||||
raise ValueError('bytes is not a 16-char string')
|
||||
int = long(('%02x' * 16) % tuple(map(ord, bytes)), 16)
|
||||
if fields is not None:
|
||||
if len(fields) != 6:
|
||||
raise ValueError('fields is not a 6-tuple')
|
||||
(time_low, time_mid, time_hi_version,
|
||||
clock_seq_hi_variant, clock_seq_low, node) = fields
|
||||
if not 0 <= time_low < 1 << 32L:
|
||||
raise ValueError('field 1 out of range (need a 32-bit value)')
|
||||
if not 0 <= time_mid < 1 << 16L:
|
||||
raise ValueError('field 2 out of range (need a 16-bit value)')
|
||||
if not 0 <= time_hi_version < 1 << 16L:
|
||||
raise ValueError('field 3 out of range (need a 16-bit value)')
|
||||
if not 0 <= clock_seq_hi_variant < 1 << 8L:
|
||||
raise ValueError('field 4 out of range (need an 8-bit value)')
|
||||
if not 0 <= clock_seq_low < 1 << 8L:
|
||||
raise ValueError('field 5 out of range (need an 8-bit value)')
|
||||
if not 0 <= node < 1 << 48L:
|
||||
raise ValueError('field 6 out of range (need a 48-bit value)')
|
||||
clock_seq = (clock_seq_hi_variant << 8L) | clock_seq_low
|
||||
int = ((time_low << 96L) | (time_mid << 80L) |
|
||||
(time_hi_version << 64L) | (clock_seq << 48L) | node)
|
||||
if int is not None:
|
||||
if not 0 <= int < 1 << 128L:
|
||||
raise ValueError('int is out of range (need a 128-bit value)')
|
||||
if version is not None:
|
||||
if not 1 <= version <= 5:
|
||||
raise ValueError('illegal version number')
|
||||
# Set the variant to RFC 4122.
|
||||
int &= ~(0xc000 << 48L)
|
||||
int |= 0x8000 << 48L
|
||||
# Set the version number.
|
||||
int &= ~(0xf000 << 64L)
|
||||
int |= version << 76L
|
||||
self.__dict__['int'] = int
|
||||
|
||||
def __cmp__(self, other):
|
||||
if isinstance(other, UUID):
|
||||
return cmp(self.int, other.int)
|
||||
return NotImplemented
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.int)
|
||||
|
||||
def __int__(self):
|
||||
return self.int
|
||||
|
||||
def __repr__(self):
|
||||
return 'UUID(%r)' % str(self)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
raise TypeError('UUID objects are immutable')
|
||||
|
||||
def __str__(self):
|
||||
hex = '%032x' % self.int
|
||||
return '%s-%s-%s-%s-%s' % (
|
||||
hex[:8], hex[8:12], hex[12:16], hex[16:20], hex[20:])
|
||||
|
||||
def get_bytes(self):
|
||||
bytes = ''
|
||||
for shift in range(0, 128, 8):
|
||||
bytes = chr((self.int >> shift) & 0xff) + bytes
|
||||
return bytes
|
||||
|
||||
bytes = property(get_bytes)
|
||||
|
||||
def get_bytes_le(self):
|
||||
bytes = self.bytes
|
||||
return (bytes[3] + bytes[2] + bytes[1] + bytes[0] +
|
||||
bytes[5] + bytes[4] + bytes[7] + bytes[6] + bytes[8:])
|
||||
|
||||
bytes_le = property(get_bytes_le)
|
||||
|
||||
def get_fields(self):
|
||||
return (self.time_low, self.time_mid, self.time_hi_version,
|
||||
self.clock_seq_hi_variant, self.clock_seq_low, self.node)
|
||||
|
||||
fields = property(get_fields)
|
||||
|
||||
def get_time_low(self):
|
||||
return self.int >> 96L
|
||||
|
||||
time_low = property(get_time_low)
|
||||
|
||||
def get_time_mid(self):
|
||||
return (self.int >> 80L) & 0xffff
|
||||
|
||||
time_mid = property(get_time_mid)
|
||||
|
||||
def get_time_hi_version(self):
|
||||
return (self.int >> 64L) & 0xffff
|
||||
|
||||
time_hi_version = property(get_time_hi_version)
|
||||
|
||||
def get_clock_seq_hi_variant(self):
|
||||
return (self.int >> 56L) & 0xff
|
||||
|
||||
clock_seq_hi_variant = property(get_clock_seq_hi_variant)
|
||||
|
||||
def get_clock_seq_low(self):
|
||||
return (self.int >> 48L) & 0xff
|
||||
|
||||
clock_seq_low = property(get_clock_seq_low)
|
||||
|
||||
def get_time(self):
|
||||
return (((self.time_hi_version & 0x0fffL) << 48L) |
|
||||
(self.time_mid << 32L) | self.time_low)
|
||||
|
||||
time = property(get_time)
|
||||
|
||||
def get_clock_seq(self):
|
||||
return (((self.clock_seq_hi_variant & 0x3fL) << 8L) |
|
||||
self.clock_seq_low)
|
||||
|
||||
clock_seq = property(get_clock_seq)
|
||||
|
||||
def get_node(self):
|
||||
return self.int & 0xffffffffffff
|
||||
|
||||
node = property(get_node)
|
||||
|
||||
def get_hex(self):
|
||||
return '%032x' % self.int
|
||||
|
||||
hex = property(get_hex)
|
||||
|
||||
def get_urn(self):
|
||||
return 'urn:uuid:' + str(self)
|
||||
|
||||
urn = property(get_urn)
|
||||
|
||||
def get_variant(self):
|
||||
if not self.int & (0x8000 << 48L):
|
||||
return RESERVED_NCS
|
||||
elif not self.int & (0x4000 << 48L):
|
||||
return RFC_4122
|
||||
elif not self.int & (0x2000 << 48L):
|
||||
return RESERVED_MICROSOFT
|
||||
else:
|
||||
return RESERVED_FUTURE
|
||||
|
||||
variant = property(get_variant)
|
||||
|
||||
def get_version(self):
|
||||
# The version bits are only meaningful for RFC 4122 UUIDs.
|
||||
if self.variant == RFC_4122:
|
||||
return int((self.int >> 76L) & 0xf)
|
||||
|
||||
version = property(get_version)
|
||||
|
||||
|
||||
def _find_mac(command, args, hw_identifiers, get_index):
|
||||
import os
|
||||
for dir in ['', '/sbin/', '/usr/sbin']:
|
||||
executable = os.path.join(dir, command)
|
||||
if not os.path.exists(executable):
|
||||
continue
|
||||
|
||||
try:
|
||||
# LC_ALL to get English output, 2>/dev/null to
|
||||
# prevent output on stderr
|
||||
cmd = 'LC_ALL=C %s %s 2>/dev/null' % (executable, args)
|
||||
pipe = os.popen(cmd)
|
||||
except IOError:
|
||||
continue
|
||||
|
||||
for line in pipe:
|
||||
words = line.lower().split()
|
||||
for i in range(len(words)):
|
||||
if words[i] in hw_identifiers:
|
||||
return int(words[get_index(i)].replace(':', ''), 16)
|
||||
return None
|
||||
|
||||
|
||||
def _ifconfig_getnode():
|
||||
"""Get the hardware address on Unix by running ifconfig."""
|
||||
|
||||
# This works on Linux ('' or '-a'), Tru64 ('-av'), but not all Unixes.
|
||||
for args in ('', '-a', '-av'):
|
||||
mac = _find_mac('ifconfig', args, ['hwaddr', 'ether'], lambda i: i + 1)
|
||||
if mac:
|
||||
return mac
|
||||
|
||||
import socket
|
||||
ip_addr = socket.gethostbyname(socket.gethostname())
|
||||
|
||||
# Try getting the MAC addr from arp based on our IP address (Solaris).
|
||||
mac = _find_mac('arp', '-an', [ip_addr], lambda i: -1)
|
||||
if mac:
|
||||
return mac
|
||||
|
||||
# This might work on HP-UX.
|
||||
mac = _find_mac('lanscan', '-ai', ['lan0'], lambda i: 0)
|
||||
if mac:
|
||||
return mac
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def _ipconfig_getnode():
|
||||
"""Get the hardware address on Windows by running ipconfig.exe."""
|
||||
import os
|
||||
import re
|
||||
dirs = ['', r'c:\windows\system32', r'c:\winnt\system32']
|
||||
try:
|
||||
import ctypes
|
||||
buffer = ctypes.create_string_buffer(300)
|
||||
ctypes.windll.kernel32.GetSystemDirectoryA(buffer, 300)
|
||||
dirs.insert(0, buffer.value.decode('mbcs'))
|
||||
except:
|
||||
pass
|
||||
for dir in dirs:
|
||||
try:
|
||||
pipe = os.popen(os.path.join(dir, 'ipconfig') + ' /all')
|
||||
except IOError:
|
||||
continue
|
||||
for line in pipe:
|
||||
value = line.split(':')[-1].strip().lower()
|
||||
if re.match('([0-9a-f][0-9a-f]-){5}[0-9a-f][0-9a-f]', value):
|
||||
return int(value.replace('-', ''), 16)
|
||||
|
||||
|
||||
def _netbios_getnode():
|
||||
"""Get the hardware address on Windows using NetBIOS calls.
|
||||
See http://support.microsoft.com/kb/118623 for details."""
|
||||
import win32wnet
|
||||
import netbios
|
||||
ncb = netbios.NCB()
|
||||
ncb.Command = netbios.NCBENUM
|
||||
ncb.Buffer = adapters = netbios.LANA_ENUM()
|
||||
adapters._pack()
|
||||
if win32wnet.Netbios(ncb) != 0:
|
||||
return
|
||||
adapters._unpack()
|
||||
for i in range(adapters.length):
|
||||
ncb.Reset()
|
||||
ncb.Command = netbios.NCBRESET
|
||||
ncb.Lana_num = ord(adapters.lana[i])
|
||||
if win32wnet.Netbios(ncb) != 0:
|
||||
continue
|
||||
ncb.Reset()
|
||||
ncb.Command = netbios.NCBASTAT
|
||||
ncb.Lana_num = ord(adapters.lana[i])
|
||||
ncb.Callname = '*'.ljust(16)
|
||||
ncb.Buffer = status = netbios.ADAPTER_STATUS()
|
||||
if win32wnet.Netbios(ncb) != 0:
|
||||
continue
|
||||
status._unpack()
|
||||
bytes = map(ord, status.adapter_address)
|
||||
return ((bytes[0] << 40L) + (bytes[1] << 32L) + (bytes[2] << 24L) +
|
||||
(bytes[3] << 16L) + (bytes[4] << 8L) + bytes[5])
|
||||
|
||||
# Thanks to Thomas Heller for ctypes and for his help with its use here.
|
||||
|
||||
# If ctypes is available, use it to find system routines for UUID generation.
|
||||
_uuid_generate_random = _uuid_generate_time = _UuidCreate = None
|
||||
try:
|
||||
import ctypes
|
||||
import ctypes.util
|
||||
_buffer = ctypes.create_string_buffer(16)
|
||||
|
||||
# The uuid_generate_* routines are provided by libuuid on at least
|
||||
# Linux and FreeBSD, and provided by libc on Mac OS X.
|
||||
for libname in ['uuid', 'c']:
|
||||
try:
|
||||
lib = ctypes.CDLL(ctypes.util.find_library(libname))
|
||||
except:
|
||||
continue
|
||||
if hasattr(lib, 'uuid_generate_random'):
|
||||
_uuid_generate_random = lib.uuid_generate_random
|
||||
if hasattr(lib, 'uuid_generate_time'):
|
||||
_uuid_generate_time = lib.uuid_generate_time
|
||||
|
||||
# On Windows prior to 2000, UuidCreate gives a UUID containing the
|
||||
# hardware address. On Windows 2000 and later, UuidCreate makes a
|
||||
# random UUID and UuidCreateSequential gives a UUID containing the
|
||||
# hardware address. These routines are provided by the RPC runtime.
|
||||
# NOTE: at least on Tim's WinXP Pro SP2 desktop box, while the last
|
||||
# 6 bytes returned by UuidCreateSequential are fixed, they don't appear
|
||||
# to bear any relationship to the MAC address of any network device
|
||||
# on the box.
|
||||
try:
|
||||
lib = ctypes.windll.rpcrt4
|
||||
except:
|
||||
lib = None
|
||||
_UuidCreate = getattr(lib, 'UuidCreateSequential',
|
||||
getattr(lib, 'UuidCreate', None))
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def _unixdll_getnode():
|
||||
"""Get the hardware address on Unix using ctypes."""
|
||||
_uuid_generate_time(_buffer)
|
||||
return UUID(bytes=_buffer.raw).node
|
||||
|
||||
|
||||
def _windll_getnode():
|
||||
"""Get the hardware address on Windows using ctypes."""
|
||||
if _UuidCreate(_buffer) == 0:
|
||||
return UUID(bytes=_buffer.raw).node
|
||||
|
||||
|
||||
def _random_getnode():
|
||||
"""Get a random node ID, with eighth bit set as suggested by RFC 4122."""
|
||||
import random
|
||||
return random.randrange(0, 1 << 48L) | 0x010000000000L
|
||||
|
||||
_node = None
|
||||
|
||||
|
||||
def getnode():
|
||||
"""Get the hardware address as a 48-bit positive integer.
|
||||
|
||||
The first time this runs, it may launch a separate program, which could
|
||||
be quite slow. If all attempts to obtain the hardware address fail, we
|
||||
choose a random 48-bit number with its eighth bit set to 1 as recommended
|
||||
in RFC 4122.
|
||||
"""
|
||||
|
||||
global _node
|
||||
if _node is not None:
|
||||
return _node
|
||||
|
||||
import sys
|
||||
if sys.platform == 'win32':
|
||||
getters = [_windll_getnode, _netbios_getnode, _ipconfig_getnode]
|
||||
else:
|
||||
getters = [_unixdll_getnode, _ifconfig_getnode]
|
||||
|
||||
for getter in getters + [_random_getnode]:
|
||||
try:
|
||||
_node = getter()
|
||||
except:
|
||||
continue
|
||||
if _node is not None:
|
||||
return _node
|
||||
|
||||
_last_timestamp = None
|
||||
|
||||
|
||||
def uuid1(node=None, clock_seq=None):
|
||||
"""Generate a UUID from a host ID, sequence number, and the current time.
|
||||
If 'node' is not given, getnode() is used to obtain the hardware
|
||||
address. If 'clock_seq' is given, it is used as the sequence number;
|
||||
otherwise a random 14-bit sequence number is chosen."""
|
||||
|
||||
# When the system provides a version-1 UUID generator, use it (but don't
|
||||
# use UuidCreate here because its UUIDs don't conform to RFC 4122).
|
||||
if _uuid_generate_time and node is clock_seq is None:
|
||||
_uuid_generate_time(_buffer)
|
||||
return UUID(bytes=_buffer.raw)
|
||||
|
||||
global _last_timestamp
|
||||
import time
|
||||
nanoseconds = int(time.time() * 1e9)
|
||||
# 0x01b21dd213814000 is the number of 100-ns intervals between the
|
||||
# UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00.
|
||||
timestamp = int(nanoseconds / 100) + 0x01b21dd213814000L
|
||||
if timestamp <= _last_timestamp:
|
||||
timestamp = _last_timestamp + 1
|
||||
_last_timestamp = timestamp
|
||||
if clock_seq is None:
|
||||
import random
|
||||
clock_seq = random.randrange(1 << 14L) # instead of stable storage
|
||||
time_low = timestamp & 0xffffffffL
|
||||
time_mid = (timestamp >> 32L) & 0xffffL
|
||||
time_hi_version = (timestamp >> 48L) & 0x0fffL
|
||||
clock_seq_low = clock_seq & 0xffL
|
||||
clock_seq_hi_variant = (clock_seq >> 8L) & 0x3fL
|
||||
if node is None:
|
||||
node = getnode()
|
||||
return UUID(fields=(time_low, time_mid, time_hi_version,
|
||||
clock_seq_hi_variant, clock_seq_low, node), version=1)
|
||||
|
||||
|
||||
def uuid3(namespace, name):
|
||||
"""Generate a UUID from the MD5 hash of a namespace UUID and a name."""
|
||||
try:
|
||||
from hashlib import md5
|
||||
except ImportError:
|
||||
from md5 import md5
|
||||
hash = md5(namespace.bytes + name).digest()
|
||||
return UUID(bytes=hash[:16], version=3)
|
||||
|
||||
|
||||
def uuid4():
|
||||
"""Generate a random UUID."""
|
||||
|
||||
# When the system provides a version-4 UUID generator, use it.
|
||||
if _uuid_generate_random:
|
||||
_uuid_generate_random(_buffer)
|
||||
return UUID(bytes=_buffer.raw)
|
||||
|
||||
# Otherwise, get randomness from urandom or the 'random' module.
|
||||
try:
|
||||
import os
|
||||
return UUID(bytes=os.urandom(16), version=4)
|
||||
except:
|
||||
import random
|
||||
bytes = [chr(random.randrange(256)) for i in range(16)]
|
||||
return UUID(bytes=bytes, version=4)
|
||||
|
||||
|
||||
def uuid5(namespace, name):
|
||||
"""Generate a UUID from the SHA-1 hash of a namespace UUID and a name."""
|
||||
try:
|
||||
from hashlib import sha1 as sha
|
||||
except ImportError:
|
||||
from sha import sha
|
||||
hash = sha(namespace.bytes + name).digest()
|
||||
return UUID(bytes=hash[:16], version=5)
|
||||
|
||||
# The following standard UUIDs are for use with uuid3() or uuid5().
|
||||
|
||||
NAMESPACE_DNS = UUID('6ba7b810-9dad-11d1-80b4-00c04fd430c8')
|
||||
NAMESPACE_URL = UUID('6ba7b811-9dad-11d1-80b4-00c04fd430c8')
|
||||
NAMESPACE_OID = UUID('6ba7b812-9dad-11d1-80b4-00c04fd430c8')
|
||||
NAMESPACE_X500 = UUID('6ba7b814-9dad-11d1-80b4-00c04fd430c8')
|
|
@ -0,0 +1,21 @@
|
|||
Metadata-Version: 1.0
|
||||
Name: django-extensions
|
||||
Version: 0.7.1
|
||||
Summary: Extensions for Django
|
||||
Home-page: http://github.com/django-extensions/django-extensions
|
||||
Author: Bas van Oostveen
|
||||
Author-email: v.oostveen@gmail.com
|
||||
License: New BSD License
|
||||
Description: django-extensions bundles several useful
|
||||
additions for Django projects. See the project page for more information:
|
||||
http://github.com/django-extensions/django-extensions
|
||||
Platform: any
|
||||
Classifier: Development Status :: 4 - Beta
|
||||
Classifier: Development Status :: 5 - Production/Stable
|
||||
Classifier: Environment :: Web Environment
|
||||
Classifier: Framework :: Django
|
||||
Classifier: Intended Audience :: Developers
|
||||
Classifier: License :: OSI Approved :: BSD License
|
||||
Classifier: Operating System :: OS Independent
|
||||
Classifier: Programming Language :: Python
|
||||
Classifier: Topic :: Utilities
|
|
@ -0,0 +1,108 @@
|
|||
MANIFEST.in
|
||||
setup.cfg
|
||||
setup.py
|
||||
django_extensions/__init__.py
|
||||
django_extensions/models.py
|
||||
django_extensions/settings.py
|
||||
django_extensions.egg-info/PKG-INFO
|
||||
django_extensions.egg-info/SOURCES.txt
|
||||
django_extensions.egg-info/dependency_links.txt
|
||||
django_extensions.egg-info/top_level.txt
|
||||
django_extensions/admin/__init__.py
|
||||
django_extensions/admin/widgets.py
|
||||
django_extensions/conf/app_template/__init__.py.tmpl
|
||||
django_extensions/conf/app_template/forms.py.tmpl
|
||||
django_extensions/conf/app_template/models.py.tmpl
|
||||
django_extensions/conf/app_template/urls.py.tmpl
|
||||
django_extensions/conf/app_template/views.py.tmpl
|
||||
django_extensions/conf/command_template/management/__init__.py.tmpl
|
||||
django_extensions/conf/command_template/management/commands/__init__.py.tmpl
|
||||
django_extensions/conf/command_template/management/commands/sample.py.tmpl
|
||||
django_extensions/conf/jobs_template/jobs/__init__.py.tmpl
|
||||
django_extensions/conf/jobs_template/jobs/sample.py.tmpl
|
||||
django_extensions/conf/jobs_template/jobs/daily/__init__.py.tmpl
|
||||
django_extensions/conf/jobs_template/jobs/hourly/__init__.py.tmpl
|
||||
django_extensions/conf/jobs_template/jobs/monthly/__init__.py.tmpl
|
||||
django_extensions/conf/jobs_template/jobs/weekly/__init__.py.tmpl
|
||||
django_extensions/conf/jobs_template/jobs/yearly/__init__.py.tmpl
|
||||
django_extensions/db/__init__.py
|
||||
django_extensions/db/models.py
|
||||
django_extensions/db/fields/__init__.py
|
||||
django_extensions/db/fields/encrypted.py
|
||||
django_extensions/db/fields/json.py
|
||||
django_extensions/jobs/__init__.py
|
||||
django_extensions/jobs/daily/__init__.py
|
||||
django_extensions/jobs/daily/cache_cleanup.py
|
||||
django_extensions/jobs/daily/daily_cleanup.py
|
||||
django_extensions/jobs/hourly/__init__.py
|
||||
django_extensions/jobs/monthly/__init__.py
|
||||
django_extensions/jobs/weekly/__init__.py
|
||||
django_extensions/jobs/yearly/__init__.py
|
||||
django_extensions/management/__init__.py
|
||||
django_extensions/management/color.py
|
||||
django_extensions/management/jobs.py
|
||||
django_extensions/management/modelviz.py
|
||||
django_extensions/management/signals.py
|
||||
django_extensions/management/utils.py
|
||||
django_extensions/management/commands/__init__.py
|
||||
django_extensions/management/commands/clean_pyc.py
|
||||
django_extensions/management/commands/compile_pyc.py
|
||||
django_extensions/management/commands/create_app.py
|
||||
django_extensions/management/commands/create_command.py
|
||||
django_extensions/management/commands/create_jobs.py
|
||||
django_extensions/management/commands/describe_form.py
|
||||
django_extensions/management/commands/dumpscript.py
|
||||
django_extensions/management/commands/export_emails.py
|
||||
django_extensions/management/commands/find_template.py
|
||||
django_extensions/management/commands/generate_secret_key.py
|
||||
django_extensions/management/commands/graph_models.py
|
||||
django_extensions/management/commands/mail_debug.py
|
||||
django_extensions/management/commands/notes.py
|
||||
django_extensions/management/commands/passwd.py
|
||||
django_extensions/management/commands/print_user_for_session.py
|
||||
django_extensions/management/commands/reset_db.py
|
||||
django_extensions/management/commands/runjob.py
|
||||
django_extensions/management/commands/runjobs.py
|
||||
django_extensions/management/commands/runprofileserver.py
|
||||
django_extensions/management/commands/runscript.py
|
||||
django_extensions/management/commands/runserver_plus.py
|
||||
django_extensions/management/commands/set_fake_emails.py
|
||||
django_extensions/management/commands/set_fake_passwords.py
|
||||
django_extensions/management/commands/shell_plus.py
|
||||
django_extensions/management/commands/show_templatetags.py
|
||||
django_extensions/management/commands/show_urls.py
|
||||
django_extensions/management/commands/sqlcreate.py
|
||||
django_extensions/management/commands/sqldiff.py
|
||||
django_extensions/management/commands/sync_media_s3.py
|
||||
django_extensions/management/commands/syncdata.py
|
||||
django_extensions/management/commands/unreferenced_files.py
|
||||
django_extensions/management/commands/update_permissions.py
|
||||
django_extensions/media/django_extensions/css/jquery.autocomplete.css
|
||||
django_extensions/media/django_extensions/img/indicator.gif
|
||||
django_extensions/media/django_extensions/js/jquery.ajaxQueue.js
|
||||
django_extensions/media/django_extensions/js/jquery.autocomplete.js
|
||||
django_extensions/media/django_extensions/js/jquery.bgiframe.min.js
|
||||
django_extensions/media/django_extensions/js/jquery.js
|
||||
django_extensions/mongodb/__init__.py
|
||||
django_extensions/mongodb/models.py
|
||||
django_extensions/mongodb/fields/__init__.py
|
||||
django_extensions/mongodb/fields/encrypted.py
|
||||
django_extensions/mongodb/fields/json.py
|
||||
django_extensions/templates/django_extensions/graph_models/body.html
|
||||
django_extensions/templates/django_extensions/graph_models/head.html
|
||||
django_extensions/templates/django_extensions/graph_models/rel.html
|
||||
django_extensions/templates/django_extensions/graph_models/tail.html
|
||||
django_extensions/templates/django_extensions/widgets/foreignkey_searchinput.html
|
||||
django_extensions/templatetags/__init__.py
|
||||
django_extensions/templatetags/highlighting.py
|
||||
django_extensions/templatetags/syntax_color.py
|
||||
django_extensions/templatetags/truncate_letters.py
|
||||
django_extensions/templatetags/widont.py
|
||||
django_extensions/tests/__init__.py
|
||||
django_extensions/tests/encrypted_fields.py
|
||||
django_extensions/tests/models.py
|
||||
django_extensions/tests/utils.py
|
||||
django_extensions/utils/__init__.py
|
||||
django_extensions/utils/dia2django.py
|
||||
django_extensions/utils/text.py
|
||||
django_extensions/utils/uuid.py
|
|
@ -0,0 +1 @@
|
|||
|
|
@ -0,0 +1 @@
|
|||
django_extensions
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
VERSION = (0, 7, 1)
|
||||
|
||||
# Dynamically calculate the version based on VERSION tuple
|
||||
if len(VERSION) > 2 and VERSION[2] is not None:
|
||||
if isinstance(VERSION[2], int):
|
||||
str_version = "%s.%s.%s" % VERSION[:3]
|
||||
else:
|
||||
str_version = "%s.%s_%s" % VERSION[:3]
|
||||
else:
|
||||
str_version = "%s.%s" % VERSION[:2]
|
||||
|
||||
__version__ = str_version
|
|
@ -0,0 +1,147 @@
|
|||
#
|
||||
# Autocomplete feature for admin panel
|
||||
#
|
||||
# Most of the code has been written by Jannis Leidel and was updated a bit
|
||||
# for django_extensions.
|
||||
# http://jannisleidel.com/2008/11/autocomplete-form-widget-foreignkey-model-fields/
|
||||
#
|
||||
# to_string_function, Satchmo adaptation and some comments added by emes
|
||||
# (Michal Salaban)
|
||||
#
|
||||
|
||||
import operator
|
||||
from django.http import HttpResponse, HttpResponseNotFound
|
||||
from django.db import models
|
||||
from django.db.models.query import QuerySet
|
||||
from django.utils.encoding import smart_str
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.text import get_text_list
|
||||
try:
|
||||
from functools import update_wrapper
|
||||
except ImportError:
|
||||
from django.utils.functional import update_wrapper
|
||||
|
||||
from django_extensions.admin.widgets import ForeignKeySearchInput
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
if 'reversion' in settings.INSTALLED_APPS:
|
||||
from reversion.admin import VersionAdmin as ModelAdmin
|
||||
else:
|
||||
from django.contrib.admin import ModelAdmin
|
||||
|
||||
|
||||
class ForeignKeyAutocompleteAdmin(ModelAdmin):
|
||||
"""Admin class for models using the autocomplete feature.
|
||||
|
||||
There are two additional fields:
|
||||
- related_search_fields: defines fields of managed model that
|
||||
have to be represented by autocomplete input, together with
|
||||
a list of target model fields that are searched for
|
||||
input string, e.g.:
|
||||
|
||||
related_search_fields = {
|
||||
'author': ('first_name', 'email'),
|
||||
}
|
||||
|
||||
- related_string_functions: contains optional functions which
|
||||
take target model instance as only argument and return string
|
||||
representation. By default __unicode__() method of target
|
||||
object is used.
|
||||
"""
|
||||
|
||||
related_search_fields = {}
|
||||
related_string_functions = {}
|
||||
|
||||
def get_urls(self):
|
||||
from django.conf.urls.defaults import patterns, url
|
||||
|
||||
def wrap(view):
|
||||
def wrapper(*args, **kwargs):
|
||||
return self.admin_site.admin_view(view)(*args, **kwargs)
|
||||
return update_wrapper(wrapper, view)
|
||||
|
||||
info = self.model._meta.app_label, self.model._meta.module_name
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'foreignkey_autocomplete/$',
|
||||
wrap(self.foreignkey_autocomplete),
|
||||
name='%s_%s_autocomplete' % info),
|
||||
) + super(ForeignKeyAutocompleteAdmin, self).get_urls()
|
||||
return urlpatterns
|
||||
|
||||
def foreignkey_autocomplete(self, request):
|
||||
"""
|
||||
Searches in the fields of the given related model and returns the
|
||||
result as a simple string to be used by the jQuery Autocomplete plugin
|
||||
"""
|
||||
query = request.GET.get('q', None)
|
||||
app_label = request.GET.get('app_label', None)
|
||||
model_name = request.GET.get('model_name', None)
|
||||
search_fields = request.GET.get('search_fields', None)
|
||||
object_pk = request.GET.get('object_pk', None)
|
||||
try:
|
||||
to_string_function = self.related_string_functions[model_name]
|
||||
except KeyError:
|
||||
to_string_function = lambda x: x.__unicode__()
|
||||
if search_fields and app_label and model_name and (query or object_pk):
|
||||
def construct_search(field_name):
|
||||
# use different lookup methods depending on the notation
|
||||
if field_name.startswith('^'):
|
||||
return "%s__istartswith" % field_name[1:]
|
||||
elif field_name.startswith('='):
|
||||
return "%s__iexact" % field_name[1:]
|
||||
elif field_name.startswith('@'):
|
||||
return "%s__search" % field_name[1:]
|
||||
else:
|
||||
return "%s__icontains" % field_name
|
||||
model = models.get_model(app_label, model_name)
|
||||
queryset = model._default_manager.all()
|
||||
data = ''
|
||||
if query:
|
||||
for bit in query.split():
|
||||
or_queries = [models.Q(**{construct_search(
|
||||
smart_str(field_name)): smart_str(bit)})
|
||||
for field_name in search_fields.split(',')]
|
||||
other_qs = QuerySet(model)
|
||||
other_qs.dup_select_related(queryset)
|
||||
other_qs = other_qs.filter(reduce(operator.or_, or_queries))
|
||||
queryset = queryset & other_qs
|
||||
data = ''.join([u'%s|%s\n' % (
|
||||
to_string_function(f), f.pk) for f in queryset])
|
||||
elif object_pk:
|
||||
try:
|
||||
obj = queryset.get(pk=object_pk)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
data = to_string_function(obj)
|
||||
return HttpResponse(data)
|
||||
return HttpResponseNotFound()
|
||||
|
||||
def get_help_text(self, field_name, model_name):
|
||||
searchable_fields = self.related_search_fields.get(field_name, None)
|
||||
if searchable_fields:
|
||||
help_kwargs = {
|
||||
'model_name': model_name,
|
||||
'field_list': get_text_list(searchable_fields, _('and')),
|
||||
}
|
||||
return _('Use the left field to do %(model_name)s lookups in the fields %(field_list)s.') % help_kwargs
|
||||
return ''
|
||||
|
||||
def formfield_for_dbfield(self, db_field, **kwargs):
|
||||
"""
|
||||
Overrides the default widget for Foreignkey fields if they are
|
||||
specified in the related_search_fields class attribute.
|
||||
"""
|
||||
if (isinstance(db_field, models.ForeignKey) and
|
||||
db_field.name in self.related_search_fields):
|
||||
model_name = db_field.rel.to._meta.object_name
|
||||
help_text = self.get_help_text(db_field.name, model_name)
|
||||
if kwargs.get('help_text'):
|
||||
help_text = u'%s %s' % (kwargs['help_text'], help_text)
|
||||
kwargs['widget'] = ForeignKeySearchInput(db_field.rel,
|
||||
self.related_search_fields[db_field.name])
|
||||
kwargs['help_text'] = help_text
|
||||
return super(ForeignKeyAutocompleteAdmin,
|
||||
self).formfield_for_dbfield(db_field, **kwargs)
|
|
@ -0,0 +1,77 @@
|
|||
from django import forms
|
||||
from django.conf import settings
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.text import truncate_words
|
||||
from django.template.loader import render_to_string
|
||||
from django.contrib.admin.widgets import ForeignKeyRawIdWidget
|
||||
|
||||
|
||||
class ForeignKeySearchInput(ForeignKeyRawIdWidget):
|
||||
"""
|
||||
A Widget for displaying ForeignKeys in an autocomplete search input
|
||||
instead in a <select> box.
|
||||
"""
|
||||
# Set in subclass to render the widget with a different template
|
||||
widget_template = None
|
||||
# Set this to the patch of the search view
|
||||
search_path = '../foreignkey_autocomplete/'
|
||||
|
||||
class Media:
|
||||
css = {
|
||||
'all': ('django_extensions/css/jquery.autocomplete.css',)
|
||||
}
|
||||
js = (
|
||||
'django_extensions/js/jquery.js',
|
||||
'django_extensions/js/jquery.bgiframe.min.js',
|
||||
'django_extensions/js/jquery.ajaxQueue.js',
|
||||
'django_extensions/js/jquery.autocomplete.js',
|
||||
)
|
||||
|
||||
def label_for_value(self, value):
|
||||
key = self.rel.get_related_field().name
|
||||
obj = self.rel.to._default_manager.get(**{key: value})
|
||||
return truncate_words(obj, 14)
|
||||
|
||||
def __init__(self, rel, search_fields, attrs=None):
|
||||
self.search_fields = search_fields
|
||||
super(ForeignKeySearchInput, self).__init__(rel, attrs)
|
||||
|
||||
def render(self, name, value, attrs=None):
|
||||
if attrs is None:
|
||||
attrs = {}
|
||||
output = [super(ForeignKeySearchInput, self).render(name, value, attrs)]
|
||||
opts = self.rel.to._meta
|
||||
app_label = opts.app_label
|
||||
model_name = opts.object_name.lower()
|
||||
related_url = '../../../%s/%s/' % (app_label, model_name)
|
||||
params = self.url_parameters()
|
||||
if params:
|
||||
url = '?' + '&'.join(['%s=%s' % (k, v) for k, v in params.items()])
|
||||
else:
|
||||
url = ''
|
||||
if not 'class' in attrs:
|
||||
attrs['class'] = 'vForeignKeyRawIdAdminField'
|
||||
# Call the TextInput render method directly to have more control
|
||||
output = [forms.TextInput.render(self, name, value, attrs)]
|
||||
if value:
|
||||
label = self.label_for_value(value)
|
||||
else:
|
||||
label = u''
|
||||
context = {
|
||||
'url': url,
|
||||
'related_url': related_url,
|
||||
'admin_media_prefix': settings.ADMIN_MEDIA_PREFIX,
|
||||
'search_path': self.search_path,
|
||||
'search_fields': ','.join(self.search_fields),
|
||||
'model_name': model_name,
|
||||
'app_label': app_label,
|
||||
'label': label,
|
||||
'name': name,
|
||||
}
|
||||
output.append(render_to_string(self.widget_template or (
|
||||
'django_extensions/widgets/%s/%s/foreignkey_searchinput.html' % (app_label, model_name),
|
||||
'django_extensions/widgets/%s/foreignkey_searchinput.html' % app_label,
|
||||
'django_extensions/widgets/foreignkey_searchinput.html',
|
||||
), context))
|
||||
output.reverse()
|
||||
return mark_safe(u''.join(output))
|
|
@ -0,0 +1,3 @@
|
|||
from django import forms
|
||||
|
||||
# place form definition here
|
|
@ -0,0 +1,3 @@
|
|||
from django.db import models
|
||||
|
||||
# Create your models here.
|
|
@ -0,0 +1,3 @@
|
|||
from django.conf.urls.defaults import *
|
||||
|
||||
# place app url patterns here
|
|
@ -0,0 +1 @@
|
|||
# Create your views here.
|
|
@ -0,0 +1,7 @@
|
|||
from django.core.management.base import {{ base_command }}
|
||||
|
||||
class Command({{ base_command }}):
|
||||
help = "My shiny new management command."
|
||||
|
||||
def {{ handle_method }}:
|
||||
raise NotImplementedError()
|
|
@ -0,0 +1,8 @@
|
|||
from django_extensions.management.jobs import BaseJob
|
||||
|
||||
class Job(BaseJob):
|
||||
help = "My sample job."
|
||||
|
||||
def execute(self):
|
||||
# executing empty sample job
|
||||
pass
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue