~ubuntuone-hackers/django-configglue/trunk

« back to all changes in this revision

Viewing changes to django_configglue/schema.py

changed default behaviour to automatically create a schema if the requested one is not found

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# Copyright 2010 Canonical Ltd.  This software is licensed under the
2
2
# GNU Lesser General Public License version 3 (see the file LICENSE).
 
3
import inspect
3
4
import logging
4
5
 
5
6
from configglue.pyschema.schema import (
13
14
    TupleOption,
14
15
)
15
16
from django import get_version
 
17
from django.conf import global_settings
 
18
from django.conf.project_template import settings as project_settings
16
19
 
17
20
 
18
21
# As in django.conf.global_settings:
32
35
        return result
33
36
 
34
37
 
 
38
def derivate_django_schema(schema, exclude=None):
 
39
    """Return a modified version of a schema.
 
40
 
 
41
    The source schema *must* have a 'version' attribute and
 
42
    a 'django' section.
 
43
 
 
44
    The resulting schema is almost a copy of the original one, except
 
45
    for excluded options in the 'django' section.
 
46
    """
 
47
    if not exclude:
 
48
        return schema
 
49
 
 
50
    # create the schema class
 
51
    cls = type(schema.__name__, (schema,), {'version': schema.version})
 
52
    # include all non-excluded options
 
53
    options = {}
 
54
    for option in schema().django.options():
 
55
        if option.name in exclude:
 
56
            continue
 
57
        options[option.name] = option
 
58
    # create the 'django' section
 
59
    django_section = type('django', (Section,), options)
 
60
    setattr(cls, 'django', django_section)
 
61
    return cls
 
62
 
 
63
 
35
64
class BaseDjangoSchema(Schema):
36
65
    version = '1.0.2 final'
37
66
 
53
82
            help="Whether to use the 'Etag' header. This saves bandwidth but "
54
83
                 "slows down performance.")
55
84
 
