Solution 1 :

The solution turns out to be very simple, django-crispy-forms.

Once I’ve added the required settings in settings.py

I updated my create-blog.html:

{% load crispy_forms_filters %}

<form class="create-form" method="post" enctype="multipart/form-data">
                        {% csrf_token %}

                        {{ form | crispy }}

                        {% for field in form %}
                            <p>{% for error in field.errors %}
                                <p style="color: red;">{{ error }}</p>
                            {% endfor %}
                            </p>
                        {% endfor %}

                        {% if form.non_field_errors %}
                            <div style="color:red;">
                                <p>{{ form.non_field_errors }}</p>
                            </div>
                        {% endif %}

                        <!-- Submit btn -->
                        <button class="submit-button btn btn-lg btn-primary btn-block" type="submit">POST</button>
                    </form>

Make sure to load crispy_forms_filters at the top of the file.

In my views.py I have to use save_m2m since I’ve used obj.save(commit=False).

def create_blog_view(request):
    context = {}

    user = request.user
    categories = Category.objects.all
    statuses = BlogPost.STATUS

    if not user.is_authenticated:
        return redirect('login')

    form = CreateBlogPostForm(request.POST or None, request.FILES or None)

    if form.is_valid():
        obj = form.save(commit=False)
        author = Account.objects.filter(username=user.username).first()
        obj.author = author
        obj.save()
        form.save_m2m()
        form = CreateBlogPostForm()

    context['form'] = form
    context['categories'] = categories
    context['statuses'] = statuses

    return render(request, 'my_simple_blog/create-blog.html', context)

And you should be able to do multiple select and the choices will be reflected in Django Admin.

Solution 2 :

In my case I was setting a sliced QuerySet in my custom form:

class MyModelAdminForm(forms.ModelForm):
    """Custom form to prevent the huge list of ManyToMany choices"""
    class Meta:
        model = MyModel
        exclude = []

    def __init__(self, *args, **kwargs):
        super(MyModelAdminForm, self).__init__(*args, **kwargs)

        # Keeps only the first 100 choices
        self.fields['list_of_choices'].queryset = Choice.objects.all()[:100]

The problem is that Django raises an exception when applying a filter on a slice while saving the form. So I had to change my code to use a “pure” queryset as proposed in this answer:

class MyModelAdminForm(forms.ModelForm):
    """Custom form to prevent the huge list of ManyToMany choices"""
    class Meta:
        model = MyModel
        exclude = []

    def __init__(self, *args, **kwargs):
        super(MyModelAdminForm, self).__init__(*args, **kwargs)

        # Keeps only the first 100 choices
        ids = [elem.pk for elem in Choice.objects.all()[:100]]
        self.fields['list_of_choices'].queryset = Choice.objects.filter(pk__in=ids)

It’s slower as it makes two queries to the DB, but at least it works with Django Forms and the admin panel works again

Problem :

I want to be able to select multiple categories when a blog post is created. I can do this without a problem on Django admin but I can’t do it using a bootstrap form.

I have a bootstrap form that a user will be able to see the list of categories available in the database. I can show the values in the dropdown menu and I am able to select multiple categories, but when I hit post, it keeps saying {category} is not a valid value.

I have tried save_m2m() but it didn’t work either. A post can have multiple categories and a category can have multiple post.
I can’t figure out if it’s in my model or form or the html file itself.

models.py

class Category(models.Model):
    category_name = models.CharField(max_length=255)
    slug = models.SlugField(blank=True, unique=True)
    date_created = models.DateTimeField(auto_now_add=True, verbose_name='date created')
    date_updated = models.DateTimeField(auto_now=True, verbose_name='date updated')

    class Meta:
        verbose_name_plural = 'categories'

    def __str__(self):
        return self.category_name

class BlogPost(models.Model):
    STATUS = (
        ('draft', 'Draft'),
        ('published', 'Published'),
    )

    title = models.CharField(max_length=500, null=False, blank=False)
    body = models.TextField(max_length=5000, null=False, blank=False)
    image = models.ImageField(upload_to=upload_location, null=False, blank=False)
    date_published = models.DateTimeField(auto_now_add=True, verbose_name='date published')
    date_updated = models.DateTimeField(auto_now=True, verbose_name='date updated')
    author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    slug = models.SlugField(blank=True, unique=True)
    category = models.ManyToManyField(Category)
    status = models.CharField(max_length=10, choices=STATUS, default='draft')

    def __str__(self):
        return self.title

forms.py

class CreateBlogPostForm(forms.ModelForm):

    class Meta:
        model = BlogPost
        fields = ['title', 'body', 'image', 'category', 'status']

views.py

def create_blog_view(request):
    context = {}

    user = request.user
    categories = Category.objects.all
    statuses = BlogPost.STATUS

    if not user.is_authenticated:
        return redirect('login')

    form = CreateBlogPostForm(request.POST or None, request.FILES or None)

    if form.is_valid():
        obj = form.save(commit=False)
        author = Account.objects.filter(username=user.username).first()
        obj.author = author
        obj.save()
        form = CreateBlogPostForm()

    context['form'] = form
    context['categories'] = categories
    context['statuses'] = statuses

    return render(request, 'my_simple_blog/create-blog.html', context)

create-blog.html

<form class="create-form" method="post" enctype="multipart/form-data">
                        {% csrf_token %}

                        <!-- title -->
                        <!-- body -->
                        <!-- image -->


                        <!-- category -->
                        <div class="form-group">
                            <label for="id_category">Category</label>
                            <select multiple class="form-control" id="id_category" name="category">
                                {% for category in categories %}
                                    <option value="{{ category.id }}">{{ category }}</option>
                                {% endfor %}
                            </select>
                        </div>

                        <!-- status -->
                        <div class="form-group">
                            <label for="id_status">Status</label>
                            <select class="form-control" id="id_status" name="status">
                                {% for val, name in statuses %}
                                    <option value="{{ val }}">{{ name }}</option>
                                {% endfor %}
                            </select>
                        </div>

                        {% for field in form %}
                            <p>{% for error in field.errors %}
                                <p style="color: red;">{{ error }}</p>
                            {% endfor %}
                            </p>
                        {% endfor %}

                        {% if form.non_field_errors %}
                            <div style="color:red;">
                                <p>{{ form.non_field_errors }}</p>
                            </div>
                        {% endif %}

                        <!-- Submit btn -->
                        <button class="submit-button btn btn-lg btn-primary btn-block" type="submit">POST</button>
                    </form>

UPDATE:
I got it working to a point where I can select multiple and create the post by sending category id as the value of the multi select option.
But this selection is not reflected in Django Admin.

Comments

Comment posted by bmons

author you are assigning from User class, so it should be author = request.user

Comment posted by azmirfakkri

I’m able to get the right username on this line

Comment posted by bmons

I was offline, filter will give you a list, instead you have to call it with get

Comment posted by azmirfakkri

Thanks author is from Account class. Changed that to get. But this is unrelated to my issue posted.

By

Leave a Reply

Your email address will not be published. Required fields are marked *