0

I have a problem when I want to save the objects such as the Tags, and always returned an error because form validation.

Select a valid choice. hello is not one of the available choices.

Here, I want to implement the select input dynamically which customs additional value from the users creation.

For the frontend demo, like this snippet: https://jsfiddle.net/agaust/p377zxu4/

django additional tags


As conceptually, the tags input provide available tags that already created before... But, the important thing is the Users are allowed to create additional tags what they wants.

1. here is my models.py

@python_2_unicode_compatible
class Tag(models.Model):
    title = models.CharField(max_length=200)
    slug = models.SlugField(max_length=200, unique=True)

    def __str__(self):
        return self.title

    class Meta:
        verbose_name = _('Detail Tag')
        verbose_name_plural = _('Tags')

@python_2_unicode_compatible
class Thread(TimeStampedModel):
    title = models.CharField(max_length=200)
    ....
    tags = models.ManyToManyField(
      Tag, blank=True, related_name='tags_thread')

2. forms.py

from myapp.models import (Tag, Thread)

class ThreadForm(forms.ModelForm):
    description = DraceditorFormField()
    tags = forms.ModelMultipleChoiceField(
        to_field_name='slug', # set the value to slug field, not pk/id
        required=False,
        label=_('Additional tags'),
        help_text=_('Sparate by comma to add more than once, or select from available tags'),
        queryset=Tag.objects.all(),
        widget=forms.SelectMultiple(attrs={
            'placeholder': _('Additional tags'),
            'class': 'ui search fluid dropdown dropdown-add-tags'
        })
    )

    class Meta:
        model = Thread
        fields = '__all__'
        exclude = [
            'author', 'topic', 'rating', 
            'created', 'modified'
        ]
        widgets = {
            'title': forms.TextInput(attrs={'placeholder': _('Title')})
        }

    def clean(self):
        # this condition only if the POST data is cleaned, right?
        cleaned_data = super(ThreadForm, self).clean()
        print(cleaned_data.get('tags')) # return queryset of tags

3. views.py

def save_tagging(post_getlist_tags):
    """
    return value list of slugs from the filed of `tags`.
    allow to create if the tag is doesn't exist.
    this function bassed on slug field.

    :param `post_getlist_tags` is request.POST.getlist('tags', [])
    """
    cleaned_slug_tags = []
    for value in post_getlist_tags:
        slug = slugify(value)
        if Tag.objects.filter(slug=slug).exists():
            cleaned_slug_tags.append(slug)
        else:
            tag = Tag.objects.create(title=value, slug=slug)
            cleaned_slug_tags.append(tag.slug)
    return cleaned_slug_tags

@login_required
def thread_new(request, topic_slug):
    ....
    topic = get_object_or_404(Topic, slug=topic_slug)

    if request.method == 'POST':
        form = ThreadForm(request.POST, instance=Thread())
        if form.is_valid():
            initial = form.save(commit=False)
            initial.author = request.user
            initial.topic = topic

            # set tagging, this will not being executed because error form validation
            initial.tags = save_tagging(request.POST.getlist('tags', []))

            initial.save()
            form.save()
        else:
            # forms.errors # goes here..

Let checkout what I have when I typing the additional tags,

additional tags

<select multiple="multiple" id="id_tags" name="tags" placeholder="Additional tags">
  <option value="hello" class="addition">hello</option>
  <option value="albacore-tuna" class="addition">albacore-tuna</option>
  <option value="amur-leopard" class="addition">amur-leopard</option>
  <option value="This other once" class="addition">This other once</option>
</select>

This why I implement my field of tags in the form is like this...

tags = forms.ModelMultipleChoiceField(
    to_field_name='slug'
    ....
)

I would be very appreciated for the answers... :)


Update Solved

Thank you so much for @Resley Rodrigues for help.. Finally, I got it without the form field... only handled in the views and the template.

def save_tagging(post_getlist_tags):
    """
    return objects list of tags.
    allow to create if the tag is doesn't exist.
    this function bassed on slug field.

    :param `post_getlist_tags` is request.POST.getlist('fake_tags', [])
    """
    cleaned_tags = []
    for value in post_getlist_tags:
        slug = slugify(value)
        if Tag.objects.filter(slug=slug).exists():
            tag = Tag.objects.filter(slug=slug).first()
            cleaned_tags.append(tag)
        else:
            # makesure the slug is not empty string.
            # because I found the empty string is saved.
            if bool(slug.strip()):
                tag = Tag.objects.create(title=value, slug=slug)
                tag.save()
                cleaned_tags.append(tag)
    return cleaned_tags


@login_required
def thread_new(request, topic_slug):
    ....
    if request.method == 'POST':
        form = ThreadForm(request.POST, instance=Thread())
        if form.is_valid():
            initial = form.save(commit=False)
            initial.author = request.user
            ....
            form.save()

            # set tagging after created the object
            saved_tags = save_tagging(request.POST.getlist('fake_tags', []))
            initial.tags.add(*saved_tags)

and the templates.html using field named by fake_tags, I just think it should hasn't crash with field that already named by tags.

<select name="fake_tags" multiple="multiple" class="ui search fluid dropdown dropdown-add-tags"></select>

<script>
  $(document).ready(function() {
    $('.ui.dropdown.dropdown-add-tags').dropdown({
      placeholder: '{% trans "Additional tags" %}',
      allowAdditions: true,
      minCharacters: 3,
      apiSettings: {
        url: 'http://api.semantic-ui.com/tags/{query}'
      }
    });
  });
</script>

For edit mode, add the following these lines below after end-tag of $('.ui.dropdown.dropdown-add-tags').dropdown({...});

thread is instance object.

var items = [{% for tag in thread.tags.all %}"{{ tag.title }}"{% if not forloop.last %},{% endif %}{% endfor %}];
$('.ui.dropdown.dropdown-add-tags').dropdown(
  'set selected', items
);
binpy
  • 3,994
  • 3
  • 17
  • 54
  • Maybe you could try with a `MultipleChoiceField` which is not tied to any model... but a better approach would be to create all the missing `Tag`s in the POST method before you initialize the form (kinda loses the point of using forms) – Resley Rodrigues Jan 23 '17 at 15:13
  • I already tried with it, but still returned the same error.. – binpy Jan 23 '17 at 15:14
  • 1
    The problem is you're creating new tags only if the form is valid, but it will never be valid when you have text which doesn't match any existing Tag. You could override the validation and make it return the entire list of tags as it is – Resley Rodrigues Jan 23 '17 at 15:22
  • Google led me to [this post](http://stackoverflow.com/questions/33997530/django-modelchoicefield-allow-objects-creation) which might help – Resley Rodrigues Jan 23 '17 at 15:22
  • So, I do it without field in the form? – binpy Jan 23 '17 at 15:24
  • Ahaa.... I see it... thank so much for your help... – binpy Jan 23 '17 at 15:26

0 Answers0