56
 
        admins = ListOption(item=TupleOption(2), default=[],
 
85
        admins = ListOption(item=TupleOption(length=2), default=[],
57
86
            help="People who get code error notifications. In the format "
58
87
                 "(('Full Name', 'email@domain.com'), "
59
88
                 "('Full Name', 'anotheremail@domain.com'))")
136
165
        locale_paths = ListOption(item=StringOption())
137
166
        language_cookie_name = StringOption(default='django_language')
138
167
 
139
 
        managers = ListOption(item=TupleOption(2), default=[],
 
168
        managers = ListOption(item=TupleOption(length=2), default=[],
140
169
            help="Not-necessarily-technical managers of the site. They get broken "
141
170
                 "link notifications and other various e-mails")
142
171
 
464
493
        ####################
465
494
 
466
495
        site_id = IntOption(default=1)
467
 
        root_urlconf = StringOption(default='urls')
468
 
 
469
 
 
470
 
class Django112Schema(BaseDjangoSchema):
 
496
        root_urlconf = StringOption(default='{{ project_name }}.urls')
 
497
 
 
498
 
 
499
Django112Base = derivate_django_schema(
 
500
    BaseDjangoSchema, exclude=['jing_path'])
 
501
 
 
502
 
 
503
class Django112Schema(Django112Base):
471
504
    version = '1.1.2'
472
505
 
473
506
    # sections
474
 
    class django(BaseDjangoSchema.django):
 
507
    class django(Django112Base.django):
475
508
 
476
509
        ################
477
510
        # CORE         #
483
516
            help="Languages we provide translations for, out of the box. "
484
517
                 "The language name should be the utf-8 encoded local name "
485
518
                 "for the language",
486
 
            default = [
 
519
            default=[
487
520
                ('ar', gettext_noop('Arabic')),
488
521
                ('bg', gettext_noop('Bulgarian')),
489
522
                ('bn', gettext_noop('Bengali')),
558
591
            help="Languages we provide translations for, out of the box. "
559
592
                 "The language name should be the utf-8 encoded local name "
560
593
                 "for the language",
561
 
            default = [
 
594
            default=[
562
595
                ('ar', gettext_noop('Arabic')),
563
596
                ('bg', gettext_noop('Bulgarian')),
564
597
                ('bn', gettext_noop('Bengali')),
637
670
                'host': StringOption(),
638
671
                'port': StringOption(),
639
672
            }),
 
673
            default={
 
674
                'default': {
 
675
                    'ENGINE': 'django.db.backends.',
 
676
                    'NAME': '',
 
677
                    'USER': '',
 
678
                    'PASSWORD': '',
 
679
                    'HOST': '',
 
680
                    'PORT': '',
 
681
                }
 
682
            }
640
683
        )
641
684
        database_routers = ListOption(
642
685
            item=StringOption(),
651
694
 
652
695
        installed_apps = ListOption(item=StringOption(),
653
696
            help="List of strings representing installed apps",
654
 
            default = [
 
697
            default=[
655
698
                'django.contrib.auth',
656
699
                'django.contrib.contenttypes',
657
700
                'django.contrib.sessions',
662
705
        template_loaders = ListOption(item=StringOption(),
663
706
            help="List of callables that know how to import templates from "
664
707
                 "various sources",
665
 
            default = [
 
708
            default=[
666
709
                'django.template.loaders.filesystem.Loader',
667
710
                'django.template.loaders.app_directories.Loader',
668
711
            ])
673
716
                 "context. Each one should be a callable that takes the request "
674
717
                 "object as its only parameter and returns a dictionary to add to "
675
718
                 "the context",
676
 
            default = [
 
719
            default=[
677
720
                'django.contrib.auth.context_processors.auth',
678
721
                'django.core.context_processors.debug',
679
722
                'django.core.context_processors.i18n',
696
739
        date_input_formats = ListOption(
697
740
            item=StringOption(),
698
741
            default=[
699
 
                '%%Y-%%m-%%d', '%%m/%%d/%%Y', '%%m/%%d/%%y', # '2006-10-25', '10/25/2006', '10/25/06'
700
 
                '%%b %%d %%Y', '%%b %%d, %%Y',               # 'Oct 25 2006', 'Oct 25, 2006'
701
 
                '%%d %%b %%Y', '%%d %%b, %%Y',               # '25 Oct 2006', '25 Oct, 2006'
702
 
                '%%B %%d %%Y', '%%B %%d, %%Y',               # 'October 25 2006', 'October 25, 2006'
703
 
                '%%d %%B %%Y', '%%d %%B, %%Y',               # '25 October 2006', '25 October, 2006'
 
742
                '%Y-%m-%d', '%m/%d/%Y', '%m/%d/%y', # '2006-10-25', '10/25/2006', '10/25/06'
 
743
                '%b %d %Y', '%b %d, %Y',               # 'Oct 25 2006', 'Oct 25, 2006'
 
744
                '%d %b %Y', '%d %b, %Y',               # '25 Oct 2006', '25 Oct, 2006'
 
745
                '%B %d %Y', '%B %d, %Y',               # 'October 25 2006', 'October 25, 2006'
 
746
                '%d %B %Y', '%d %B, %Y',               # '25 October 2006', '25 October, 2006'
704
747
            ],
705
748
            help="Default formats to be used when parsing dates from input "
706
749
                "boxes, in order")
707
750
        time_input_formats = ListOption(
708
751
            item=StringOption(),
709
752
            default=[
710
 
                '%%H:%%M:%%S',     # '14:30:59'
711
 
                '%%H:%%M',         # '14:30'
 
753
                '%H:%M:%S',     # '14:30:59'
 
754
                '%H:%M',         # '14:30'
712
755
            ],
713
756
            help="Default formats to be used when parsing times from input "
714
757
                "boxes, in order")
715
758
        datetime_input_formats = ListOption(
716
759
            item=StringOption(),
717
760
            default=[
718
 
                '%%Y-%%m-%%d %%H:%%M:%%S',     # '2006-10-25 14:30:59'
719
 
                '%%Y-%%m-%%d %%H:%%M',         # '2006-10-25 14:30'
720
 
                '%%Y-%%m-%%d',                 # '2006-10-25'
721
 
                '%%m/%%d/%%Y %%H:%%M:%%S',     # '10/25/2006 14:30:59'
722
 
                '%%m/%%d/%%Y %%H:%%M',         # '10/25/2006 14:30'
723
 
                '%%m/%%d/%%Y',                 # '10/25/2006'
724
 
                '%%m/%%d/%%y %%H:%%M:%%S',     # '10/25/06 14:30:59'
725
 
                '%%m/%%d/%%y %%H:%%M',         # '10/25/06 14:30'
726
 
                '%%m/%%d/%%y',                 # '10/25/06'
 
761
                '%Y-%m-%d %H:%M:%S',      # '2006-10-25 14:30:59'
 
762
                '%Y-%m-%d %H:%M',         # '2006-10-25 14:30'
 
763
                '%Y-%m-%d',               # '2006-10-25'
 
764
                '%m/%d/%Y %H:%M:%S',      # '10/25/2006 14:30:59'
 
765
                '%m/%d/%Y %H:%M',         # '10/25/2006 14:30'
 
766
                '%m/%d/%Y',               # '10/25/2006'
 
767
                '%m/%d/%y %H:%M:%S',      # '10/25/06 14:30:59'
 
768
                '%m/%d/%y %H:%M',         # '10/25/06 14:30'
 
769
                '%m/%d/%y',               # '10/25/06'
727
770
            ],
728
771
            help="Default formats to be used when parsing dates and times "
729
772
                "from input boxes, in order")
757
800
                 "request phase, these middleware classes will be applied in the "
758
801
                 "order given, and in the response phase the middleware will be "
759
802
                 "applied in reverse order",
760
 
            default = [
 
803
            default=[
761
804
                'django.middleware.common.CommonMiddleware',
762
805
                'django.contrib.sessions.middleware.SessionMiddleware',
763
806
                'django.middleware.csrf.CsrfViewMiddleware',
798
841
            help="The name of the class to use to run the test suite")
799
842
 
800
843
 
801
 
class Django13Schema(Django125Schema):
 
844
Django13Base = derivate_django_schema(
 
845
    Django125Schema, exclude=['cache_backend'])
 
846
 
 
847
 
 
848
class Django13Schema(Django13Base):
802
849
    version = '1.3'
803
850
 
804
851
    # sections
805
 
    class django(Django125Schema.django):
 
852
    class django(Django13Base.django):
806
853
 
807
854
        ################
808
855
        # CORE         #
814
861
            help="Languages we provide translations for, out of the box. "
815
862
                 "The language name should be the utf-8 encoded local name "
816
863
                 "for the language",
817
 
            default = [
 
864
            default=[
818
865
                ('ar', gettext_noop('Arabic')),
819
866
                ('az', gettext_noop('Azerbaijani')),
820
867
                ('bg', gettext_noop('Bulgarian')),
884
931
                ('zh-tw', gettext_noop('Traditional Chinese')),
885
932
            ])
886
933
 
 
934
        installed_apps = ListOption(item=StringOption(),
 
935
            help="List of strings representing installed apps",
 
936
            default=[
 
937
                'django.contrib.auth',
 
938
                'django.contrib.contenttypes',
 
939
                'django.contrib.sessions',
 
940
                'django.contrib.sites',
 
941
                'django.contrib.messages',
 
942
                'django.contrib.staticfiles',
 
943
            ])
 
944
 
887
945
        template_context_processors = ListOption(
888
946
            item=StringOption(),
889
947
            help="List of processors used by RequestContext to populate the "
890
948
                 "context. Each one should be a callable that takes the request "
891
949
                 "object as its only parameter and returns a dictionary to add to "
892
950
                 "the context",
893
 
            default = [
 
951
            default=[
894
952
                'django.contrib.auth.context_processors.auth',
895
953
                'django.core.context_processors.debug',
896
954
                'django.core.context_processors.i18n',
904
962
            help='Absolute path to the directory that holds static files.')
905
963
 
906
964
        static_url = StringOption(
907
 
            null=True, default=None,
 
965
            null=True, default='/static/',
908
966
            help='URL that handles the static files served from STATIC_ROOT.')
909
967
 
910
968
        ############
919
977
        # CACHE #
920
978
        #########
921
979
 
922
 
        # remove obsoleted setting
923
 
        cache_backend = None
924
 
 
925
980
        caches = DictOption()
926
981
        cache_middleware_alias = StringOption(default='default')
927
982
 
1010
1065
        self._schemas[version] = schema_cls
1011
1066
 
1012
1067
    def get(self, version, strict=True):
1013
 
        if version not in self._schemas:
1014
 
            msg = "No schema registered for version %r" % version
1015
 
            if strict:
1016
 
                raise ValueError(msg)
 
1068
        if version in self._schemas:
 
1069
            return self._schemas[version]
 
1070
 
 
1071
        msg = "No schema registered for version %r" % version
 
1072
        if strict:
 
1073
            raise ValueError(msg)
 
1074
        else:
 
1075
            logging.warn(msg)
 
1076
 
 
1077
        logging.warn("Dynamically creating schema for version %r" % version)
 
1078
        schema = self.build(version)
 
1079
        return schema
 
1080
 
 
1081
    def build(self, version_string=None, options=None):
 
1082
        if version_string is None:
 
1083
            version_string = get_version()
 
1084
        if options is None:
 
1085
            options = dict([(name.lower(), value) for (name, value) in
 
1086
                inspect.getmembers(global_settings) if name.isupper()])
 
1087
            project_options = dict([(name.lower(), value) for (name, value) in
 
1088
                inspect.getmembers(project_settings) if name.isupper()])
 
1089
            options.update(project_options)
 
1090
 
 
1091
        class DjangoSchema(Schema):
 
1092
            version = version_string
 
1093
 
 
1094
            class django(Section):
 
1095
                pass
 
1096
 
 
1097
        def get_option_type(name, value):
 
1098
            type_mappings = {
 
1099
                int: IntOption,
 
1100
                bool: BoolOption,
 
1101
                list: ListOption,
 
1102
                tuple: TupleOption,
 
1103
                dict: DictOption,
 
1104
                str: StringOption,
 
1105
                unicode: StringOption,
 
1106
            }
 
1107
            if value is None:
 
1108
                return StringOption(name=name, default=value, null=True)
1017
1109
            else:
1018
 
                logging.warn(msg)
1019
 
 
1020
 
            versions = sorted(self._schemas.keys())
1021
 
            if not versions:
1022
 
                raise ValueError("No schemas registered")
1023
 
 
1024
 
            last = versions[0]
1025
 
            for v in sorted(self._schemas.keys()):
1026
 
                if version < v:
1027
 
                    break
1028
 
                last = v
1029
 
            version = last
1030
 
            logging.warn("Falling back to schema for version %r" % version)
1031
 
        return self._schemas[version]
 
1110
                option_type = type_mappings[type(value)]
 
1111
                kwargs = {'name': name, 'default': value}
 
1112
 
 
1113
                if option_type in (DictOption, ListOption):
 
1114
                    # get inner item type
 
1115
                    if option_type == DictOption:
 
1116
                        items = value.values()
 
1117
                    else:
 
1118
                        items = value
 
1119
 
 
1120
                    item_type = None
 
1121
                    if items:
 
1122
                        item_type = type_mappings.get(type(items[0]), None)
 
1123
                        # make sure all items have a consistent type
 
1124
                        for item in items:
 
1125
                            current_item_type = type_mappings.get(
 
1126
                                type(item), None)
 
1127
                            if current_item_type != item_type:
 
1128
                                item_type = None
 
1129
                                # mismatching types found. fallback to default
 
1130
                                # item type
 
1131
                                break
 
1132
                    if item_type is not None:
 
1133
                        kwargs['item'] = item_type()
 
1134
 
 
1135
                return option_type(**kwargs)
 
1136
 
 
1137
        for name, value in options.items():
 
1138
            if name == '__CONFIGGLUE_PARSER__':
 
1139
                continue
 
1140
            option = get_option_type(name, value)
 
1141
            setattr(DjangoSchema.django, name, option)
 
1142
 
 
1143
        # register schema for it to be available during next query
 
1144
        self.register(DjangoSchema, version_string)
 
1145
        return DjangoSchema
1032
1146
 
1033
1147
 
1034
1148
schemas = DjangoSchemaFactory()