Commits

Felipe Vieira committed 3edef78

Added TYPE_ONE based on TYPE_MANY. Basically allow only one choice and render schema with radio input.

Comments (0)

Files changed (5)

         "Returns dictionary of lookups for facet-specific query."
         return {'%s__in' % self.lookup_name: value} if value else {}
 
+class OneToManyFacet(Facet):
+    field_class = forms.models.ModelChoiceField
+
+    def _get_queryset(self):
+        assert self.schema.datatype == self.schema.TYPE_ONE
+        return self.schema.get_choices()
+
+    @property
+    def extra(self):
+        return {
+            'queryset': self._get_queryset(),
+            'widget': forms.RadioSelect,
+        }
+
+    def get_lookups(self, value):
+        "Returns dictionary of lookups for facet-specific query."
+        return {'%s__in' % self.lookup_name: value} if value else {}
+
 
 class IntegerFacet(Facet):
     field_class = forms.IntegerField
     'range': MultiRangeFacet,
     'date':  DateFacet,
     'bool':  BooleanFacet,
+    'one':   OneToManyFacet,
     'many':  ManyToManyFacet,
 }
 
 
 # django
 from django.forms import (BooleanField, CharField, CheckboxSelectMultiple,
-                          DateField, FloatField, ModelForm, ModelMultipleChoiceField,    #MultipleChoiceField,
-                          ValidationError)
-from django.contrib.admin.widgets import AdminDateWidget, FilteredSelectMultiple    #, RelatedFieldWidgetWrapper
+                          DateField, FloatField, ModelForm, ModelChoiceField,
+                          ModelMultipleChoiceField, ValidationError)
+from django.contrib.admin.widgets import AdminDateWidget, FilteredSelectMultiple, AdminRadioSelect    #, RelatedFieldWidgetWrapper
 from django.utils.translation import ugettext_lazy as _
 
 # this app
         'float': FloatField,
         'date': DateField,
         'bool': BooleanField,
+        'one': ModelChoiceField,
         'many': ModelMultipleChoiceField,    #RelatedFieldWidgetWrapper(MultipleChoiceField),
         'range': RangeField,
     }
     FIELD_EXTRA = {
         'date': {'widget': AdminDateWidget},
+        'one': lambda schema: {
+            'widget': AdminRadioSelect
+        },
         'many': lambda schema: {
             'widget': CheckboxSelectMultiple
                       if len(schema.get_choices()) <= 5 else
                 choices = getattr(self.instance, schema.name)
                 defaults.update({'queryset': schema.get_choices(),
                                  'initial': [x.pk for x in choices]})
+            elif datatype == schema.TYPE_ONE:
+                choice = getattr(self.instance, schema.name)
+                defaults.update({'queryset': schema.get_choices(),
+                                 'initial': choice.pk if choice else None,
+                                 # if schema is required remove --------- from ui
+                                 'empty_label' : None if schema.required else u"---------"})
 
             extra = self.FIELD_EXTRA.get(datatype, {})
             if hasattr(extra, '__call__'):
 
             # fill initial data (if attribute was already defined)
             value = getattr(self.instance, schema.name)
-            if value and not datatype == schema.TYPE_MANY:    # m2m is already done above
+            if value and not datatype in (schema.TYPE_ONE, schema.TYPE_MANY):    # choices are already done above
                 self.initial[schema.name] = value
 
     def save(self, commit=True):
                 if subname in related_schemata:
                     # EAV attribute (Attr instance linked to entity)
                     schema = related_schemata.get(subname)
-                    if schema.datatype == schema.TYPE_MANY:
-                        d = self._filter_by_m2m_schema(qs, subname, subsublookup, value, schema, model=related_model)
+                    if schema.datatype in (schema.TYPE_ONE, schema.TYPE_MANY):
+                        d = self._filter_by_choice_schema(qs, subname, subsublookup, value, schema, model=related_model)
                     elif schema.datatype == schema.TYPE_RANGE:
                         d = self._filter_by_range_schema(qs, subname, subsublookup, value, schema)
                     else:
         elif name in schemata:
             # EAV attribute (Attr instance linked to entity)
             schema = schemata.get(name)
-            if schema.datatype == schema.TYPE_MANY:
-                return self._filter_by_m2m_schema(qs, name, sublookup, value, schema)
+            if schema.datatype in (schema.TYPE_ONE, schema.TYPE_MANY):
+                return self._filter_by_choice_schema(qs, name, sublookup, value, schema)
             elif schema.datatype == schema.TYPE_RANGE:
                 return self._filter_by_range_schema(qs, name, sublookup, value, schema)
             else:
         })
         return conditions
 
