Django

Code

Changeset 740

Show
Ignore:
Timestamp:
09/30/05 07:16:43 (3 years ago)
Author:
rjwittams
Message:

Initial checkin of new admin branch. Ticket #535. I've tried to cover the changes below, but may have forgotten some.

M django/conf/urls/admin.py
Modified to allow running the old and new code in parallel. Simply add _old on the end of a change or add form to check against the behaviour of the old admin.
eg http://myadmin/auth/users/1/ -> http://myadmin/auth/users/1_old/

A django/conf/admin_templates/admin_change_form.html
A django/conf/admin_templates/admin_edit_inline_stacked.html
A django/conf/admin_templates/admin_field.html
A django/conf/admin_templates/admin_field_widget.html
A django/conf/admin_templates/admin_edit_inline_tabular.html

These are templates extracted from the admin code that are now used to render the views.

M django/conf/admin_media/js/urlify.js

Change to dashes rather than underscores in slug fields.

M django/core/formfields.py

All of the data conversion from POST to something fields can understand now takes place here.

M django/core/meta/init.py

Added InlineRelatedObject? and added manipulator methods for data flattening.
Also includes a fix to ordering descending select= fields.

M django/core/meta/fields.py

Data flattening pushed down into fields.

M django/core/defaulttags.py

Added "include" tag, which is like ssi parsed, but uses normal template resolution rather than absolute paths.

M django/core/validators.py

Allow dashes in slugfields.

A django/templatetags/admin_modify.py

A new set of template tags to provide functionality for the admin.

M django/views/admin/main.py

New view functions for add and change. New helper objects for the admin templates to access ( BoundField?, AdminFieldSet?)

M tests/runtests.py

