Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Added raw_id_admin support to ManyToManyField objects; fixes #260

git-svn-id: http://code.djangoproject.com/svn/django/trunk@516 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 7d374ad5973ff5b14e996a38e078c7d46646ae5d 1 parent 935daf0
Jacob Kaplan-Moss authored August 16, 2005
7  django/conf/admin_media/js/admin/RelatedObjectLookups.js
@@ -9,7 +9,12 @@ function showRelatedObjectLookupPopup(triggeringLink) {
9 9
 }
10 10
 
11 11
 function dismissRelatedLookupPopup(win, chosenId) {
12  
-    document.getElementById(win.name).value = chosenId;
  12
+    var elem = document.getElementById(win.name);
  13
+    if (elem.className.indexOf('vCommaSeparatedIntegerField') != -1 && elem.value) {
  14
+        elem.value += ',' + chosenId;
  15
+    } else {
  16
+        document.getElementById(win.name).value = chosenId;
  17
+    }
13 18
     win.close();
14 19
 }
15 20
 
39  django/core/meta/fields.py
@@ -71,7 +71,10 @@ def __init__(self, name, verbose_name=None, primary_key=False,
71 71
         self.radio_admin = radio_admin
72 72
         self.help_text = help_text
73 73
         if rel and isinstance(rel, ManyToMany):
74  
-            self.help_text += ' Hold down "Control", or "Command" on a Mac, to select more than one.'
  74
+            if rel.raw_id_admin:
  75
+                self.help_text += ' Separate multiple IDs with commas.'
  76
+            else:
  77
+                self.help_text += ' Hold down "Control", or "Command" on a Mac, to select more than one.'
75 78
 
76 79
         # Set db_index to True if the field has a relationship and doesn't explicitly set db_index.
77 80
         if db_index is None:
@@ -572,17 +575,37 @@ def __init__(self, to, rel_name=None, **kwargs):
572 575
             num_in_admin=kwargs.pop('num_in_admin', 0),
573 576
             related_name=kwargs.pop('related_name', None),
574 577
             filter_interface=kwargs.pop('filter_interface', None),
575  
-            limit_choices_to=kwargs.pop('limit_choices_to', None))
  578
+            limit_choices_to=kwargs.pop('limit_choices_to', None),
  579
+            raw_id_admin=kwargs.pop('raw_id_admin', False))
  580
+        if kwargs["rel"].raw_id_admin:
  581
+            kwargs.setdefault("validator_list", []).append(self.isValidIDList)
576 582
         Field.__init__(self, **kwargs)
577 583
 
578 584
     def get_manipulator_field_objs(self):
579  
-        choices = self.get_choices(include_blank=False)
580  
-        return [curry(formfields.SelectMultipleField, size=min(max(len(choices), 5), 15), choices=choices)]
581  
-
  585
+        if self.rel.raw_id_admin:
  586
+            return [formfields.CommaSeparatedIntegerField]
  587
+        else:
  588
+            choices = self.get_choices(include_blank=False)
  589
+            return [curry(formfields.SelectMultipleField, size=min(max(len(choices), 5), 15), choices=choices)]
  590
+            
582 591
     def get_m2m_db_table(self, original_opts):
583 592
         "Returns the name of the many-to-many 'join' table."
584 593
         return '%s_%s' % (original_opts.db_table, self.name)
585  
-
  594
+       
  595
+    def isValidIDList(self, field_data, all_data):
  596
+        "Validates that the value is a valid list of foreign keys"
  597
+        mod = self.rel.to.get_model_module()
  598
+        try:
  599
+            pks = map(int, field_data.split(','))
  600
+        except ValueError:
  601
+            # the CommaSeparatedIntegerField validator will catch this error
  602
+            return
  603
+        objects = mod.get_in_bulk(pks)
  604
+        if len(objects) != len(pks):
  605
+            badkeys = [k for k in pks if k not in objects]
  606
+            raise validators.ValidationError, "Please enter valid %s IDs (the value%s %r %s invalid)" % \
  607
+                (self.verbose_name, len(badkeys) > 1 and 's' or '', len(badkeys) == 1 and badkeys[0] or tuple(badkeys), len(badkeys) == 1 and "is" or "are")
  608
+    
586 609
 class OneToOneField(IntegerField):
587 610
     def __init__(self, to, to_field=None, rel_name=None, **kwargs):
588 611
         kwargs['name'] = kwargs.get('name', 'id')
@@ -631,13 +654,15 @@ def get_related_field(self):
631 654
 
632 655
 class ManyToMany:
633 656
     def __init__(self, to, name, num_in_admin=0, related_name=None,
634  
-        filter_interface=None, limit_choices_to=None):
  657
+        filter_interface=None, limit_choices_to=None, raw_id_admin=False):
635 658
         self.to, self.name = to._meta, name
636 659
         self.num_in_admin = num_in_admin
637 660
         self.related_name = related_name
638 661
         self.filter_interface = filter_interface
639 662
         self.limit_choices_to = limit_choices_to or {}
640 663
         self.edit_inline = False
  664
+        self.raw_id_admin = raw_id_admin
  665
+        assert not (self.raw_id_admin and self.filter_interface), "ManyToMany relationships may not use both raw_id_admin and filter_interface"
641 666
 
642 667
 class OneToOne(ManyToOne):
