Django Form: Select Multipe allow to add the objects

余生颓废 提交于 2021-02-08 11:24:46

问题


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/


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,

<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
);

来源:https://stackoverflow.com/questions/41809057/django-form-select-multipe-allow-to-add-the-objects

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!