1. David Wolever
  2. urlqueue

Commits

David Wolever  committed 28377cf

Adding a basic QueuedUrl model.

  • Participants
  • Parent commits 30870b5
  • Branches default

Comments (0)

Files changed (79)

File db.sqlite3

  • Ignore whitespace
Empty file added.

File libs/django_extensions/__init__.py

View file
  • Ignore whitespace
+
+VERSION = (0, "4.1")
+
+# Dynamically calculate the version based on VERSION tuple
+if len(VERSION)>2 and VERSION[2] is not None:
+    str_version = "%s.%s_%s" % VERSION[:3]
+else:
+    str_version = "%s.%s" % VERSION[:2]
+
+__version__ = str_version

File libs/django_extensions/admin/__init__.py

View file
  • Ignore whitespace
+#
+#    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.contrib import admin
+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
+
+from django_extensions.admin.widgets import ForeignKeySearchInput
+
+class ForeignKeyAutocompleteAdmin(admin.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 __call__(self, request, url):
+        if url is None:
+            pass
+        elif url == 'foreignkey_autocomplete':
+            return self.foreignkey_autocomplete(request)
+        return super(ForeignKeyAutocompleteAdmin, self).__call__(request, url)
+
+    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)

File libs/django_extensions/admin/widgets.py

View file
  • Ignore whitespace
+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 = '?' + '&amp;'.join(['%s=%s' % (k, v) for k, v in params.items()])
+        else:
+            url = ''
+        if not attrs.has_key('class'):
+            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))

File libs/django_extensions/conf/app_template/__init__.py.tmpl

  • Ignore whitespace
Empty file added.

File libs/django_extensions/conf/app_template/forms.py.tmpl

View file
  • Ignore whitespace
+from django import forms
+
+# place form definition here

File libs/django_extensions/conf/app_template/models.py.tmpl

View file
  • Ignore whitespace
+from django.db import models
+
+# Create your models here.

File libs/django_extensions/conf/app_template/urls.py.tmpl

View file
  • Ignore whitespace
+from django.conf.urls.defaults import *
+
+# place app url patterns here

File libs/django_extensions/conf/app_template/views.py.tmpl

View file
  • Ignore whitespace
+# Create your views here.

File libs/django_extensions/conf/command_template/management/__init__.py.tmpl

  • Ignore whitespace
Empty file added.

File libs/django_extensions/conf/command_template/management/commands/__init__.py.tmpl

  • Ignore whitespace
Empty file added.

File libs/django_extensions/conf/command_template/management/commands/sample.py.tmpl

View file
  • Ignore whitespace
+from django.core.management.base import {{ base_command }}
+
+class Command({{ base_command }}):
+    help = "My shiny new management command."
+
+    def {{ handle_method }}:
+        raise NotImplementedError()

File libs/django_extensions/conf/jobs_template/jobs/__init__.py.tmpl

  • Ignore whitespace
Empty file added.

File libs/django_extensions/conf/jobs_template/jobs/daily/__init__.py.tmpl

  • Ignore whitespace
Empty file added.

File libs/django_extensions/conf/jobs_template/jobs/hourly/__init__.py.tmpl

  • Ignore whitespace
Empty file added.

File libs/django_extensions/conf/jobs_template/jobs/monthly/__init__.py.tmpl

  • Ignore whitespace
Empty file added.

File libs/django_extensions/conf/jobs_template/jobs/sample.py.tmpl

View file
  • Ignore whitespace
+from django_extensions.management.jobs import BaseJob
+
+class Job(BaseJob):
+    help = "My sample job."
+
+    def execute(self):
+        # executing empty sample job
+        pass

File libs/django_extensions/conf/jobs_template/jobs/weekly/__init__.py.tmpl

  • Ignore whitespace
Empty file added.

File libs/django_extensions/db/__init__.py

  • Ignore whitespace
Empty file added.

File libs/django_extensions/db/fields/__init__.py

View file
  • Ignore whitespace
+"""
+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)
+        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('editable', False)
+        kwargs.setdefault('blank', True)
+        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(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 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

File libs/django_extensions/db/models.py

View file
  • Ignore whitespace
+"""
+Django Extensions abstract base model classes.
+"""
+
+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:
+        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

File libs/django_extensions/jobs/__init__.py

  • Ignore whitespace
Empty file added.

File libs/django_extensions/jobs/daily/__init__.py

  • Ignore whitespace
Empty file added.

File libs/django_extensions/jobs/daily/cache_cleanup.py

View file
  • Ignore whitespace
+"""
+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
+        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()" % \
+                (backend.quote_name(table_name), backend.quote_name('expires')))
+            transaction.commit_unless_managed()

File libs/django_extensions/jobs/daily/daily_cleanup.py

View file
  • Ignore whitespace
+"""
+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")

File libs/django_extensions/jobs/hourly/__init__.py

  • Ignore whitespace
Empty file added.

File libs/django_extensions/jobs/monthly/__init__.py

  • Ignore whitespace
Empty file added.

File libs/django_extensions/jobs/weekly/__init__.py

  • Ignore whitespace
Empty file added.

File libs/django_extensions/management/__init__.py

  • Ignore whitespace
Empty file added.

File libs/django_extensions/management/color.py

View file
  • Ignore whitespace
+"""
+Sets up the terminal color scheme.
+"""
+
+from django.core.management import color
+from django.utils import termcolors
+
+def color_style():
+    style = color.color_style()
+    style.URL = termcolors.make_style(fg='green', opts=('bold',))
+    style.MODULE = termcolors.make_style(fg='yellow')
+    style.MODULE_NAME = termcolors.make_style(opts=('bold',))
+    return style

File libs/django_extensions/management/commands/__init__.py

  • Ignore whitespace
Empty file added.

File libs/django_extensions/management/commands/clean_pyc.py

View file
  • Ignore whitespace
+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"),
+    )

File libs/django_extensions/management/commands/compile_pyc.py

View file
  • Ignore whitespace
+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"),
+    )

File libs/django_extensions/management/commands/create_app.py

View file
  • Ignore whitespace
+import os
+import re
+import django_extensions
+from django.core.management.base import CommandError, LabelCommand, _make_writeable
+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 app to be created'),
+    )
+    
+    help = ("Creates a Django application directory structure based on the specified template directory.")
+    args = "[appname]"
+    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)
+                
+        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)
+        try:
+            os.makedirs(app_dir)
+        except OSError, e:
+            raise CommandError(e)
+        
+        copy_template(app_template, app_dir, project_name, 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
+    
+    # walks the template structure and copies it
+    for d, subdirs, files in os.walk(app_template):
+        relative_dir = d[len(app_template)+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('app_name', app_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('{{ app_name }}', app_name).replace('{{ project_name }}', project_name))
+            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))

File libs/django_extensions/management/commands/create_command.py

View file
  • Ignore whitespace
+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))

File libs/django_extensions/management/commands/create_jobs.py

View file
  • Ignore whitespace
+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))

File libs/django_extensions/management/commands/describe_form.py

View file
  • Ignore whitespace
+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) }

File libs/django_extensions/management/commands/dumpscript.py

View file
  • Ignore whitespace
+#!/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.objects.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. """

File libs/django_extensions/management/commands/export_emails.py

View file
  • Ignore whitespace
+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_OUTPUT("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))

File libs/django_extensions/management/commands/generate_secret_key.py

View file
  • Ignore whitespace
+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)])

File libs/django_extensions/management/commands/graph_models.py

View file
  • Ignore whitespace
+from django.core.management.base import BaseCommand, CommandError
+from optparse import make_option
+from django_extensions.management.modelviz import generate_dot
+