643 668
     def __init__(self, to, name, field_name, num_in_admin=0, edit_inline=False,
25  django/views/admin/main.py
@@ -510,7 +510,7 @@ def _get_flattened_data(field, val):
510 510
     else:
511 511
         return {field.name: val}
512 512
 
513  
-use_raw_id_admin = lambda field: isinstance(field.rel, meta.ManyToOne) and field.rel.raw_id_admin
  513
+use_raw_id_admin = lambda field: isinstance(field.rel, (meta.ManyToOne, meta.ManyToMany)) and field.rel.raw_id_admin
514 514
 
515 515
 def _get_submit_row_template(opts, app_label, add, change, show_delete, ordered_objects):
516 516
     t = ['<div class="submit-row">']
@@ -722,8 +722,13 @@ def _get_admin_field(field_list, name_prefix, rel, add, change):
722 722
         if change and field.primary_key:
723 723
             t.append('{{ %soriginal.%s }}' % ((rel and name_prefix or ''), field.name))
724 724
         if change and use_raw_id_admin(field):
725  
-            obj_repr = '%soriginal.get_%s|truncatewords:"14"' % (rel and name_prefix or '', field.rel.name)
726  
-            t.append('{%% if %s %%}&nbsp;<strong>{{ %s }}</strong>{%% endif %%}' % (obj_repr, obj_repr))
  725
+            if isinstance(field.rel, meta.ManyToOne):
  726
+                if_bit = '%soriginal.get_%s' % (rel and name_prefix or '', field.rel.name)
  727
+                obj_repr = if_bit + '|truncatewords:"14"'
  728
+            elif isinstance(field.rel, meta.ManyToMany):
  729
+                if_bit = '%soriginal.get_%s_list' % (rel and name_prefix or '', field.rel.name)
  730
+                obj_repr = if_bit + '|join:", "|truncatewords:"14"'
  731
+            t.append('{%% if %s %%}&nbsp;<strong>{{ %s }}</strong>{%% endif %%}' % (if_bit, obj_repr))
727 732
         if field.help_text:
728 733
             t.append('<p class="help">%s</p>\n' % field.help_text)
729 734
     t.append('</div>\n\n')
@@ -766,6 +771,9 @@ def add_stage(request, app_label, module_name, show_delete=False, form_url='', p
766 771
             new_data.update(request.FILES)
767 772
         errors = manipulator.get_validation_errors(new_data)
768 773
         if not errors and not request.POST.has_key("_preview"):
  774
+            for f in opts.many_to_many:
  775
+                if f.rel.raw_id_admin:
  776
+                    new_data.setlist(f.name, new_data[f.name].split(","))
769 777
             manipulator.do_html2python(new_data)
770 778
             new_object = manipulator.save(new_data)
771 779
             pk_value = getattr(new_object, opts.pk.name)
@@ -804,7 +812,7 @@ def add_stage(request, app_label, module_name, show_delete=False, form_url='', p
804 812
         # In required many-to-many fields with only one available choice,
805 813
         # select that one available choice.
806 814
         for f in opts.many_to_many:
807  
-            if not f.blank and not f.rel.edit_inline and len(manipulator[f.name].choices) == 1:
  815
+            if not f.blank and not f.rel.edit_inline and not f.rel.raw_id_admin and len(manipulator[f.name].choices) == 1:
808 816
                 new_data[f.name] = [manipulator[f.name].choices[0][0]]
809 817
         # Add default data for related objects.
810 818
         for rel_opts, rel_field in opts.get_inline_related_objects():
@@ -855,13 +863,18 @@ def change_stage(request, app_label, module_name, object_id):
855 863
         manipulator = mod.ChangeManipulator(object_id)
856 864
     except ObjectDoesNotExist:
857 865
         raise Http404
  866
+
858 867
     inline_related_objects = opts.get_inline_related_objects()
859 868
     if request.POST:
860 869
         new_data = request.POST.copy()
861 870
         if opts.has_field_type(meta.FileField):
862 871
             new_data.update(request.FILES)
  872
+
863 873
         errors = manipulator.get_validation_errors(new_data)
864 874
         if not errors and not request.POST.has_key("_preview"):
  875
+            for f in opts.many_to_many:
  876
+                if f.rel.raw_id_admin:
  877
+                    new_data.setlist(f.name, new_data[f.name].split(","))
865 878
             manipulator.do_html2python(new_data)
866 879
             new_object = manipulator.save(new_data)
867 880
             pk_value = getattr(new_object, opts.pk.name)
@@ -904,7 +917,9 @@ def change_stage(request, app_label, module_name, object_id):
904 917
         for f in opts.fields:
905 918
             new_data.update(_get_flattened_data(f, getattr(obj, f.name)))
906 919
         for f in opts.many_to_many:
907  
-            if not f.rel.edit_inline:
  920
+            if f.rel.raw_id_admin:
  921
+                new_data[f.name] = ",".join([str(i.id) for i in getattr(obj, 'get_%s_list' % f.rel.name)()])
  922
+            elif not f.rel.edit_inline:
908 923
                 new_data[f.name] = [i.id for i in getattr(obj, 'get_%s_list' % f.rel.name)()]
909 924
         for rel_obj, rel_field in inline_related_objects:
910 925
             var_name = rel_obj.object_name.lower()

0 notes on commit 7d374ad

Please sign in to comment.
Something went wrong with that request. Please try again.