Show the details of an error rather than assuming the existance of a database.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/branches/new-admin/django/conf/admin_media/js/urlify.js

    r96 r740  
    1111    s = s.replace(/[^\w\s]/g, '');   // remove unneeded chars 
    1212    s = s.replace(/^\s+|\s+$/g, ''); // trim leading/trailing spaces 
    13     s = s.replace(/\s+/g, '_');      // convert spaces to underscores 
     13    s = s.replace(/\s+/g, '-');      // convert spaces to dashes 
    1414    s = s.toLowerCase();             // convert to lowercase 
    1515    return s.substring(0, num_chars);// trim to first num_chars chars 
  • django/branches/new-admin/django/conf/urls/admin.py

    r469 r740  
    4949urlpatterns += ( 
    5050    # Metasystem admin pages 
     51    ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/add_old/$', 'django.views.admin.main.add_stage'), 
     52    ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/(?P<object_id>.+)_old/$', 'django.views.admin.main.change_stage'), 
    5153    ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/$', 'django.views.admin.main.change_list'), 
    52     ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/add/$', 'django.views.admin.main.add_stage'), 
     54    ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/add/$', 'django.views.admin.main.add_stage_new'), 
    5355    ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/jsvalidation/$', 'django.views.admin.jsvalidation.jsvalidation'), 
    5456    ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/(?P<object_id>.+)/history/$', 'django.views.admin.main.history'), 
    5557    ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/(?P<object_id>.+)/delete/$', 'django.views.admin.main.delete_stage'), 
    56     ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/(?P<object_id>.+)/$', 'django.views.admin.main.change_stage'), 
     58    ('^(?P<app_label>[^/]+)/(?P<module_name>[^/]+)/(?P<object_id>.+)/$', 'django.views.admin.main.change_stage_new'), 
    5759) 
    5860urlpatterns = patterns('', *urlpatterns) 
  • django/branches/new-admin/django/core/defaulttags.py

    r574 r740  
    33import sys 
    44import template 
     5import template_loader 
    56 
    67class CommentNode(template.Node): 
     
    224225        return output 
    225226 
     227class IncludeNode(template.Node): 
     228    def __init__(self, template_path): 
     229        self.template_path_var = template_path_var 
     230 
     231    def render(self, context): 
     232         try: 
     233             template_path = template.resolve(self.template_path_var, context) 
     234             t = template_loader.get_template(template_path) 
     235             return t.render(context) 
     236         except: 
     237             return '' # Fail silently for invalid included templates. 
     238 
     239 
    226240class LoadNode(template.Node): 
    227241    def __init__(self, taglib): 
     
    601615    return SsiNode(bits[1], parsed) 
    602616 
     617def do_include(parser, token): 
     618    """ 
     619    Loads a template using standard resolution mechanisms, and renders it in the current context.      
     620    """ 
     621    bits = token.contents.split() 
     622    parsed = False 
     623    if len(bits) != 2: 
     624        raise template.TemplateSyntaxError, "'include' tag takes one argument: the path to the template to be included" 
     625    return IncludeNode(bits[1]) 
     626 
    603627def do_load(parser, token): 
    604628    """ 
     
    756780template.register_tag('if', do_if) 
    757781template.register_tag('ifchanged', do_ifchanged) 
     782template.register_tag('include', do_include) 
    758783template.register_tag('regroup', do_regroup) 
    759784template.register_tag('ssi', do_ssi) 
  • django/branches/new-admin/django/core/formfields.py

    r702 r740  
    2323            if field.field_name == field_name: 
    2424                return field 
    25         raise KeyError, "Field %s not found" % field_name 
     25        raise KeyError, "Field %s not found\n%s" % (field_name, repr(self.fields))  
    2626 
    2727    def __delitem__(self, field_name): 
     
    8888        expected to deal with invalid input. 
    8989        """ 
    90         for field in self.fields: 
    91             if new_data.has_key(field.field_name): 
    92                 new_data.setlist(field.field_name, 
    93                     [field.__class__.html2python(data) for data in new_data.getlist(field.field_name)]) 
    94             else: 
    95                 try: 
    96                     # individual fields deal with None values themselves 
    97                     new_data.setlist(field.field_name, [field.__class__.html2python(None)]) 
    98                 except EmptyValue: 
    99                     new_data.setlist(field.field_name, []) 
     90        """ 
     91        for field in self.fields: 
     92        """ 
     93 
     94        for field in self.fields: 
     95            field.convert_post_data(new_data) 
    10096 
    10197class FormWrapper: 
     
    105101    prepopulated data and validation error messages to the formfield objects. 
    106102    """ 
    107     def __init__(self, manipulator, data, error_dict): 
     103    def __init__(self, manipulator, data, error_dict, edit_inline = False): 
    108104        self.manipulator, self.data = manipulator, data 
    109105        self.error_dict = error_dict 
     106        self._inline_collections = None 
     107        self.edit_inline = edit_inline 
    110108 
    111109    def __repr__(self): 
    112         return repr(self.data
     110        return repr(self.__dict__
    113111 
    114112    def __getitem__(self, key): 
    115113        for field in self.manipulator.fields: 
    116114            if field.field_name == key: 
    117                 if hasattr(field, 'requires_data_list') and hasattr(self.data, 'getlist'): 
    118                     data = self.data.getlist(field.field_name) 
    119                 else: 
    120                     data = self.data.get(field.field_name, None) 
    121                 if data is None: 
    122                     data = '' 
    123                 return FormFieldWrapper(field, data, self.error_dict.get(field.field_name, [])) 
     115                data = field.extract_data(self.data) 
     116                return FormFieldWrapper(field, data, self.error_dict.get(field.field_name, [])) 
     117        if self.edit_inline: 
     118            self.fill_inline_collections()  
     119            for inline_collection in self._inline_collections: 
     120                if inline_collection.name == key: 
     121                    return inline_collection 
     122 
    124123        raise KeyError 
     124 
     125    def fill_inline_collections(self):  
     126        if not self._inline_collections: 
     127            ic = [] 
     128            related_objects = self.manipulator.get_inline_related_objects_wrapped() 
     129            for rel_obj in related_objects: 
     130                data = rel_obj.extract_data(self.data) 
     131                inline_collection = InlineObjectCollection(self.manipulator, rel_obj, data, self.error_dict) 
     132                ic.append(inline_collection) 
     133            self._inline_collections = ic 
     134 
     135 
    125136 
    126137    def has_errors(self): 
     
    136147        "Renders the field" 
    137148        return str(self.formfield.render(self.data)) 
     149             
    138150 
    139151    def __repr__(self): 
     
    156168            return '' 
    157169 
     170    def get_id(self): 
     171        return  self.formfield.get_id() 
     172 
    158173class FormFieldCollection(FormFieldWrapper): 
    159174    "A utility class that gives the template access to a dict of FormFieldWrappers" 
     
    175190        errors = [] 
    176191        for field in self.formfield_dict.values(): 
    177             errors.extend(field.errors()) 
     192            if(hasattr(field, 'errors') ): 
     193                errors.extend(field.errors()) 
    178194        return errors 
     195 
     196    def has_errors(self): 
     197        return bool(len(self.errors()))  
     198         
     199    def html_combined_error_list(self): 
     200        return ''.join( [ field.html_error_list() for field in self.formfield_dict.values() if hasattr(field, 'errors')]) 
     201 
     202class InlineObjectCollection: 
     203    "An object that acts like a list of form field collections."   
     204    def __init__(self, parent_manipulator,  rel_obj, data, errors): 
     205        self.parent_manipulator = parent_manipulator  
     206        self.rel_obj = rel_obj 
     207        self.data = data 
     208        self.errors = errors 
     209        self._collections = None    
     210        self.name = rel_obj.name 
     211  
     212    def __len__(self): 
     213        self.fill()  
     214        return self._collections.__len__() 
     215     
     216    def __getitem__(self, k): 
     217        self.fill() 
     218        return self._collections.__getitem__(k) 
     219 
     220    def __setitem__(self, k, v): 
     221        self.fill() 
     222        return self._collections.__setitem__(k,v) 
     223 
     224    def __delitem__(self, k): 
     225        self.fill() 
     226        return self._collections.__delitem__(k) 
     227 
     228    def __iter__(self): 
     229        self.fill() 
     230        return self._collections.__iter__() 
     231 
     232    def fill(self): 
     233        if self._collections: 
     234            return  
     235        else: 
     236            var_name = self.rel_obj.opts.object_name.lower() 
     237            wrapper = [] 
     238            orig = hasattr(self.parent_manipulator, 'original_object') and self.parent_manipulator.original_object  or None  
     239            orig_list = self.rel_obj.get_list(orig) 
     240            for i, instance in enumerate(orig_list): 
     241                collection = {'original': instance } 
     242                for f in self.rel_obj.editable_fields(): 
     243                        for field_name in f.get_manipulator_field_names(''): 
     244                            full_field_name = '%s.%d.%s' % (var_name, i, field_name) 
     245                            field = self.parent_manipulator[full_field_name] 
     246                            data = field.extract_data(self.data) 
     247                            errors = self.errors.get(full_field_name, []) 
     248#                           if(errors):raise full_field_name + " " + repr(errors) 
     249                            collection[field_name] = FormFieldWrapper(field, data, errors) 
     250                wrapper.append(FormFieldCollection(collection)) 
     251            self._collections = wrapper  
    179252 
    180253class FormField: 
     
    210283        raise NotImplementedError 
    211284 
     285    def get_member_name(self): 
     286        if hasattr(self, 'member_name'): 
     287            return self.member_name 
     288        else: 
     289            return self.field_name 
     290 
     291    def extract_data(self, data_dict): 
     292        if hasattr(self, 'requires_data_list') and hasattr(data_dict, 'getlist'): 
     293            data = data_dict.getlist(self.get_member_name()) 
     294        else: 
     295            data = data_dict.get(self.get_member_name(), None) 
     296        if data is None: 
     297            data = '' 
     298        self.data_dict = data_dict   
     299        return data 
     300 
     301    def convert_post_data(self, new_data): 
     302        name = self.get_member_name() 
     303        if new_data.has_key(name): 
     304            d = new_data.getlist(name) 
     305            #del new_data[self.field_name] 
     306            new_data.setlist(name, 
     307                    [self.__class__.html2python(data)  
     308                     for data in d]) 
     309        else: 
     310            try: 
     311               # individual fields deal with None values themselves 
     312               new_data.setlist(name, [self.__class__.html2python(None)]) 
     313            except EmptyValue: 
     314               new_data.setlist(name, []) 
     315 
     316    def get_id(self): 
     317        return  FORM_FIELD_ID_PREFIX + self.field_name   
    212318#################### 
    213319# GENERIC WIDGETS  # 
     
    238344            data = data.encode('utf-8') 
    239345        return '<input type="text" id="%s" class="v%s%s" name="%s" size="%s" value="%s" %s/>' % \ 
    240             (FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__, self.is_required and ' required' or '', 
     346            (self.get_id(), self.__class__.__name__, self.is_required and ' required' or '', 
    241347            self.field_name, self.length, escape(data), maxlength) 
    242348 
     
    249355        # value is always blank because we never want to redisplay it 
    250356        return '<input type="password" id="%s" class="v%s%s" name="%s" value="" />' % \ 
    251             (FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__, self.is_required and ' required' or '', 
     357            (self.get_id(), self.__class__.__name__, self.is_required and ' required' or '', 
    252358            self.field_name) 
    253359 
     
    267373            data = data.encode('utf-8') 
    268374        return '<textarea id="%s" class="v%s%s" name="%s" rows="%s" cols="%s">%s</textarea>' % \ 
    269             (FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__, self.is_required and ' required' or '', 
     375            (self.get_id(), self.__class__.__name__, self.is_required and ' required' or '', 
    270376            self.field_name, self.rows, self.cols, escape(data)) 
    271377 
     
    277383    def render(self, data): 
    278384        return '<input type="hidden" id="%s" name="%s" value="%s" />' % \ 
    279             (FORM_FIELD_ID_PREFIX + self.field_name, self.field_name, escape(data)) 
     385            (self.get_id(), self.field_name, escape(data)) 
    280386 
    281387class CheckboxField(FormField): 
     
    290396            checked_html = ' checked="checked"' 
    291397        return '<input type="checkbox" id="%s" class="v%s" name="%s"%s />' % \ 
    292             (FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__, 
     398            (self.get_id(), self.__class__.__name__, 
    293399            self.field_name, checked_html) 
    294400 
     
    300406    html2python = staticmethod(html2python) 
    301407 
     408 
    302409class SelectField(FormField): 
    303     def __init__(self, field_name, choices=[], size=1, is_required=False, validator_list=[]): 
     410    def __init__(self, field_name, choices=[], size=1, is_required=False, validator_list=[], member_name=None): 
    304411        self.field_name = field_name 
    305412        # choices is a list of (value, human-readable key) tuples because order matters 
    306413        self.choices, self.size, self.is_required = choices, size, is_required 
    307414        self.validator_list = [self.isValidChoice] + validator_list 
    308  
    309     def render(self, data): 
     415        if member_name != None: 
     416            self.member_name = member_name 
     417 
     418    def render(self, data): 
     419        str_data = str(data) # normalize to string 
    310420        output = ['<select id="%s" class="v%s%s" name="%s" size="%s">' % \ 
    311             (FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__, self.is_required and ' required' or '', 
    312             self.field_name, self.size)] 
    313         str_data = str(data) # normalize to string 
     421            (self.get_id(), self.__class__.__name__,  
     422             self.is_required and ' required' or '', self.field_name, self.size)] 
    314423        for value, display_name in self.choices: 
    315424            selected_html = '' 
     
    335444 
    336445class RadioSelectField(FormField): 
    337     def __init__(self, field_name, choices=[], ul_class='', is_required=False, validator_list=[]): 
     446    def __init__(self, field_name, choices=[], ul_class='', is_required=False, validator_list=[], member_name=None): 
    338447        self.field_name = field_name 
    339448        # choices is a list of (value, human-readable key) tuples because order matters 
     
    341450        self.validator_list = [self.isValidChoice] + validator_list 
    342451        self.ul_class = ul_class 
     452        if member_name != None: 
     453            self.member_name = member_name 
    343454 
    344455    def render(self, data): 
     
    383494                'name': display_name, 
    384495                'field': '<input type="radio" id="%s" name="%s" value="%s"%s/>' % \ 
    385                     (FORM_FIELD_ID_PREFIX + self.field_name + '_' + str(i), self.field_name, value, selected_html), 
     496                    (self.get_id() + '_' + str(i), self.field_name, value, selected_html), 
    386497                'label': '<label for="%s">%s</label>' % \ 
    387                     (FORM_FIELD_ID_PREFIX + self.field_name + '_' + str(i), display_name), 
     498                    (self.get_id() + '_' + str(i), display_name), 
    388499            }) 
    389500        return RadioFieldRenderer(datalist, self.ul_class) 
     
    415526    def render(self, data): 
    416527        output = ['<select id="%s" class="v%s%s" name="%s" size="%s" multiple="multiple">' % \ 
    417             (FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__, self.is_required and ' required' or '', 
     528            (self.get_id(), self.__class__.__name__, self.is_required and ' required' or '', 
    418529            self.field_name, self.size)] 
    419530        str_data_list = map(str, data) # normalize to strings 
     
    470581                checked_html = ' checked="checked"' 
    471582            field_name = '%s%s' % (self.field_name, value) 
    472             output.append('<li><input type="checkbox" id="%s%s" class="v%s" name="%s"%s /> <label for="%s%s">%s</label></li>' % \ 
    473                 (FORM_FIELD_ID_PREFIX, field_name, self.__class__.__name__, field_name, checked_html, 
    474                 FORM_FIELD_ID_PREFIX, field_name, choice)) 
     583            output.append('<li><input type="checkbox" id="%s" class="v%s" name="%s"%s /> <label for="%s">%s</label></li>' % \ 
     584                (get_id() + value , self.__class__.__name__, field_name, checked_html, 
     585                get_id() + value, choice)) 
    475586        output.append('</ul>') 
    476587        return '\n'.join(output) 
     
    491602    def render(self, data): 
    492603        return '<input type="file" id="%s" class="v%s" name="%s" />' % \ 
    493             (FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__, 
     604            (self.get_id(), self.__class__.__name__, 
    494605            self.field_name) 
    495606 
  • django/branches/new-admin/django/core/meta/fields.py

    r713 r740  
    1717 
    1818# Values for Relation.edit_inline. 
    19 TABULAR, STACKED = 1, 2 
     19TABULAR, STACKED = "admin_edit_inline_tabular", "admin_edit_inline_stacked" 
    2020 
    2121RECURSIVE_RELATIONSHIP_CONSTANT = 'self' 
     
    156156                return self.default.__get_value__() 
    157157            return self.default 
    158         if self.null: 
     158        if not self.empty_strings_allowed or self.null: 
    159159            return None 
    160160        return "" 
     
    164164        Returns a list of field names that this object adds to the manipulator. 
    165165        """ 
    166         return [name_prefix + self.name
     166        return [name_prefix + self.column
    167167 
    168168    def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False): 
     
    178178            params['maxlength'] = self.maxlength 
    179179        if isinstance(self.rel, ManyToOne): 
     180            params['member_name'] = name_prefix + self.get_db_column() 
    180181            if self.rel.raw_id_admin: 
    181182                field_objs = self.get_manipulator_field_objs() 
     
    184185                if self.radio_admin: 
    185186                    field_objs = [formfields.RadioSelectField] 
    186                     params['choices'] = self.get_choices(include_blank=self.blank, blank_choice=BLANK_CHOICE_NONE) 
    187187                    params['ul_class'] = get_ul_class(self.radio_admin) 
    188188                else: 
     
    191191                    else: 
    192192                        field_objs = [formfields.SelectField] 
    193                     params['choices'] = self.get_choices() 
     193                params['choices'] = self.get_choices_default() 
    194194        elif self.choices: 
    195195            if self.radio_admin: 
    196196                field_objs = [formfields.RadioSelectField] 
    197                 params['choices'] = self.get_choices(include_blank=self.blank, blank_choice=BLANK_CHOICE_NONE) 
    198197                params['ul_class'] = get_ul_class(self.radio_admin) 
    199198            else: 
    200199                field_objs = [formfields.SelectField] 
    201                 params['choices'] = self.get_choices() 
     200              
     201            params['choices'] = self.get_choices_default() 
    202202        else: 
    203203            field_objs = self.get_manipulator_field_objs() 
     
    259259            return val 
    260260 
     261 
    261262    def get_choices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH): 
    262263        "Returns a list of tuples used as SelectField choices for this field." 
     264        
    263265        first_choice = include_blank and blank_choice or [] 
    264266        if self.choices: 
    265267            return first_choice + list(self.choices) 
    266268        rel_obj = self.rel.to 
    267         return first_choice + [(getattr(x, rel_obj.pk.column), repr(x)) for x in rel_obj.get_model_module().get_list(**self.rel.limit_choices_to)] 
    268  
     269        choices = first_choice + [(getattr(x, rel_obj.pk.column), repr(x)) for x in rel_obj.get_model_module().get_list(**self.rel.limit_choices_to)] 
     270  
     271        return choices 
     272 
     273 
     274    def get_choices_default(self): 
     275        if(self.radio_admin): 
     276            return self.get_choices(include_blank=self.blank, blank_choice=BLANK_CHOICE_NONE) 
     277        else: 
     278            return self.get_choices() 
     279 
     280    def _get_val_from_obj(self, obj): 
     281        if obj: 
     282           return getattr(obj, self.column)  
     283        else:  
     284           return self.get_default() 
     285 
     286    def flatten_data(self, obj = None): 
     287         """ 
     288             Returns a dictionary mapping the field's manipulator field names to its 
     289             "flattened" string values for the admin view. Obj is the instance to extract the  
     290             values from.  
     291         """ 
     292         return { self.get_db_column(): self._get_val_from_obj(obj)} 
     293         
     294  
    269295class AutoField(Field): 
    270296    empty_strings_allowed = False 
     
    275301    def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False): 
    276302        if not rel: 
    277             return [] # Don't add a FormField unless it's in a related context. 
     303            return [] # Don't add a FormField unless it's in a related change context. 
    278304        return Field.get_manipulator_fields(self, opts, manipulator, change, name_prefix, rel) 
    279305 
     
    330356    def get_manipulator_field_objs(self): 
    331357        return [formfields.DateField] 
     358 
     359    def flatten_data(self, obj = None): 
     360        val = self._get_val_from_obj(obj) 
     361        return {self.get_db_column(): (val is not None and val.strftime("%Y-%m-%d") or '')} 
    332362 
    333363class DateTimeField(DateField): 
     
    359389            return datetime.datetime.combine(d, t) 
    360390        return self.get_default() 
     391 
     392    def flatten_data(self,obj = None): 
     393        val = self._get_val_from_obj(obj)  
     394        date_field, time_field = self.get_manipulator_field_names('') 
     395        return {date_field: (val is not None and val.strftime("%Y-%m-%d") or ''), 
     396                time_field: (val is not None and val.strftime("%H:%M:%S") or '')} 
    361397 
    362398class EmailField(Field): 
     
    543579        return [formfields.TimeField] 
    544580 
     581    def flatten_data(self,obj = None): 
     582        val = self._get_val_from_obj(obj)  
     583        return {self.get_db_column(): (val is not None and val.strftime("%H:%M:%S") or '')}  
     584 
    545585class URLField(Field): 
    546586    def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs): 
     
    598638    def get_manipulator_field_objs(self): 
    599639        return [formfields.IntegerField] 
     640 
     641    def flatten_data(self, obj = None): 
     642        if not obj:  
     643            # In required many-to-one fields with only one available choice, 
     644            # select that one available choice. Note: We have to check that 
     645            # the length of choices is *2*, not 1, because SelectFields always 
     646            # have an initial "blank" value. 
     647            if not self.blank and not self.rel.raw_id_admin and self.choices: 
     648               choice_list = self.get_choices_default() 
     649               if len(choice_list) == 2: 
     650                  return { self.name : choice_list[1][0] } 
     651        return Field.flatten_data(self, obj) 
    600652 
    601653class ManyToManyField(Field): 
     
    616668            return [formfields.CommaSeparatedIntegerField] 
    617669        else: 
    618             choices = self.get_choices(include_blank=False
     670            choices = self.get_choices_default(
    619671            return [curry(formfields.SelectMultipleField, size=min(max(len(choices), 5), 15), choices=choices)] 
     672 
     673    def get_choices_default(self): 
     674        return Field.get_choices(self, include_blank=False) 
    620675 
    621676    def get_m2m_db_table(self, original_opts): 
     
    638693                len(badkeys) == 1 and badkeys[0] or tuple(badkeys), 
    639694                len(badkeys) == 1 and "is" or "are") 
     695 
     696    def flatten_data(self, obj = None): 
     697        new_data = {}  
     698        if obj: 
     699            get_list_func = getattr(obj, 'get_%s_list' % self.rel.singular) 
     700            instance_ids = [getattr(instance, self.rel.to.pk.column) for instance in get_list_func()] 
     701            if self.rel.raw_id_admin: 
     702                 new_data[self.name] = ",".join([str(id) for id in instance_ids]) 
     703            elif not self.rel.edit_inline: 
     704                 new_data[self.name] = instance_ids  
     705        else: 
     706            # In required many-to-many fields with only one available choice, 
     707            # select that one available choice. 
     708            if not self.blank and not self.rel.edit_inline and not self.rel.raw_id_admin and self.choices: 
     709               choice_list = self.get_choices_default() 
     710               if len(choice_list) == 1: 
     711                   new_data[self.name] = [choices_list[0][0]] 
     712        return new_data 
     713 
    640714 
    641715class OneToOneField(IntegerField): 
     
    721795        field names. If self.fields is None, defaults to putting every 
    722796        non-AutoField field with editable=True in a single fieldset. 
     797         
     798        returns a list of lists of name, dict  
     799        the dict has attribs 'fields' and maybe 'classes'.  
     800        fields is a list of subclasses of Field.  
     801 
     802        Return value needs to be encapsulated. 
    723803        """ 
    724804        if self.fields is None: 
  • django/branches/new-admin/django/core/meta/__init__.py

    r698 r740  
    146146class BadKeywordArguments(Exception): 
    147147    pass 
     148 
     149 
     150class InlineRelatedObject(object): 
     151    def __init__(self,parent_opts, opts, field): 
     152        self.parent_opts = parent_opts 
     153        self.opts = opts 
     154        self.field = field 
     155        self.name = opts.module_name 
     156 
     157    def flatten_data(self,obj = None): 
     158        var_name = self.opts.object_name.lower() 
     159        new_data = {} 
     160        rel_instances = self.get_list(obj) 
     161 
     162        for i, rel_instance in enumerate(rel_instances): 
     163            instance_data = {}  
     164            for f in self.opts.fields + self.opts.many_to_many: 
     165                field_data = f.flatten_data(rel_instance) 
     166                #if hasattr(f, 'editable') and f.editable and f != self.field: 
     167                for name, value in field_data.items(): 
     168                    instance_data['%s.%d.%s' % (var_name, i, name)] = value 
     169            new_data.update(instance_data)              
     170     
     171        return new_data         
     172 
     173    def extract_data(self, data): 
     174        "Pull out the data meant for inline objects of this class, ie anything starting with our module name" 
     175        return data # TODO   
     176     
     177    def get_list(self, parent_instance = None): 
     178        "Get the list of this type of object from an instance of the parent class" 
     179        if parent_instance != None: 
     180            func_name = 'get_%s_list' % self.parent_opts.get_rel_object_method_name(self.opts, self.field) 
     181            func = getattr(parent_instance, func_name) 
     182            list = func() 
     183             
     184            count = len(list) + self.field.rel.num_extra_on_change 
     185            if self.field.rel.min_num_in_admin: 
     186               count = max(count, self.field.rel.min_num_in_admin) 
     187            if self.field.rel.max_num_in_admin: 
     188               count = min(count, self.field.rel.max_num_in_admin) 
     189        
     190            change = count - len(list)  
     191            if change > 0: 
     192                return list + [None for _ in range(change)] 
     193            if change < 0: 
     194                return list[:change] 
     195            else: # Just right 
     196                return list 
     197        else: 
     198            return [None for _ in range(self.field.rel.num_in_admin)] 
     199 
     200     
     201    def editable_fields(self, wrapping_func = lambda x: x): 
     202        """Get the fields in this class that should be edited inline. 
     203        Pass a callable, eg a class, as the second argument to wrap the fields. 
     204        This can be useful to add extra attributes for use in templates.""" 
     205         
     206        return [wrapping_func(f) for f in self.opts.fields + self.opts.many_to_many if f.editable and f != self.field ] 
     207         
     208    def __repr__(self): 
     209        return "<InlineRelatedObject: %s related to %s>" % ( self.name, self.field.name)       
     210 
     211         
    148212 
    149213class Options: 
     
    317381    def get_inline_related_objects(self): 
    318382        return [(a, b) for a, b in self.get_all_related_objects() if b.rel.edit_inline] 
     383 
     384    def get_inline_related_objects_wrapped(self): 
     385        return [InlineRelatedObject(self, opts, field) for opts, field in self.get_all_related_objects() if field.rel.edit_inline] 
     386 
     387    def get_data_holders(self): 
     388        return self.fields + self.many_to_many + self.get_inline_related_objects_wrapped() 
    319389 
    320390    def get_all_related_many_to_many_objects(self): 
     
    595665 
    596666        for f in opts.fields: 
     667            #TODO : change this into a virtual function so that user defined fields will be able to add methods to module or class.  
    597668            if f.choices: 
    598669                # Add "get_thingie_display" method to get human-readable value. 
     
    789860        if cursor.fetchone(): 
    790861            db_values = [f.get_db_prep_save(f.pre_save(getattr(self, f.column), False)) for f in non_pks] 
    791             cursor.execute("UPDATE %s SET %s WHERE %s=%%s" % (opts.db_table, 
    792                 ','.join(['%s=%%s' % f.column for f in non_pks]), opts.pk.column), 
     862            while 1: 
     863                try: 
     864                    idx = db_values.index('') 
     865                    non_pks[idx:idx+1] = [] 
     866                    db_values[idx:idx +1] = [] 
     867                except: break 
     868            cursor.execute("UPDATE %s SET %s WHERE %s=%%s" % (opts.db_table,  
     869                ','.join(['%s=%%s' % f.column for f in non_pks]), opts.pk.column), 
    793870                db_values + [pk_val]) 
    794871        else: 
     
    13331410            order_by.append(db.get_random_function_sql()) 
    13341411        else: 
     1412            if f.startswith('-'): 
     1413                col_name = f[1:] 
     1414                order = "DESC" 
     1415            else: 
     1416                col_name = f 
     1417                order = "ASC" 
    13351418            # Use the database table as a column prefix if it wasn't given, 
    13361419            # and if the requested column isn't a custom SELECT. 
    1337             if "." not in f and f not in [k[0] for k in kwargs.get('select', [])]: 
     1420            if "." not in col_name and col_name not in [k[0] for k in kwargs.get('select', [])]: 
    13381421                table_prefix = opts.db_table + '.' 
    13391422            else: 
    13401423                table_prefix = '' 
    1341             if f.startswith('-'): 
    1342                 order_by.append('%s%s DESC' % (table_prefix, orderfield2column(f[1:], opts))) 
    1343             else: 
    1344                 order_by.append('%s%s ASC' % (table_prefix, orderfield2column(f, opts))) 
     1424             
     1425            order_by.append('%s%s %s' % (table_prefix, orderfield2column(col_name, opts), order)) 
     1426     
    13451427    order_by = ", ".join(order_by) 
    13461428 
     
    13981480    man.__init__ = curry(manipulator_init, opts, add, change) 
    13991481    man.save = curry(manipulator_save, opts, klass, add, change) 
     1482    man.get_inline_related_objects_wrapped = curry(manipulator_get_inline_related_objects_wrapped, opts, klass, add, change) 
     1483    man.flatten_data = curry(manipulator_flatten_data, opts, klass, add, change) 
    14001484    for field_name_list in opts.unique_together: 
    14011485        setattr(man, 'isUnique%s' % '_'.join(field_name_list), curry(manipulator_validator_unique_together, field_name_list, opts)) 
     
    14401524 
    14411525    # Add fields for related objects. 
    1442     for rel_opts, rel_field in opts.get_inline_related_objects(): 
     1526    for obj in opts.get_inline_related_objects_wrapped(): 
    14431527        if change: 
    1444             count = getattr(self.original_object, 'get_%s_count' % opts.get_rel_object_method_name(rel_opts, rel_field))() 
    1445             count += rel_field.rel.num_extra_on_change 
    1446             if rel_field.rel.min_num_in_admin: 
    1447                 count = max(count, rel_field.rel.min_num_in_admin) 
    1448             if rel_field.rel.max_num_in_admin: 
    1449                 count = min(count, rel_field.rel.max_num_in_admin) 
     1528            count = getattr(self.original_object, 'get_%s_count' % opts.get_rel_object_method_name(obj.opts, obj.field))() 
     1529            count += obj.field.rel.num_extra_on_change 
     1530            if obj.field.rel.min_num_in_admin: 
     1531                count = max(count, obj.field.rel.min_num_in_admin) 
     1532            if obj.field.rel.max_num_in_admin: 
     1533                count = min(count, obj.field.rel.max_num_in_admin) 
    14501534        else: 
    1451             count = rel_field.rel.num_in_admin 
    1452         for f in rel_opts.fields + rel_opts.many_to_many: 
    1453             if f.editable and f != rel_field and (not f.primary_key or (f.primary_key and change))
     1535            count = obj.field.rel.num_in_admin 
     1536        for f in obj.opts.fields + obj.opts.many_to_many: 
     1537            if f.editable and f != obj.field
    14541538                for i in range(count): 
    1455                     self.fields.extend(f.get_manipulator_fields(rel_opts, self, change, name_prefix='%s.%d.' % (rel_opts.object_name.lower(), i), rel=True)) 
     1539                    self.fields.extend(f.get_manipulator_fields(obj.opts, self, change, name_prefix='%s.%d.' % (obj.opts.object_name.lower(), i), rel=True)) 
    14561540 
    14571541    # Add field for ordering. 
     
    15931677            getattr(new_object, 'set_%s_order' % rel_opts.object_name.lower())(order) 
    15941678    return new_object 
     1679 
     1680def manipulator_get_inline_related_objects_wrapped(opts, klass, add, change, self): 
     1681    return opts.get_inline_related_objects_wrapped()  
     1682         
     1683def manipulator_flatten_data(opts, klass, add, change, self): 
     1684     new_data = {} 
     1685     obj = change and self.original_object or None 
     1686     for f in opts.get_data_holders(): 
     1687            new_data.update(f.flatten_data(obj)) 
     1688     return new_data 
    15951689 
    15961690def manipulator_validator_unique_together(field_name_list, opts, self, field_data, all_data): 
  • django/branches/new-admin/django/core/validators.py

    r712 r740  
    1313_datere = r'\d{4}-((?:0?[1-9])|(?:1[0-2]))-((?:0?[1-9])|(?:[12][0-9])|(?:3[0-1]))' 
    1414_timere = r'(?:[01]?[0-9]|2[0-3]):[0-5][0-9](?::[0-5][0-9])?' 
    15 alnum_re = re.compile(r'^\w+$') 
     15alnum_re = re.compile(r'^[\w-]+$') 
    1616alnumurl_re = re.compile(r'^[\w/]+$') 
    1717ansi_date_re = re.compile('^%s$' % _datere) 
     
    5454def isAlphaNumeric(field_data, all_data): 
    5555    if not alnum_re.search(field_data): 
    56         raise ValidationError, "This value must contain only letters, numbers and underscores." 
     56        raise ValidationError, "This value must contain only letters, numbers, dashes and underscores." 
    5757 
    5858def isAlphaNumericURL(field_data, all_data): 
  • django/branches/new-admin/django/views/admin/main.py

    r714 r740  
    11# Generic admin views, with admin templates created dynamically at runtime. 
    22 
    3 from django.core import formfields, meta, template_loader 
     3from django.core import formfields, meta, template_loader, template 
    44from django.core.exceptions import Http404, ObjectDoesNot