-    def _filter_by_m2m_schema(self, qs, lookup, sublookup, value, schema, model=None):
+    def _filter_by_choice_schema(self, qs, lookup, sublookup, value, schema, model=None):
         """
         Filters given entity queryset by an attribute which is linked to given
-        many-to-many schema.
+        choice schema.
         """
         model = model or self.model
         schemata = dict((s.name, s) for s in model.get_schemata_for_model())   # TODO cache this dict, see above too
 '''
 class BaseSchemaManager(Manager):
 
-    def for_form(self, *args, **kw):
+    def )for_form(self, *args, **kw):
         return self.filter(choices=None, *args, **kw)
 
     def for_lookups(self, *args, **kw):
     TYPE_FLOAT   = 'float'
     TYPE_DATE    = 'date'
     TYPE_BOOLEAN = 'bool'
+    TYPE_ONE     = 'one'
     TYPE_MANY    = 'many'
     TYPE_RANGE   = 'range'
 
         (TYPE_FLOAT,   _('number')),
         (TYPE_DATE,    _('date')),
         (TYPE_BOOLEAN, _('boolean')),
+        (TYPE_ONE,     _('choice')),
         (TYPE_MANY,    _('multiple choices')),
         (TYPE_RANGE,   _('numeric range')),
     )
         """
         Saves given EAV attribute with given value for given entity.
 
-        If schema is not many-to-one, the value is saved to the corresponding
+        If schema is not a choice, the value is saved to the corresponding
         Attr instance (which is created or updated).
 
-        If schema is many-to-one, the value is processed thusly:
+        If schema is an cvhoice (one-to-one or many-to-one), the value is
+        processed thusly:
 
-        * if value is iterable, all Attr instances for corresponding managed m2m
+        * if value is iterable, all Attr instances for corresponding managed choice
           schemata are updated (those with names from the value list are set to
           True, others to False). If a list item is not in available choices,
           ValueError is raised;
           processed as above (i.e. "foo" --> ["foo"]).
         """
 
-        if self.datatype == self.TYPE_MANY:
-            self._save_m2m_attr(entity, value)
+        if self.datatype in (self.TYPE_ONE, self.TYPE_MANY):
+            self._save_choice_attr(entity, value)
         else:
             self._save_single_attr(entity, value)
 
                 setattr(attr, k, v)
             attr.save()
 
-    def _save_m2m_attr(self, entity, value):
+    def _save_choice_attr(self, entity, value):
+        """
+        Creates or updates BaseChoice(s) attribute(s) for given entity.
+        """
 
         if not hasattr(value, '__iter__'):
             value = [value]
 
+        if self.datatype == self.TYPE_ONE and len(value) > 1:
+            raise TypeError('Cannot assign multiple values "%s" to TYPE_ONE '
+                            'must be only one BaseChoice instance.'
+                            % value)
+
         if not all(isinstance(x, BaseChoice) for x in value):
             raise TypeError('Cannot assign "%s": "Attr.choice" '
                             'must be a BaseChoice instance.'
-                            % ', '.join(value))
+                            % value)
 
         # drop all attributes for this entity/schema pair
         self.get_attrs(entity).delete()
 
-        # Attr instances for corresponding managed m2m schemata are updated
+        # Attr instances for corresponding managed choice schemata are updated
         for choice in value:
             self._save_single_attr(
                 entity,
         return u'%s: %s "%s"' % (self.entity, self.schema.title, self.value)
 
     def _get_value(self):
-        if self.schema.datatype == self.schema.TYPE_MANY:
+        if self.schema.datatype in (self.schema.TYPE_ONE, self.schema.TYPE_MANY):
             return self.choice
         if self.schema.datatype == self.schema.TYPE_RANGE:
             names = ('value_range_%s' % x for x in ('min', 'max'))
 >>> e2.save()
 Traceback (most recent call last):
     ...
-TypeError: Cannot assign "wrong choice": "Attr.choice" must be a BaseChoice instance.
+TypeError: Cannot assign [\'wrong choice\']: "Attr.choice" must be a BaseChoice instance.
 >>> e2.size = [small, large]
 >>> e2.save()
 >>> e3 = Entity.objects.get(pk=e.pk)
 ]
 
 ##
+## one-to-one
+##
+
+>>> protein = Schema.objects.create(name='protein', title='Protein', datatype=Schema.TYPE_ONE)
+>>> egg_albumen = protein.choices.create(title='Egg Albumen')
+>>> gluten = protein.choices.create(title='Gluten')
+>>> lean_meat = protein.choices.create(title='Lean Meat')
+>>> egg_albumen
+<Choice: Egg Albumen>
+>>> gluten.schema
+<Schema: Protein (choices)>
+>>> e = Entity(title='Cane')
+>>> e.protein = egg_albumen
+>>> e.save()
+>>> e2 = Entity.objects.get(pk=e.pk)
+>>> e2.protein
+<Choice: Egg Albumen>
+>>> e2.protein = [gluten, lean_meat]
+>>> e2.save()
+Traceback (most recent call last):
+    ...
+TypeError: Cannot assign multiple values [Choice: Gluten>, <Choice: Lean Meat>] to TYPE_ONE: must be only one BaseChoice instance.
+>>> e3 = Entity.objects.get(pk=e.pk)
+>>> e3.protein
+<Choice: Egg Albumen>
+>>> e2.protein = ['wrong choice']
+>>> e2.save()
+Traceback (most recent call last):
+    ...
+TypeError: Cannot assign [\'wrong choice\']: "Attr.choice" must be a BaseChoice instance.
+
+##
 ## combined
 ##