title: custom data coercion with django modelform
body: <p>Caveat: I don't have a deep knowledge, so if I'm barking up the wrong tree please let me know.</p>
  
  <p>Anyways, I'm working on writing a tagging app. I want the user to be able to enter a list of space separated words rather than have to browse through a gigantic list of tags.</p>
  
  <p>There are two models. One holds the name of the tags, and another holds tag assignments.</p>
  
  <pre><code>class Tags(models.Model):
      name = models.CharField(max_length=50)
  
      def __unicode__(self):
          return self.name
  
  class Tagged(models.Model):
      tag = models.ForeignKey(Tags)
      app = models.CharField(max_length=256)
      app_item = models.IntegerField()
  </code></pre>
  
  <p>The modelform just displays the tag field as that is the only input needed from the user.</p>
  
  <pre><code>class TaggedForm(forms.ModelForm):
      class Meta:
          model = Tagged
          fields = ('tag',)
          widgets = {
              'tag': TextInput(),
          }
  </code></pre>
  
  <p>The problem I'm having is that while I am able to enter a list of space separated choices, the input is rejected as invalid.</p>
  
  <blockquote>
    <p>Select a valid choice. That choice is not one of the available choices.</p>
  </blockquote>
  
  <p>What I would like to do is get the data, coerce it into valid choices myself and return that cleaned data as a choice field (ie: take what's unexpected and user-friendly and make it expected and django friendly).</p>
  
  <p>My question is, how can I do this and simply as possible?</p>
  
  <p>Thanks.</p>
  
  <p><strong>Edit:</strong></p>
  
  <p>The solution follows the recommendation of Tisho.</p>
  
  <p>The data is cleaned in the form and a custom save function handles the saving so the app only needs to pass in a few variables. It's still a bit rough around the edges (no permissions for instance), but it works.</p>
  
  <pre><code>class TaggedForm(forms.Form):
      tags = forms.CharField(max_length=50)
  
      def save(self, app, app_item, tags):
  
          # Convert tag string into tag list (whitespace and duplicates are removed here)
          split_tags = list(set(tags.strip().split(' ')))
          tag_list = []
          for tag in split_tags:
              if tag != ''  and tag != u'':
                  tag_list.append(tag)
  
          # Get list of current tags
          current_tags = Tagged.objects.filter(app=app, app_item=app_item)
  
          # Separate new, stable, and old tags
          # (stable tags are deleted from tag_list leaving it populated by only new tags)
          old_tags = []
          if current_tags:
              for tag in current_tags:
  
                  # Stable tags
                  if str(tag.tag) in tag_list:
  
                      # Delete tag from tag_list (this removes pre-existing tags leaving just new tags)
                      del tag_list[tag_list.index(str(tag.tag))]
  
                  # Old tags
                  elif not str(tag.tag) in tag_list:
                      old_tags.append(tag.tag)
  
          # Remove old tags
          try:
              Tagged.objects.filter(tag__in=old_tags).delete()
          except Tagged.DoesNotExist:
              pass
  
          # Add new tags
          for tag in tag_list:
  
              # Get tag object
              try:
                  tag=Tags.objects.get(name=tag)
                  tag.save()
  
              # Create new tag
              except Tags.DoesNotExist:
                  tag = Tags(name=tag)
                  tag.save()
  
              # Add new tagging relationship
              try:
                  new_tag = Tagged(tag=tag, app=app, app_item=app_item)
                  new_tag.save()
              except Tags.DoesNotExist:
                  pass
  
      def clean(self):
  
          # Get tags
          try:
              tags = self.cleaned_data['tags'].strip().split(' ')
          except KeyError:
              tags = False
  
          # Process tags
          if tags:
  
              # Variables
              tag_list = ''
  
              # Build tag list
              for tag in tags:
                  if tag != '' or tag != u'':
                      tag_list += tag + ' '
  
              # Assign and return data
              self.cleaned_data['tags'] = tag_list
              return self.cleaned_data
  
          # Just return cleaned data if no tags were submitted
          else:
              return self.cleaned_data    
  </code></pre>
  
  <p><strong>Usage</strong>: The tagging logic is kept out of the application logic</p>
  
  <pre><code>tagged_form = TaggedForm(request.POST)
  if tagged_form.is_valid():
      tagged_form.save('articles', 1, request.POST.get('tags',''))
  </code></pre>